# kin-openapi: ValidationHandler security-requirement bypass via encoded-path operation confusion

kin-openapi's ValidationHandler validates the decoded URL.Path while gorilla/mux UseEncodedPath dispatches on RawPath, so /public%2Fstatus passes as public and reaches protected /{id} without auth.

- Payout: Research
- Severity: high
- Program: kin-openapi
- Found by: satokan
- Disclosed: 2026-10-15
- CVE: CVE-2026-100351
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-23vg-4cjm-66q5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-23vg-4cjm-66q5
- Tags: area:oss domain:oss impact:integrity impact:internal-access kind:research platform:ghsa sev:high tech:go technique:parser-differential vuln:access-control vuln:auth-bypass

**Target:** openapi3filter.ValidationHandler.Middleware middleware in Go HTTP services
**Root cause:** ValidationHandler.Load unconditionally builds routers/legacy.NewRouter, which selects the OpenAPI operation from the decoded URL.Path, while the downstream gorilla/mux router with UseEncodedPath() dispatches on URL.RawPath. The two layers disagree on which operation a request targets.
**Steps:**
1. Set up a kin-openapi ValidationHandler.Middleware with an explicit AuthenticationFunc that rejects missing credentials, and a downstream gorilla/mux router using UseEncodedPath().
2. Define a public exact route `/public/status` and a protected parameterized route `/{id}` with the same HTTP method.
3. Send `GET /public%2Fstatus` unauthenticated.
4. Observe validation selects the public `/public/status` operation, the AuthenticationFunc is never called (authentication_calls=0), and the protected `/{id}` handler runs returning HTTP 202.
5. Control: `GET /ordinary` selects the protected operation, invokes auth once, returns 401.
**How found:** Reporter noticed ValidationHandler.Middleware is documented as a gorilla/mux.MiddlewareFunc but internally uses the legacy router keyed on decoded URL.Path, while gorilla/mux with UseEncodedPath() routes on URL.RawPath — a representation mismatch between the auth decision and dispatch. Verified with a self-contained test across v0.144.0–v0.146.0 and through Nginx with both preserving and canonicalizing proxy_pass configs.
**Exploitation:** An unauthenticated attacker sends a request whose encoded path separator (`%2F`) makes the validation layer match a public operation while the downstream router decodes differently and dispatches to a protected handler. The rejecting AuthenticationFunc is skipped entirely, so the protected handler executes with the attacker's request. Reachable through Nginx when proxy_pass preserves the encoded URI (no trailing-slash URI replacement); canonicalizing proxy configs neutralize it.
**Insight:** When a security middleware and the downstream router are separate components, check whether they key on the same request representation (decoded Path vs RawPath). Encoded separators like %2F are the cheapest probe: if the validator and dispatcher disagree, a public route can shadow a protected parameterized one.

## Proof of concept (copied exactly from the original report)

**Encoded-path bypass test invocation**

```
go test -v ./openapi3filter -run '^TestEncodedPathAuthorizationBypass$' -count=1
```

**Nginx preserving proxy_pass PoC container**

```
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
```

**Attack observation showing zero auth calls**

```
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
```

