Skip to content
Back to evidence wall

PyJWT / CVE-2022-29217

Public key formats missing from the HMAC key blocklist

Historical vulnerabilityDetection match (historical test)PyPI

The HMAC algorithm blocks only some public-key strings, so OpenSSH keys such as ssh-ed25519 can be used as an HMAC secret and an attacker can sign accepted tokens with the public key.

Evidence chain

Follow each source in order. Missing material is explicit; an advisory or fix link does not stand in for a product test.

  1. Product observation

    An L0 scan (no hints) of the released archive produced a finding that meets the location and CWE criteria registered before the scan. This is detection of a known issue, not an original discovery or a successful exploit.

    Release-archive scan result
  2. Human / upstream attribution

    The advisory credits Aapo Oksman (Nixu) with the disclosure; see the PyJWT advisory for fix credits. Vettalon performed detection of a known issue only.

    Upstream advisory
  3. Fixed-version comparison

    The upstream fixed release was scanned with the same parameters to see whether the patched code is still flagged. This is a scan control, not an exploit or differential test.

    Fixed-release scan control
  4. Unverified scope

    The models may have seen this advisory or its fix during training, so a match does not show that they reasoned it out from the code. Candidates and needs_human results are not automatic confirmation, and no PoC or exploit was run. This was a single run and model output is nondeterministic. Five hand-picked known issues cannot support a recall or false-positive rate. Other output is published as counts only and was not reviewed. The raw report.json and trace are not public; only their SHA-256 is listed. The run status in the table is what the product reported: incomplete means some unit was deferred, truncated, failed or could not be cross-checked, with the reason shown, but the target file was sent for review in every arm. On the fixed release, "not flagged" only means no unrefuted output in the corresponding range carried a listed CWE; the other output in that arm was not reviewed and says nothing about the fixed release being free of other issues. The 2.4.0 archive ships a regression test named after the advisory (tests/test_advisory.py); it was removed before scanning. 2.3.0 has no such file.

    Read limits

What this evidence supports

The 2.3.0 archive scan raised a CWE-327 candidate at jwt/algorithms.py:193 (HMACAlgorithm.prepare_key); grok judged it plausible and it ended as needs_human, meeting the registered criteria. The fixed 2.4.0 scan with the same parameters produced nothing in the corresponding range with a listed CWE.

Test method

Official release archives of the vulnerable and fixed versions were downloaded from the package registry, checked against SHA-256 and scanned as whole trees without git history. Both arms used the same parameters: deep profile, claude as finder, grok and agy (GPT-OSS) as cross-family verifiers, at most 8 audit units, one verifier run per candidate, PoC off. Model input contained no CVE id, advisory text or fix diff, and changelog files were removed from both arms. Match criteria (file, line ranges, CWE list) were committed publicly at 12:51 UTC on 2026-10-20; the first scan started 29 seconds later. The registeredAt field inside the file says 12:55; it was a hand-typed estimate made before the commit and is wrong. The commit time is authoritative, and the file was left unchanged so its digest still matches.

Mode
release-archive
Finding status
needs_human
Location
jwt/algorithms.py:193
Download public record (JSON)

Public detection evidence snapshot

The HMAC key blocklist misses OpenSSH EC/EdDSA public keys, allowing HS256 algorithm confusion.

grok: plausible. The verifier did not refute the candidate; it remains needs_human and is not a confirmation.

Finder
claude-opus-5-5 (Anthropic)
Verifier and verdict
grok-4.7 (xAI): plausible
Tool version
vettalon 0.3.0 (built from source on 2026-10-20)
Model versions
Finder claude-opus-5-5 (claude CLI 2.1.300); verifiers grok-4.7 (grok CLI 1.0.62) and gpt-oss-120b-medium (agy 1.4.3)
Historical scope
Whole release archive, no git history; up to 8 audit units; changelog files removed from both arms; L0 (no CWE, location or advisory hint)

Rerun detection

Install the Vettalon CLI and configure your models and credentials in config.toml. These steps download the official release archive, check its SHA-256 and scan the whole tree; the pre-registration lists excluded files. Model and rule versions may differ; identical results are not guaranteed. Deep mode sends source to configured models. This is detection only; it does not run the upstream project or verify exploitation.

curl -sSfLo release-archive https://files.pythonhosted.org/packages/1d/8e/01bdcfdbb352daaba8ea406d9df149c5bba7dbf70f908d4fa4c269fe6a08/PyJWT-2.3.0.tar.gz
shasum -a 256 release-archive   # expect b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41
mkdir release-tree && bsdtar -xf release-archive --strip-components 1 -C release-tree
# remove the files listed under "excluded" in the pre-registration, then:
vettalon scan ./release-tree --profile deep --config ./config.toml --finders claude --verifiers grok,agy --max-modules 8 --refuter-runs 1 --no-prior --time-budget 2400 --report ./result.json

Pre-registered release-archive scan

Both arms scan the official release archive (no git history) with the same parameters. Model input contains no CVE id, advisory text or fix diff. A match is judged against the file, line ranges and CWE registered publicly before the first scan; other output is listed as counts only and was not reviewed. "Not flagged" on the fixed release only means nothing in the corresponding range met the criteria. The models may have seen this advisory during training, and a candidate is not an automatic confirmation.

ArmVersionResultModel callsRun statusTarget file reviewed
Vulnerable2.3.0Found10incomplete (injection_uncrosschecked)yes
Fixed2.4.0Not flagged10incomplete (injection_uncrosschecked)yes
Download case scan summary (JSON)
Summary artifact SHA-256cf7636c10081000a50f1f26a7d76c5b29d369e8289f8c8f675ac31f709bc1f08

Match criteria were registered publicly before the first scan: Pre-registration (JSON)

Pre-registration SHA-2566c7ef71cffa3729de787444c67823b01afcda71d61b42f2a005fda892a07db91

Limits and prerequisites

The models may have seen this advisory or its fix during training, so a match does not show that they reasoned it out from the code. Candidates and needs_human results are not automatic confirmation, and no PoC or exploit was run. This was a single run and model output is nondeterministic. Five hand-picked known issues cannot support a recall or false-positive rate. Other output is published as counts only and was not reviewed. The raw report.json and trace are not public; only their SHA-256 is listed. The run status in the table is what the product reported: incomplete means some unit was deferred, truncated, failed or could not be cross-checked, with the reason shown, but the target file was sent for review in every arm. On the fixed release, "not flagged" only means no unrefuted output in the corresponding range carried a listed CWE; the other output in that arm was not reviewed and says nothing about the fixed release being free of other issues. The 2.4.0 archive ships a regression test named after the advisory (tests/test_advisory.py); it was removed before scanning. 2.3.0 has no such file.

Sources and attribution

The advisory credits Aapo Oksman (Nixu) with the disclosure; see the PyJWT advisory for fix credits. Vettalon performed detection of a known issue only.

Original detection report SHA-256

For comparison by holders of the original report. The download is a curated record, not the raw scan report.

81fb9120cbb14c416b5ec50f84185a4bae09ff7478d72b51cb2e769ac34baf94