GIVE YOUR AGENT THE EVIDENCE

Set up in your agent.

One prompt to get started. Paste it into your coding agent to connect cve.new through MCP and bring vulnerability context into your workflow.

Your agent needs remote MCP support and access to https://cve.new/mcp.

Read the setup guide

GIVE YOUR AGENT THE EVIDENCE

Investigate in your agent.

Turn this advisory into a focused investigation. Ask your agent to check your project’s versions, explain the evidence, and suggest a compatible fix.

Your agent needs remote MCP support and access to https://cve.new/mcp.

Read the setup guide

GHSA-8m4g-j4wp-wgcghigh7.4

Authlib accepts an empty HMAC key, allowing JWS/JWT signature forgery (HS256/HS384/HS512)

Check your stack

The advisory, in context

Reporter: Cavan Loughran, Celvex Group Inc.

Summary

Authlib's symmetric (oct) key handling accepts an empty key (empty bytes b'' / empty string '') as a valid HMAC signing and verification key. An empty HMAC key produces a deterministic digest that any party can reproduce, so an attacker who knows (or can cause) the application to verify a token with an empty key can forge arbitrary tokens that pass HS256/HS384/HS512 signature verification. This is the Authlib member of the cross-language empty-HMAC-key family already assigned CVE-2026-44351 (fast-jwt, npm) and CVE-2026-45363 (ruby-jwt, RubyGems).

This is DISTINCT from Authlib's existing recent JWT-verification advisories. It is not closed by CVE-2026-27962 / GHSA-wvwj-cvrp-7pv5 (JWS JWK header injection, the key=None case), nor by CVE-2026-28802 / GHSA-7wc2-qxgw-g8gg (alg:none blank-signature bypass), nor by GHSA-5357-c2jx-v7qh (algorithm confusion with asymmetric public keys). Those address header-driven key injection, the none algorithm, and asymmetric-vs-symmetric confusion respectively; none of them rejects a zero-length oct key.

Affected versions

Authlib <= 1.7.2 (latest release 1.7.2, 2026-05-06). Confirmed present at the v1.7.2 release tag and on the current main HEAD. The affected code is in authlib/jose/rfc7518/oct_key.py and authlib/jose/rfc7518/jws_algs.py.

Root cause (exact code location, release tag v1.7.2)

  • authlib/jose/rfc7518/oct_key.py, class OctKey:
    • validate_raw_key is a type-only check: return isinstance(key, bytes). An empty bytes object passes.
    • import_key rejects only keys whose material starts with known unsafe prefixes (if raw_key.startswith(POSSIBLE_UNSAFE_KEYS): raise ValueError(...), i.e. PEM/SSH prefixes). Empty bytes do not match any unsafe prefix, so b'' is accepted.
    • get_op_key loads the raw key without validating its length.
    • No check anywhere rejects a zero-length key, so an empty key b'' passes every gate.
  • authlib/jose/rfc7518/jws_algs.py, class HMACAlgorithm:
    • prepare_key: return OctKey.import_key(raw_data).
    • sign / verify call key.get_op_key(...) then hmac.new(op_key, msg, self.hash_alg) / hmac.compare_digest(...). With an empty op key this becomes hmac.new(b'', ...): a deterministic, publicly reproducible digest. There is no empty-key or minimum-length check.

Why this is a real signature bypass

An empty HMAC key makes the signature a public, deterministic function of the header and payload, so anyone can compute a valid signature and forge tokens. The realistic trigger matches the sibling CVEs: the deploying application sources the verification key from a place that can be empty at runtime, for example os.environ.get('JWT_SECRET', ''), a database/config column whose default is '', or a key-resolver that returns '' on a miss. Authlib is widely used to BUILD token verifiers (OAuth 2.0 resource servers, OIDC relying parties), and Authlib's own recent advisory history shows the JWS/JWT verify path is the live attack surface, so a key-resolution path that can yield '' is a realistic exposure.

Reproduction (prose, no weaponized exploit)

  1. In an application that uses Authlib to verify JWS/JWT, arrange for the verification key to be an empty value (e.g. an unset environment variable defaulted to '').
  2. An attacker constructs a token with header {alg: HS256} and an arbitrary payload, and computes the HS256 signature using an empty key (a value any party can reproduce because the key is empty).
  3. Authlib's HMAC verification accepts the token as validly signed, because OctKey.import_key did not reject the empty key and hmac.compare_digest matches the attacker-computable digest. The forged token is treated as authentic.

