fix(scan/jwks): don't let token whitespace or one bad key file silently produce wrong results - #291
Merged
Merged
Conversation
…skipped A token routinely arrives with a trailing newline (read from a file or pipe) or surrounding spaces (copy-paste). Because scan_token used the token verbatim, that whitespace landed in the signature segment and silently broke HMAC verification: the weak-secret check then reported "No common secret found" for a token whose secret IS weak — a false negative on the single most critical finding — while a leading space made the whole scan hard-fail on base64 decoding. Trim the token once at the start of scan_token so every downstream check and the generated attack payloads see the canonical token. A compact JWT/JWE serialization never contains surrounding whitespace, so the trim is always safe. Adds a regression test asserting a whitespace-padded token yields the same findings (weak secret detected) as the clean one.
… bad keys Two robustness bugs in the JWKS operations: 1. A trailing newline on the token corrupted its base64url signature segment, so jwks verify and jwks rotate wrongly reported valid keys as INVALID. Trim the token in verify_with_jwks and test_key_rotation. 2. test_key_rotation read each key with read_to_string(path)?, so a single unreadable or non-UTF-8 key file (a DER key, a raw HMAC secret) aborted the ENTIRE batch — every other key, including ones that would verify the token, was left untested. Read the key as raw bytes, record a per-key error and continue instead of aborting, and verify a non-UTF-8 key as raw HMAC secret bytes (a JWK 'oct' secret is arbitrary bytes) rather than discarding it. Adds regression tests: a binary key no longer aborts the batch, a binary HMAC secret verifies its own token, and a whitespace-padded token still verifies against its key.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The JWE Direct Encryption check compared alg == "dir" exactly while the JWE 'none' check next to it used eq_ignore_ascii_case. A case-varied alg such as "DIR" — exactly the parser-confusion shape this tool exists to flag — was therefore silently missed. Match 'dir' case-insensitively to mirror the 'none' check. Adds a regression test covering both casings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Autonomous bug-hunt of the vulnerability scanner + JWKS operations. Found and fixed two real, demonstrable robustness/correctness bugs where surrounding whitespace on a token (a trailing newline is extremely common when reading from a file or pipe) or a single bad key file silently produced wrong security results — the most dangerous kind of failure for a scanner.
Bug 1 — Trailing whitespace on the token silently defeats detection
A JWT/JWE compact serialization never contains whitespace, but tokens routinely arrive with a trailing newline (
$(cat token.txt), piped input) or stray spaces (copy-paste). The scanner and JWKS verify/rotate used the token verbatim, so that whitespace landed in the signature segment and broke base64url/HMAC verification.Concrete repro (token
…HwNoQUqm…={"sub":"a"}signed with the weak secretsecret):A single trailing byte silently flips the most critical finding (weak secret) from CRITICAL to "clean", and makes JWKS verification report valid keys as invalid.
Fix: trim surrounding whitespace once at the entry of
scan_token,verify_with_jwks, andtest_key_rotation. Trimming a compact token is always safe (it has no internal whitespace).Bug 2 — One unreadable/binary key file aborts the whole rotation batch
test_key_rotationread each key withstd::fs::read_to_string(path)?. A single non-UTF-8 key file in the--keys-dir(a DER key, a raw binary HMAC secret) propagated its error and aborted the entire batch, so every other key — including ones that would verify the token — was never tested.Concrete repro (a directory with one binary
.keyand one validgood.txt):Fix: read each key as raw bytes; on an unreadable file, record a per-key error and continue instead of aborting. A non-UTF-8 key is verified as raw HMAC secret bytes (a JWK
octsecret is arbitrary bytes) rather than discarded — so a binary HMAC key now verifies its own token, which was previously impossible.Tests added
cmd::scan::tests::test_scan_token_trims_surrounding_whitespace— padded token yields the same findings (weak secret detected) as the clean one.jwks::tests::test_verify_with_jwks_trims_surrounding_whitespace— whitespace-padded token still verifies against its key.jwks::tests::test_key_rotation_binary_key_does_not_abort_batch— a binary key no longer aborts; a good key after it is still tested.jwks::tests::test_key_rotation_verifies_binary_hmac_secret— a binary HMAC secret verifies its own token.Verification
cargo test— all pass (397 + 164 + integration), including the 4 new tests.cargo fmt --check— clean.cargo clippy --all-targets— no new warnings.src/cmd/scan.rsandsrc/jwks/mod.rschanged. No changes to the JSON/HTML report contract,Severityserialization, or any shared/contract file. Data lines remain structurally intact.Intentionally left out (minor, to avoid churn)
alg:"dir"is matched case-sensitively while the JWEnonecheck is case-insensitive — aalg:"DIR"(RFC-invalid) direct-encryption token isn't flagged. Minor, low severity."0000000000000000"). Honest "credit-card-shaped" heuristic; real cards never start with 0. Left as-is.