Summary
JWT Verifier assumes the decoded JWT header alg value is a string. A malformed token with a non-string alg value can cause runtime errors because classifyJwtVerificationAlgorithm() calls .toLowerCase() on the supplied value.
This is a robustness issue for a tool that processes untrusted pasted tokens.
Why this matters
Users paste arbitrary JWT-like strings, including malformed tokens from logs, bug reports, test fixtures, or attacks. A verifier should classify malformed/unsupported algorithms as actionable output, not throw a runtime error or bypass shared action feedback.
This also compounds the fire-and-forget action issue: the page currently starts verify() with void verify(), so an async runtime error can avoid the shared ToolActionBar failure path.
Current behavior
Relevant code:
src/features/tools/jwt-verifier/page.tsx
const alg = h?.alg as string || "unknown"
setVerifyResult(await verifyJwtSignature(token, secret, alg))
src/features/tools/jwt-verifier/logic.ts
classifyJwtVerificationAlgorithm(algorithm: string) calls algorithm.toLowerCase().
If the decoded header is:
or:
{ "alg": { "name": "HS256" } }
then the runtime value passed as algorithm is not a string.
Expected behavior
Malformed/non-string alg should produce a stable unsupported/malformed status, for example:
{ status: "unsupported", algorithm: "non-string alg" }
or a distinct status:
{ status: "malformed", message: "JWT header alg must be a string." }
The UI should display an actionable warning rather than throwing.
Suggested implementation plan
- Normalize the decoded
alg value before passing it to verification:
const rawAlg = h?.alg
const alg = typeof rawAlg === "string" ? rawAlg : "unknown"
- Make
classifyJwtVerificationAlgorithm() defensive against unknown input, or keep its type narrow and enforce normalization at the callsite.
- Consider adding a
malformed result status for non-string alg.
- Return the async verify Promise through
ToolActionBar as part of the related action-state issue.
- Add tests:
classifyJwtVerificationAlgorithm or verifyJwtSignature does not throw for non-string runtime values if exposed defensively;
- page-level verification of a token with
{ "alg": 123 } shows unsupported/malformed guidance;
- unsupported asymmetric algorithms still show unsupported guidance;
alg: none still shows unsigned warning.
Acceptance criteria
Related code pointers
src/features/tools/jwt-verifier/page.tsx
src/features/tools/jwt-verifier/logic.ts
tests/unit/jwt-verifier-claims.test.ts
tests/component/jwt-verifier-page.test.tsx
- related action-state issue for fire-and-forget verify handlers
Summary
JWT Verifierassumes the decoded JWT headeralgvalue is a string. A malformed token with a non-stringalgvalue can cause runtime errors becauseclassifyJwtVerificationAlgorithm()calls.toLowerCase()on the supplied value.This is a robustness issue for a tool that processes untrusted pasted tokens.
Why this matters
Users paste arbitrary JWT-like strings, including malformed tokens from logs, bug reports, test fixtures, or attacks. A verifier should classify malformed/unsupported algorithms as actionable output, not throw a runtime error or bypass shared action feedback.
This also compounds the fire-and-forget action issue: the page currently starts
verify()withvoid verify(), so an async runtime error can avoid the sharedToolActionBarfailure path.Current behavior
Relevant code:
src/features/tools/jwt-verifier/page.tsxconst alg = h?.alg as string || "unknown"setVerifyResult(await verifyJwtSignature(token, secret, alg))src/features/tools/jwt-verifier/logic.tsclassifyJwtVerificationAlgorithm(algorithm: string)callsalgorithm.toLowerCase().If the decoded header is:
{ "alg": 123 }or:
{ "alg": { "name": "HS256" } }then the runtime value passed as
algorithmis not a string.Expected behavior
Malformed/non-string
algshould produce a stable unsupported/malformed status, for example:or a distinct status:
The UI should display an actionable warning rather than throwing.
Suggested implementation plan
algvalue before passing it to verification:classifyJwtVerificationAlgorithm()defensive against unknown input, or keep its type narrow and enforce normalization at the callsite.malformedresult status for non-stringalg.ToolActionBaras part of the related action-state issue.classifyJwtVerificationAlgorithmorverifyJwtSignaturedoes not throw for non-string runtime values if exposed defensively;{ "alg": 123 }shows unsupported/malformed guidance;alg: nonestill shows unsigned warning.Acceptance criteria
algis missing, non-string, or malformed.algproduces actionable unsupported/malformed UI guidance.alg: nonebehavior still shows the unsigned warning.npm test -- --run tests/unit/jwt-verifier-claims.test.ts tests/component/jwt-verifier-page.test.tsxpasses.npm run check:typesandnpm run lintpass.Related code pointers
src/features/tools/jwt-verifier/page.tsxsrc/features/tools/jwt-verifier/logic.tstests/unit/jwt-verifier-claims.test.tstests/component/jwt-verifier-page.test.tsx