No live system was probed; the above describes the class. The vulnerable code path and the absent guard are confirmed by direct reading of the public source at the v1.7.2 tag and on current main.

Proof the fix is known-good (no novelty risk)

The same maintainer's newer JOSE library, joserfc, ALREADY rejects empty oct key material outright. In src/joserfc/_rfc7518/oct_key.py, import_key raises a ValueError on empty oct material, with an explicit note that an empty oct key produces a deterministic HMAC digest that any party can reproduce, allowing trivial signature forgery in JWS HS256/HS384/HS512 verification. (joserfc additionally warns on keys below 112 bits.) The same guard is simply absent from the older, far more widely deployed Authlib rfc7518 package. The flagship Python JOSE libraries also guard: PyJWT raises InvalidKeyError('HMAC key must not be empty.') for zero-length keys, and jwcrypto enforces an RFC 7518 minimum key length.

Impact

Token forgery / authentication bypass in OAuth 2.0 resource servers and OIDC relying parties built on Authlib whenever the HMAC verification key can be empty at runtime. An attacker can mint tokens with arbitrary claims (for example elevated scopes or another user's subject) that pass signature verification.

Suggested remediation

Reject empty oct key material in OctKey.import_key (and/or validate_raw_key), exactly as the maintainer's joserfc library already does, before the key can reach hmac.new in HMACAlgorithm.sign / verify. Consider also adopting joserfc's sub-112-bit minimum-length warning/rejection for HMAC keys.

Disclosure

Reporter: Cavan Loughran, Celvex Group Inc.

Affected packages

Start with the recorded fix for your release branch. Check your installed version and test compatibility before upgrading. Fixing this advisory does not rule out other vulnerabilities in the release.

Ecosystem versions

Earlier releases before 1.9.1

Fixed in 1.9.1

65 explicitly affected versions

0.1, 0.10, 0.11, 0.12, 0.12.1, 0.13, 0.14, 0.14.1, 0.14.2, 0.14.3, 0.15, 0.15.1, 0.15.2, 0.15.3, 0.15.4, 0.15.5, 0.15.6, 0.1rc0, 0.2, 0.2.1, 0.3, 0.4, 0.4.1, 0.5, 0.5.1, 0.6, 0.7, 0.8, 0.9, 1.0.0, 1.0.0a1, 1.0.0a2, 1.0.0b1, 1.0.0b2, 1.0.0rc1, 1.0.1, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, 1.3.2, 1.4.0, 1.4.1, 1.5.0, 1.5.1, 1.5.2, 1.6.0, 1.6.1, 1.6.10, 1.6.11, 1.6.12, 1.6.2, 1.6.3, 1.6.4, 1.6.5, 1.6.6, 1.6.7, 1.6.8, 1.6.9, 1.7.0, 1.7.1, 1.7.2, 1.8.0, 1.9.0

View affected-version evidence
{
  "ranges": [
    {
      "events": [
        {
          "introduced": "0"
        },
        {
          "fixed": "1.9.1"
        }
      ],
      "type": "ECOSYSTEM"
    }
  ],
  "versions": [
    "0.1",
    "0.10",
    "0.11",
    "0.12",
    "0.12.1",
    "0.13",
    "0.14",
    "0.14.1",
    "0.14.2",
    "0.14.3",
    "0.15",
    "0.15.1",
    "0.15.2",
    "0.15.3",
    "0.15.4",
    "0.15.5",
    "0.15.6",
    "0.1rc0",
    "0.2",
    "0.2.1",
    "0.3",
    "0.4",
    "0.4.1",
    "0.5",
    "0.5.1",
    "0.6",
    "0.7",
    "0.8",
    "0.9",
    "1.0.0",
    "1.0.0a1",
    "1.0.0a2",
    "1.0.0b1",
    "1.0.0b2",
    "1.0.0rc1",
    "1.0.1",
    "1.1.0",
    "1.2.0",
    "1.2.1",
    "1.3.0",
    "1.3.1",
    "1.3.2",
    "1.4.0",
    "1.4.1",
    "1.5.0",
    "1.5.1",
    "1.5.2",
    "1.6.0",
    "1.6.1",
    "1.6.10",
    "1.6.11",
    "1.6.12",
    "1.6.2",
    "1.6.3",
    "1.6.4",
    "1.6.5",
    "1.6.6",
    "1.6.7",
    "1.6.8",
    "1.6.9",
    "1.7.0",
    "1.7.1",
    "1.7.2",
    "1.8.0",
    "1.9.0"
  ]
}

References