Authlib coordinated wave: six-leg OAuth/OIDC control-logic pack fixed in 1.9.0¶
Published October 27, 2026 (15:37–15:39Z), all fixed in Authlib 1.9.0. Authlib is the default OAuth2/OIDC library across a large slice of Python web infrastructure, so every leg here is "everywhere at once" — same framing as this wiki's framework message-body-reader rule. Companion to the June 8 LiteLLM/Authlib batch (empty-signatures fail-open sibling lives there).
The legs¶
- CVE-2026-114615 / GHSA-7vr2-2fgp-9w7p (high, 1.3.1–1.8.0) — incomplete algorithm-confusion fix: HMAC forgery with a prefixed public key. The CVE-2024-37568 fix is a single anchored check,
raw_key.startswith(POSSIBLE_UNSAFE_KEYS)at byte 0, while the asymmetric loader (load_pem_public_key) skips anything before-----BEGIN. One leading byte — newline, space, indentation, UTF-8 BOM, or a comment line — makes the guard miss, and the PEM public key becomes the HMAC secret. Attacker holding only the issuer's published public key mints HS256 tokens thatjwt.decode()accepts as authentic. Original attack works again unchanged whenever the stored PEM doesn't start at offset 0. - CVE-2026-114617 / GHSA-c5mf-xmp2-59rc (medium, ≤1.8.0) — ID token disables its own nonce validation.
parse_id_token()readsnonce_supportedfrom the token being validated;falsesets the expected nonce toNone, skipping validation. A captured, validly signed victim ID token replays into an attacker session and authenticates the attacker as the victim at implicit/hybrid RPs. Identical code in the sync and async integration paths — one claim inside the signed envelope edits the verifier's own expectations. - CVE-2026-114618 / GHSA-fch4-3c6x-j4gc (medium, <1.9.0) — default RFC 7523 JWT-bearer grant accepts any audience. Default
get_audiences()returns empty, soverify_claims()only checks thataudexists. A legitimately issued assertion for any other audience, presented to a different authorization server sharing the same signing key, yields an access token. Cross-service lateral movement inside any shared-issuer estate. - CVE-2026-114619 / GHSA-q686-2qmg-hqc7 (medium, <1.9.0) — implicit grant checks raw scope, not allowed scope. Parent
ImplicitGrantfilters requested scope throughclient.get_allowed_scope()intorequest.scope; the OIDC leg then decides "is this an OpenID request" from the raw client-suppliedrequest.payload.scope. A client allowedprofilebut notopenidrequestsscope=openid profileand gets a signed ID token after consent. - CVE-2026-114620 / GHSA-x4rv-c3wx-4cr8 (medium, 1.6.0–1.8.x) — JAR
request_uriunvalidated SSRF, pre-auth. The RFC 9101 extension registers onbefore_get_authorization_grant, so it runs before any authentication; the only gate is thatclient_idnames an existing client — a public identifier. Therequest_uriparameter goes to theget_request_object()hook raw; the pre-registration allowlist (request_uris/require_request_uri_registration) is defined in the library and never consulted, and both docstrings tell integrators to implement the hook as an unrestrictedrequests.get(request_uri). Deployments that followed the docs own an unauthenticated SSRF primitive. - CVE-2026-114614 / GHSA-jj7h-c3hg-m23x (low, 0.13–1.7.2) —
is_secure_transport()loopback exemption bypassed by userinfo. Textualstr.startswith("http://localhost:")acceptshttp://localhost:80@evil.example/cbwhose actual host isevil.example. Smuggles cleartext non-loopback URLs past the TLS-required guard at dynamic client registration, RFC 8414 metadata validation, and error-redirect rendering.
Operator rules¶
- Byte-0 anchoring audit (leg 1). Any denylist guard implemented as
startswith/startswith-familyagainst input that a different parser later skips-whitespace-before-parsing is bypassable with one prefix byte. For any JWS library: fetch the issuer's public JWKS,POSTan HS256 token signed with (a) raw PEM, (b)"\n"+PEM, (c) BOM+PEM, (d) comment-line+PEM, headeralg=HS256+kidof the real RSA/EC key. Acceptance on (b)–(d) only = you found the incomplete-fix shape. This is the third member of this wiki's anchored-check family (see there.matchvs full-match Jupyter origin rule — here the asymmetry is guard-vs-loader instead of guard-vs-matcher). - Token-claims-as-verifier-config (leg 2). Enumerate every claim the validation code reads out of the token itself to configure its own checks. Any boolean/optional claim inside the signed envelope that can relax validation (
nonce_supported:falsestyle) converts a stolen-but-valid token into a cross-session replay. Auth flow matrix: code (unaffected) vs implicit vs hybrid vs directparse_id_token()calls — test the non-code legs even when the login button spells "authorization code." - Existence-vs-equality on identity claims (leg 3). For every token/assertion verifier, ask whether each claim is checked for presence or for value:
audexistence-only = cross-audience replay against any sibling service on the same signing key. Probe: mint a legitimate low-priv assertion for audience A, present to sibling service B's token endpoint. - Raw-vs-filtered reads (leg 4). After any normalization/filtering step, sweep for code that re-reads the original payload field instead of the filtered value. Decision-table test: client metadata permits
profileonly; requestopenid profile; a signed ID token withsub= scope-restriction bypass. - Allowlist-defined-but-never-consulted (leg 5). Grep OAuth servers for config knobs that exist in schema/docstrings (
request_uris,require_request_uri_registration) and prove the enforcement site never reads them. Then pre-auth SSRF probe:GET /authorize?client_id=<public-id>&request_uri=http://<owned-callback>/— a fetch at your callback before any login interaction is the finding. Docstring-shaped hooks ("implement asrequests.get(url)") are fingerprintable: expect this leg on any JAR deployment. - URL authority grammar (leg 6). Per RFC 3986 the authority is
[userinfo@]host[:port]. Any prefix/regex origin or scheme check must be probed withuserinfo@hostshapes:http://localhost:80@evil.example/,https://evil.example\@localhost/, plus this page family's backslash/@/#/?battery. URL-string checks where a URL parser exists = fail by default.
Validation boundaries¶
Disposable IdP/RP lab only (Authlib's own provider examples run in minutes). Leg 1 uses the lab's own issued public key; leg 3 uses two lab services sharing a signing key; leg 5 fetches an owned callback with no content. Never mint/replay tokens against real issuers, never point request_uri at metadata or internal ranges.
Skipped in this wave (tracked, no page)¶
Rustls pack (ECH buffered-data exposure, ServerHello KX-record poisoning, Debug-format secret leak, Brotli pre-auth 1 GiB alloc) — TLS-library internals, Debug-format secret leg tracked; snappy-java trio + compress-zstd panic + dnspython/dd-trace/msgpack/qs/undici DoS singles — memory-safety/availability class per parser precedent; MongoDB BSON regex OOB read (dup of Oct 8 86gh enrichment).