Skip to content

fix(tools/web): tolerate non-string JWT header fields in parse_token#390

Open
VoidChecksum wants to merge 2 commits into
mainfrom
fix/jwt-nonstring-headers
Open

fix(tools/web): tolerate non-string JWT header fields in parse_token#390
VoidChecksum wants to merge 2 commits into
mainfrom
fix/jwt-nonstring-headers

Conversation

@VoidChecksum
Copy link
Copy Markdown
Collaborator

Defect

parse_token in jwt.py calls .lower(), in, and .startswith() directly on header.alg, header.kid, and header.jku after storing them verbatim via JWTHeader.from_dict. A JWT header is attacker/server-controlled JSON; non-string values are legal — e.g. {"alg":1}, {"alg":["none"]}, {"kid":1234}, {"jku":999}. These all crash the tool:

  • int.lower()AttributeError
  • "../" in 1234TypeError
  • int.startswith(...)AttributeError

The crashes surface at two agent-facing @tool entry points (jwt_parse in tools/web/tools.py and kg_analyze_jwt in tools/research/tools.py) with no surrounding try/except, so probing a server that emits a numeric alg or kid halts the tool entirely.

Fix

Coerce alg, kid, and jku to str locals (alg_s/kid_s/jku_s) before the four findings checks, treating None as empty string for kid/jku. All existing string-path findings (alg=none, jku confusion, kid traversal, jku non-HTTPS) continue to fire unchanged.

Regression test

New file packages/decepticon/tests/unit/web/test_jwt_nonstring_headers.py covers:

  • alg=1 (int) — must not raise
  • alg=["none"] (list) — must not raise
  • kid=1234 (int) — must not raise
  • jku=999 (int) — must not raise
  • String kid traversal finding still fires
  • String jku non-HTTPS finding still fires
  • String alg=none finding still fires

All 23 tests (7 new + 16 existing) pass. No interaction with in-flight PRs (PR #387 is OAuth-only and does not touch jwt.py).

Bug: JWTHeader.from_dict stores header fields verbatim from JSON, so
numeric/list alg/kid/jku values cause AttributeError/TypeError in the
findings block of parse_token (e.g. int.lower() crashes on alg=1).
Fix: coerce alg_s/kid_s/jku_s to str before all .lower()/"in"/
.startswith() checks, preserving None-as-empty semantics for kid/jku.
Regression test: test_jwt_nonstring_headers.py covers int alg, list
alg, int kid, int jku, plus verifies string-path findings still fire.
Folds the unique JWT enhancement from #414 into this PR: flag ANY jku header (not only non-HTTPS — an attacker-influenced https JWKS URL is still a key-confusion vector) and add the previously-missing x5u (X.509 URL) check. Uses the string-safe coercion from this PR's non-string-header fix so non-string jku/x5u values still can't crash parse_token. Adds regression tests. Consolidates #414's jwt change; #414's http_request part is already covered by #358.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant