On 15 October, GitHub's advisory database added eight advisories for kin-openapi, the Go library many services use to validate HTTP requests against an OpenAPI spec. All eight were fixed by 0.149.0, released on 28 August. Most are panics. In a standard net/http server those cost one request each. One of them, a schema whose allOf, anyOf or oneOf chain loops back on itself, overflows the stack, and Go can't recover from that: on 0.148.0 a single request killed our test server outright. Two other 2026 advisories let requests skip authentication in ValidationHandler. If you depend on kin-openapi, upgrade to 0.149.0 or later. The current release is 0.151.0.
This guide is for Go teams that use kin-openapi's openapi3filter package, directly or through generated middleware, as a validation or security layer. It covers all twelve 2026 advisories, which ones can take a service down, the two that bypass authentication, what we saw when we tested them, and a script that checks your go.mod, your specs and your code.
The short answer
Upgrade github.com/getkin/kin-openapi to 0.149.0 or later. Versions before 0.149.0 crash the whole process on any request to an operation whose schema has an allOf/anyOf/oneOf cycle (CVE-2026-100354). Versions before 0.144.0 let every request through ValidationHandler's security checks when AuthenticationFunc isn't set (CVE-2026-73501). Versions 0.144.0 to 0.146.x can be bypassed with an encoded slash when the router behind it uses gorilla/mux UseEncodedPath() (CVE-2026-100351). Set AuthenticationFunc explicitly, and don't make the validator your only authentication layer. As of 16 October, the Go vulnerability database, which govulncheck reads, doesn't list any of the eight advisories added on 15 October.
What openapi3filter does, and why it's exposed
openapi3filter takes an incoming request, finds the matching operation in your spec and checks it: path, query and header parameters, the body, and the operation's security requirements. It's often the first code to touch a request, before your handler and sometimes before your own auth. That's why its bugs matter. A crash or a wrong answer here happens for unauthenticated clients, on every route the spec describes.
You can use it in three ways. ValidationHandler wraps an http.Handler and loads the spec from a file. ValidateRequest is the lower-level call that middleware such as oapi-codegen's builds on. Or you call the decoders and schema validators yourself. The panics affect all three. The two authentication bugs are specific to ValidationHandler.
The twelve 2026 advisories
| CVE | Fixed in | Kind | What triggers it |
|---|---|---|---|
| CVE-2026-73501 | 0.144.0 | Auth bypass (critical) | ValidationHandler with no AuthenticationFunc |
| CVE-2026-73502 | 0.144.0 | Panic | A content parameter whose media type has no schema |
| CVE-2026-76905 | 0.141.0 | Panic | A malformed multipart/form-data body |
| CVE-2026-77354 | 0.142.0 | Resource use | deepObject query parameter decoding |
| CVE-2026-97394 | 0.143.0 | Panic | An OpenAPI 3.1 array schema with no items |
| CVE-2026-100347 | 0.145.0 | Panic | An unusual HTTP method on a static path, legacy router |
| CVE-2026-100348 | 0.145.0 | Panic at load | additionalProperties that $refs its own schema |
| CVE-2026-100350 | 0.146.0 | CPU | A regular expression compiled on every deepObject request |
| CVE-2026-100351 | 0.147.0 | Auth bypass | Encoded / with gorilla/mux UseEncodedPath() |
| CVE-2026-100352 | 0.148.0 | Panic | YAML NaN or Inf values in the OAS 3.1 validation path |
| CVE-2026-100353 | 0.149.0 | Panic | Three array paths the 0.143.0 fix missed |
| CVE-2026-100354 | 0.149.0 | Fatal crash | allOf/anyOf/oneOf cycles |
Most of these need a particular pattern in your spec, not just a particular request. A spec with no items-less arrays, no content parameters and no combinator cycles is out of reach of half the list. The script below looks for those patterns.
CVE-2026-100353 is an incomplete fix. The fix for CVE-2026-97394 guarded three decoder paths against arrays with no items, which is legal in OpenAPI 3.1. The reporters found three more: query parameters, deepObject properties, and URL-encoded bodies where the array sits inside allOf, anyOf or oneOf. Any service that upgraded to 0.143.0 for the first fix stayed exposed until 0.149.0.
Which panics take your service down
Go's net/http documentation says that if a handler panics, the server "assumes that the effect of the panic was isolated to the active request". It recovers the panic, logs a stack trace and closes the connection. So in a normal net/http server, most of these panics drop one request and leave a stack trace in the logs. That's a nuisance, not an outage, unless an attacker sends them continuously.
Two cases are worse. A panic in a goroutine your code starts, or under a server that doesn't recover, ends the whole process, because an unrecovered panic in any goroutine does. And a stack overflow is not a panic at all. Go treats exceeding the stack limit as a fatal error, which recover() can't catch. That's why CVE-2026-100354 is the one that matters most: a schema cycle in your own spec, and one request to the operation that uses it.
We tested this on 16 October with a small net/http server wrapped in ValidationHandler, a test spec with the patterns described in the advisories, and three library versions:
| Request | 0.143.0 | 0.148.0 | 0.149.0 and 0.151.0 |
|---|---|---|---|
Secured operation, no credentials, no AuthenticationFunc set | 200, handler ran | 500, handler didn't run | 500, handler didn't run |
Query array with no items | Panic, connection closed, server up | Panic, connection closed, server up | 500, server up |
Operation using an allOf cycle | Not tested | fatal error: stack overflow, process exited | 500, server up |
We aren't publishing the test requests. Each pattern is described in its advisory, and the fix is the same whichever one you have.
The two authentication bypasses
CVE-2026-73501, fixed in 0.144.0. ValidationHandler.Load() replaced a missing AuthenticationFunc with NoopAuthenticationFunc, which approves everything. Every security requirement in the spec passed for requests with no credentials at all. Our first test row shows it: on 0.143.0 the secured operation returned 200 and the handler ran. Since 0.144.0, Load() leaves the field empty, and ValidateRequest returns ErrAuthenticationServiceMissing, which ValidationHandler's default error encoder turns into a 500. That fails closed, but it's still the wrong answer for a missing credential. Set AuthenticationFunc yourself and return a proper 401.
CVE-2026-100351, fixed in 0.147.0. ValidationHandler.Middleware is documented as gorilla/mux middleware. kin-openapi's router matches on the decoded path, but gorilla/mux with UseEncodedPath() routes on the encoded one. A request for /public%2Fstatus could be validated as the public /public/status operation and then served by a protected /{id} route. It needs that router option, overlapping public and parameterized routes with the same method, and a handler that trusts the validator's security decision. Since 0.147.0, ValidationHandler rejects a request when an encoded slash would pick a different operation.
Check your service
This script reads a service directory and any spec files you name. It reports which advisories apply to the kin-openapi version in go.mod, flags the spec patterns the advisories depend on, and finds ValidationHandler uses without an AuthenticationFunc or next to UseEncodedPath(). It uses only the Python standard library. YAML specs need PyYAML, or convert them to JSON first.
#!/usr/bin/env python3
"""Check a Go service that uses kin-openapi for the 2026 advisories and the spec
patterns and wiring they depend on.
python3 kin_openapi_check.py /path/to/service [spec.json|spec.yaml ...]
1. go.mod: which kin-openapi advisories apply to the required version.
2. Specs: array schemas with no items, allOf/anyOf/oneOf cycles, content
parameters with no schema, additionalProperties that $ref their own schema.
3. Go source: ValidationHandler without an explicit AuthenticationFunc, and
ValidationHandler next to gorilla/mux UseEncodedPath().
Standard library only; YAML specs need PyYAML, or convert them to JSON first.
Exits 1 if anything is flagged.
"""
import json, os, re, sys
# (id, cve, severity, introduced, fixed, what) from GitHub's advisory database, 16 Oct 2026
ADVISORIES = [
("GHSA-r277-6w6q-xmqw", "CVE-2026-73501", "critical", "0.0.0", "0.144.0", "ValidationHandler fails open with no AuthenticationFunc"),
("GHSA-jpcw-4wr7-c3vq", "CVE-2026-73502", "medium", "0.0.0", "0.144.0", "panic: content parameter with no schema"),
("GHSA-mmfr-pmjx-hw9w", "CVE-2026-76905", "high", "0.10.0", "0.141.0", "panic: malformed multipart body in ConvertErrors"),
("GHSA-xhj3-7xw9-vr34", "CVE-2026-77354", "high", "0.124.0", "0.142.0", "resource use: deepObject query decoding"),
("GHSA-p6wj-qrr4-pgh5", "CVE-2026-97394", "high", "0.136.0", "0.143.0", "panic: OAS 3.1 array with no items"),
("GHSA-fh4f-47mf-f2gj", "CVE-2026-100347", "medium", "0.0.0", "0.145.0", "panic: unusual HTTP method, legacy router"),
("GHSA-mh7x-f8wq-4jhx", "CVE-2026-100348", "medium", "0.0.0", "0.145.0", "panic: self-referential additionalProperties $ref"),
("GHSA-h4r5-mxv5-8mjc", "CVE-2026-100350", "medium", "0.145.0", "0.146.0", "CPU: regexp compiled per deepObject request"),
("GHSA-23vg-4cjm-66q5", "CVE-2026-100351", "high", "0.144.0", "0.147.0", "auth bypass: encoded path with UseEncodedPath"),
("GHSA-rj39-cv6w-8wv7", "CVE-2026-100352", "high", "0.0.0", "0.148.0", "panic: YAML NaN/Inf in OAS 3.1 validation"),
("GHSA-vrcc-3ch7-hppw", "CVE-2026-100353", "high", "0.136.0", "0.149.0", "panic: array with no items (incomplete fix)"),
("GHSA-g8cf-rcjf-c8cf", "CVE-2026-100354", "high", "0.0.0", "0.149.0", "FATAL: stack overflow on allOf/anyOf/oneOf cycle"),
]
def ver(s):
return tuple(int(x) for x in re.match(r"v?(\d+)\.(\d+)\.(\d+)", s).groups())
def check_gomod(root):
path = os.path.join(root, "go.mod")
m = re.search(r"github\.com/getkin/kin-openapi\s+(v[\d.]+)", open(path).read()) if os.path.exists(path) else None
if not m:
print("[info] go.mod: kin-openapi not required directly (check `go list -m all`)")
return 0
v = ver(m.group(1))
hits = [a for a in ADVISORIES if ver(a[3]) <= v < ver(a[4])]
print(f"[{'VULN' if hits else 'ok'}] go.mod requires kin-openapi {m.group(1)}: {len(hits)} of {len(ADVISORIES)} 2026 advisories apply")
for ghsa, cve, sev, _, fixed, what in hits:
print(f" {cve:16} {sev:8} fixed {fixed:8} {what}")
return len(hits)
def load_spec(path):
text = open(path).read()
if path.endswith((".yaml", ".yml")):
import yaml # PyYAML
return yaml.safe_load(text)
return json.loads(text)
def walk(node, path="#"):
if isinstance(node, dict):
yield path, node
for k, v in node.items():
yield from walk(v, f"{path}/{str(k).replace('~', '~0').replace('/', '~1')}")
elif isinstance(node, list):
for i, v in enumerate(node):
yield from walk(v, f"{path}/{i}")
def xof_refs(schema):
for key in ("allOf", "anyOf", "oneOf"):
for sub in schema.get(key) or []:
if isinstance(sub, dict) and isinstance(sub.get("$ref"), str):
yield sub["$ref"]
def check_spec(path):
spec = load_spec(path)
flagged = 0
for where, node in walk(spec):
t = node.get("type")
if (t == "array" or (isinstance(t, list) and "array" in t)) and "items" not in node and "prefixItems" not in node:
flagged += 1
print(f"[FLAG] {path} {where}: array schema with no items (CVE-2026-97394, CVE-2026-100353)")
if "in" in node and "content" in node:
for mt, media in (node.get("content") or {}).items():
if not (media or {}).get("schema"):
flagged += 1
print(f"[FLAG] {path} {where}: content parameter {mt} has no schema (CVE-2026-73502)")
schemas = (spec.get("components") or {}).get("schemas") or {}
for name, schema in schemas.items():
ap = schema.get("additionalProperties") if isinstance(schema, dict) else None
if isinstance(ap, dict) and ap.get("$ref") == f"#/components/schemas/{name}":
flagged += 1
print(f"[FLAG] {path} {name}: additionalProperties references itself (CVE-2026-100348)")
graph = {f"#/components/schemas/{n}": set(xof_refs(s)) for n, s in schemas.items() if isinstance(s, dict)}
seen = set()
for start in graph:
stack, onpath = [(start, iter(graph[start]))], {start}
while stack:
node, it = stack[-1]
nxt = next(it, None)
if nxt is None:
onpath.discard(node)
stack.pop()
elif nxt in onpath:
cycle = tuple(sorted(onpath))
if cycle not in seen:
seen.add(cycle)
flagged += 1
print(f"[FLAG] {path}: allOf/anyOf/oneOf cycle through {', '.join(c.rsplit('/', 1)[1] for c in cycle)} (CVE-2026-100354, fatal before 0.149.0)")
elif nxt in graph:
onpath.add(nxt)
stack.append((nxt, iter(graph[nxt])))
if not flagged:
print(f"[ok] {path}: none of the risky schema patterns found")
return flagged
def check_source(root):
flagged = 0
for dirpath, dirs, files in os.walk(root):
dirs[:] = [d for d in dirs if d not in ("vendor", ".git", "node_modules")]
for f in files:
if not f.endswith(".go") or f.endswith("_test.go"):
continue
p = os.path.join(dirpath, f)
src = open(p, errors="replace").read()
for m in re.finditer(r"ValidationHandler\s*\{", src):
depth, i = 1, m.end()
while depth and i < len(src):
depth += {"{": 1, "}": -1}.get(src[i], 0)
i += 1
if "AuthenticationFunc" not in src[m.end():i]:
line = src.count("\n", 0, m.start()) + 1
flagged += 1
print(f"[FLAG] {p}:{line}: ValidationHandler without AuthenticationFunc (fails open before 0.144.0, 500s after)")
if "ValidationHandler" in src and "UseEncodedPath()" in src:
flagged += 1
print(f"[FLAG] {p}: ValidationHandler with UseEncodedPath() (CVE-2026-100351 before 0.147.0)")
return flagged
def main(argv):
if len(argv) < 2:
sys.exit(__doc__)
root = argv[1]
flagged = check_gomod(root) + check_source(root)
for spec in argv[2:]:
flagged += check_spec(spec)
print(f"\n{flagged} item(s) flagged")
sys.exit(1 if flagged else 0)
if __name__ == "__main__":
main(sys.argv)
We ran it on 16 October against a small fixture service: a go.mod pinned to 0.148.0, a gorilla/mux router with UseEncodedPath(), and a spec containing each pattern:
$ python3 kin_openapi_check.py svc svc/openapi.json
[VULN] go.mod requires kin-openapi v0.148.0: 2 of 12 2026 advisories apply
CVE-2026-100353 high fixed 0.149.0 panic: array with no items (incomplete fix)
CVE-2026-100354 high fixed 0.149.0 FATAL: stack overflow on allOf/anyOf/oneOf cycle
[FLAG] svc/main.go:13: ValidationHandler without AuthenticationFunc (fails open before 0.144.0, 500s after)
[FLAG] svc/main.go: ValidationHandler with UseEncodedPath() (CVE-2026-100351 before 0.147.0)
[FLAG] svc/openapi.json #/paths/~1orders/get/parameters/0/schema: array schema with no items (CVE-2026-97394, CVE-2026-100353)
[FLAG] svc/openapi.json #/paths/~1orders/get/parameters/1: content parameter application/json has no schema (CVE-2026-73502)
[FLAG] svc/openapi.json Tags: additionalProperties references itself (CVE-2026-100348)
[FLAG] svc/openapi.json: allOf/anyOf/oneOf cycle through Clause, Filter (CVE-2026-100354, fatal before 0.149.0)
8 item(s) flagged
Only two advisories apply to 0.148.0, but the spec and wiring checks still matter. They show which advisories a service was actually exposed to before it upgraded, and the missing AuthenticationFunc returns a 500 on any version. The script doesn't follow $refs into external files, and it only detects cycles between named component schemas, so treat a clean result as a good sign, not proof.
What to do
- Upgrade to kin-openapi 0.149.0 or later. Check indirect use too, with
go list -m all | grep kin-openapi, since generated middleware and API gateways pull it in. - Don't wait for govulncheck. As of 16 October, the Go vulnerability database lists 4 of the 12 advisories. Our post on running govulncheck in production covers where it fits. For this library, check the version yourself.
- Set
AuthenticationFuncon everyValidationHandler, and have your handlers or a separate middleware check credentials as well. Validation middleware describes what the spec allows. It shouldn't be the only thing deciding who gets in. - Lint your specs for the patterns above, especially combinator cycles. They're legal OpenAPI, and the validator now handles them, but they're worth knowing about.
- Recover panics in any goroutine that validates requests outside
net/http's own handler goroutine.
The fair view
Most of these are low-impact for most users. A recovered panic per request is noisy, not dangerous, and several bugs need spec patterns many APIs never use. The maintainers fixed each one quickly and published repository advisories alongside the releases. For example, the advisory for CVE-2026-100354 went out on 28 August, minutes after 0.149.0. For the eight advisories added this week, the CVE records and GitHub's reviewed entries only arrived on 15 October, which is why most scanners stayed silent until now.
What's worth taking from it is where the risk sits. A request validator runs on untrusted input before anything else does, and its behaviour depends on how the spec is written. kin-openapi credits matiasinsaurralde, eros938, hash3liZer, satokan, arpitjain099, manus-use, EQSTLab and 232-323 for this year's reports, and we expect more as people keep looking at OpenAPI 3.1's looser schema rules.