CVE-2026-100351: kin-openapi: ValidationHandler security-requirement bypass via encoded-path operation confusion
## Summary `openapi3filter.ValidationHandler.Middleware` validates requests with kin-openapi's legacy router, which selects an OpenAPI operation from the decoded `URL.Path`. The method is documented as implementing `gorilla/mux.MiddlewareFunc`, but a downstream gorilla/mux router configured with `UseEncodedPath()` selects its handler from the encoded representation (`URL.RawPath`). An unauthenticated request such as `/public%2Fstatus` can therefore be validated as an intentionally public operation (`/public/status`) and then dispatched to a protected operation (`/{id}`). The configured, rejecting `AuthenticationFunc` is never invoked, and the protected downstream handler runs. This is distinct from GHSA-r277-6w6q-xmqw. The behavior below uses an explicit authentication callback that rejects missing credentials and remains reproducible after the v0.144.0 fix for the unsafe default callback. ## Affected products - Product: `github.com/getkin/kin-openapi` - Component: `openapi3filter.ValidationHandler.Middleware` - Confirmed post-GHSA affected releases: v0.144.0, v0.145.0, and v0.146.0 - Reporter attestation: each listed release was checked out separately, the attached single-file test was copied into `openapi3filter/`, and that test passed; maintainers should independently confirm the version range - Confirmed affected development revision: `d33e44e67734dcde81b576582185e1343fb3985d` - v0.143.0 also reproduces this mechanism, but GHSA-r277-6w6q-xmqw already provides a simpler bypass on that release; the distinct post-fix claim starts at v0.144.0 - Earlier releases: not tested; please treat the introduction boundary as unknown ## Security impact An unauthenticated remote requester can reach a handler whose OpenAPI operation requires authentication when all of the following are true: 1. the application uses `ValidationHandler.Middleware` as the security gate; 2. the downstream router preserves and interprets an encoded path, such as gorilla/mux with `UseEncodedPath()`; 3. a public exact route and a protected parameterized route overlap after decoding; 4. the public and protected operations expose the same HTTP method; 5. the protected handler relies on the validation layer's security decision. The protected operation's path-parameter value is constrained: it is an encoded representation that decodes to a public literal path (`public%2Fstatus` in this PoC), not an arbitrary attacker-selected identifier. A protected operation without a compatible parameterized path is not reachable through this specific mechanism. - Primitive: the security decision is made for a different OpenAPI operation than the operation dispatched by the application router. - Demonstrated impact: a protected handler executes while the explicitly rejecting `AuthenticationFunc` is never invoked. - Impact ceiling: confidentiality or integrity loss when the reachable protected handler has that authority; the PoC does not demonstrate data disclosure or state modification. - Availability impact: not demonstrated. ## Root cause At the confirmed development revision: - `openapi3filter/validation_handler.go`: `ValidationHandler.Load` constructs `routers/legacy.NewRouter` unconditionally. - `routers/legacy/router.go`: route selection uses the decoded request path. - `routers/gorillamux/router.go`: the kin-openapi gorilla router constructs a gorilla/mux router with `UseEncodedPath()`. - `ValidationHandler.Middleware` forwards the unchanged request after validation. The component making the authentication decision and the component dispatching the handler can therefore disagree about the selected operation. ## Reproduction The attached PoC uses a real `net/http` server, an explicit API-key callback that rejects missing credentials, and a downstream gorilla/mux router with `UseEncodedPath()`. From a kin-openapi checkout, copy the self-contained attachment `encoded_path_route_confusion_test.go.txt` into `openapi3filter/` as `encoded_path_route_confusion_test.go`, then run: ```sh go test -v ./openapi3filter -run '^TestEncodedPathAuthorizationBypass$' -count=1 ``` Expected attack observation: ```text status=202 downstream_route=/{id} protected_id=public%2Fstatus authentication_calls=0 wiring=Router.Use status=202 downstream_route=/{id} protected_id=public%2Fstatus authentication_calls=0 ``` Controls included in the test show that: - `/ordinary` selects the protected operation, invokes authentication once, and is rejected with HTTP 401; - `/public/status` reaches only the intentionally public handler; - the encoded request reaches the protected `/{id}` handler with HTTP 202 while the authentication callback remains uncalled; - both external wrapping and the documented `router.Use(validationHandler.Middleware)` wiring reproduce the same result. ## Reverse-proxy reachability The same request was reproduced through Nginx 1.31.3 using the official `nginx:alpine` image digest `sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752`. The exact container invocation was: ```sh docker run --rm --name kin-openapi-nginx \ --add-host=host.docker.internal:host-gateway \ -p 18081:8080 -p 18082:8081 \ -v "$PWD/poc/nginx.conf:/etc/nginx/nginx.conf:ro" \ nginx:alpine ``` Container port 8080 uses `proxy_pass http://host.docker.internal:18080;` without a replacement URI. Host port 18081 therefore preserved `/public%2Fstatus`; the protected handler returned HTTP 202 and the authentication callback remained at zero calls. With `proxy_pass http://host.docker.internal:18080/;` on container port 8081 / host port 18082, Nginx canonicalized the upstream URI; both layers selected `/public/status` and the bypass did not occur. Nginx access-log observations: ```text server_port=8080 "GET /public%2Fstatus HTTP/1.1" 202 0 "curl/8.7.1" server_port=8081 "GET /public%2Fstatus HTTP/1.1" 204 0 "curl/8.7.1" server_port=8080 "GET /ordinary HTTP/1.1" 401 0 "curl/8.7.1" ``` The response route headers were `X-Poc-Route: /{id}` on the preserving listener and `X-Poc-Route: /public/status` on the canonicalizing listener. Backend observations for the two requests were: ```text request_uri="/public%2Fstatus" path="/public/status" raw_path="/public%2Fstatus" auth_before=0 completed request_uri="/public%2Fstatus" auth_after=0 request_uri="/public/status" path="/public/status" raw_path="" auth_before=0 completed request_uri="/public/status" auth_after=0 ``` This control shows that upstream path handling is a deployment precondition rather than claiming that every proxy configuration is affected. The canonical protected control produced: ```text request_uri="/ordinary" path="/ordinary" raw_path="" auth_before=0 completed request_uri="/ordinary" auth_after=1 ``` ## Suggested remediation The authentication layer and downstream dispatcher must select the same OpenAPI operation from the same request representation. Potential approaches: 1. make `ValidationHandler` accept a caller-supplied `routers.Router` and require it to match downstream routing semantics; 2. document that `NewValidator` already accepts a router but is safe only when the supplied router selects operations using the same semantics as the dispatcher; callers must also supply an `AuthenticationFunc` through `ValidationOptions`, or protected requests fail with the existing missing-authentication-service error; 3. as an interim hardening option, make `ValidationHandler` fail closed on an encoded path separator when `URL.Path` and `URL.RawPath` can select different operations, and verify this behavior against the full suite; 4. document the route-identity requirement and add encoded-separator regression tests. Replacing the legacy router with the kin-openapi gorilla router closes this PoC, but a local compatibility check changed two existing missing-path-parameter tests from HTTP 400 semantics to route-not-found HTTP 404 semantics. A direct router swap is therefore not a fully validated drop-in fix. ## Public-reference triage The public advisory list and related path-encoding reports reviewed on 2026-08-18 did not describe this authentication-boundary crossing. Relevant nearby reports include issues #397, #1089, and #1105 and PR #400. This search is not proof of novelty; please deduplicate against private reports. ## Credit Preferred credit: Satoru Kanno (GMO CONNECT Inc., GMO Internet Group, Inc.)
Recommended action
Recommended action
Upgrade affected packages to a patched version: github.com/getkin/kin-openapi 0.147.0.
Technical details
- Vendor
- Not specified
- Product
- github.com/getkin/kin-openapi
- Exploitation
- none known
- Evidence
- official
Evidence and sources
This record is attributed to GitHub Advisories. Exploitation status and remediation guidance are kept separate from the vulnerability's technical severity.
Open primary source