GitHub Advisoryadvisory · Go
GHSA-23vg-4cjm-66q5 kin-openapi: ValidationHandler security-requirement bypass via encoded-path operation confusion
HIGH
Official page ↗
- Aliases
- CVE-2026-100351
- Ecosystem
- Go
- Packages
- github.com/getkin/kin-openapi
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
- Weaknesses
- CWE-289, CWE-436
- Reviewed
- 2026-10-15
Affected packages
• Go github.com/getkin/kin-openapi: >= 0.144.0, < 0.147.0 → fixed in 0.147.0
Description
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.
Pub…
Related
Alias · CVE (1)
- CVE-2026-100351 kin-openapi is a Go project for handling OpenAPI files. vulnerability
References
- https://github.com/getkin/kin-openapi/security/advisories/GHSA-23vg-4cjm-66q5 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-100351 advisory
- https://github.com/getkin/kin-openapi/commit/eda80e2676e9f577ceed2dd80e64f16083edb041 web
- https://github.com/getkin/kin-openapi package
- https://github.com/getkin/kin-openapi/releases/tag/v0.147.0 web