Repository navigation
jwt: sign and verify, every algorithm a webview can do - #166
Conversation
The JWT tool decoded and stopped there — the pane even said so — so the two questions people actually open it with went unanswered: is this signature real, and can I mint one like it to test against my service. New `jwt/jwtCrypto.ts` does both on `jose` (already a dependency, already carrying the API Client's `jsonwebtoken` shim), driving the OS webview's own Web Crypto. Nothing leaves the machine. - Algorithms: HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512, EdDSA, plus unsigned `none` for testing how a server reacts to one. Availability is probed from the engine rather than tabulated, because Ed25519 landed in WebKit, Chromium and Gecko at different times and the same build ships to all three; unavailable entries grey out instead of failing after the user has pasted a key. - Keys: PKCS#8 private, SPKI public, X.509 certificate, JWK, or a whole JWK Set (the key is picked by the token's own `kid`). PKCS#1 and SEC1 keys, which Web Crypto cannot import, are named as such with the `openssl pkcs8 -topk8` line that fixes them. - HMAC secrets carry an explicit encoding (text / base64 / base64url / hex). A secret from `openssl rand -base64 32` is bytes; HMAC-ing its printed characters produces a token the real service rejects, with nothing on screen to say why. - Verify never reads the algorithm off the token's header — that is algorithm confusion, and a debugger that reproduced it would teach it. A mismatch is reported as a mismatch. "Expired" and "wrong audience" are shown as amber, not as a red forgery: the signature held. - Decode gained the claims table — exp / nbf / iat as real times with "in 3 minutes" / "12 days ago", which is what "is this expired?" actually needs. - Generate pair fills both halves, so the Verify side is ready the moment a token is signed. Tokens and keys go to the secret vault (`useSecretState`), never `sdk.storage`. `jwt_sign` and `jwt_verify` join `jwt_decode` over MCP, running the same module, with a failed verification returned as a result rather than thrown — the caller asked a question and "no, it expired" is the answer. 56 new tests cover a round trip per algorithm, tampering, algorithm confusion, clock tolerance, JWK Set selection, secret encodings, and the `none` cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvjATyCpgyTf5gzkhZ3nP9
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DvjATyCpgyTf5gzkhZ3nP9
Important Pullfrog covered this run's model usage.
|
|
|
The job never reached the diff — it failed while opening its own model session: That is a GitHub-side service/configuration problem in the code-scanning agentic reviewer, not a finding against the change. Everything the repo's own CI runs is green on this commit — There is no fix to port from anywhere: nothing in the repository selects that model. I have re-run the failed job once; if it fails the same way again, the failure stands as an outage of that checker rather than a signal about this diff. Generated by Claude Code |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #166 +/- ##
==========================================
+ Coverage 42.67% 42.75% +0.07%
==========================================
Files 300 302 +2
Lines 19917 20222 +305
Branches 4933 5049 +116
==========================================
+ Hits 8500 8645 +145
- Misses 10400 10535 +135
- Partials 1017 1042 +25
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|




Why
The JWT tool decoded a token and stopped there — the pane said so itself. That left unanswered the two questions people actually open a JWT tool with: is this signature real, and can I mint one like it to test my service.
What changed
New
src/components/tools/jwt/jwtCrypto.tsdoes both onjose(already a dependency — it backs the API Client'sjsonwebtokenshim), driving the OS webview's own Web Crypto. Nothing leaves the machine.Algorithms
HS256/384/512·RS256/384/512·PS256/384/512·ES256/384/512·EdDSA· unsignednoneAvailability is probed from the engine, not tabulated: Ed25519 landed in WebKit, Chromium and Gecko at different times and the same build ships to all three. Unavailable entries grey out in the picker instead of failing with "Unrecognized name" after the user has pasted a key.
Keys
BEGIN PRIVATE KEYBEGIN PUBLIC KEYBEGIN CERTIFICATEdis refused for signingkidPKCS#1 and SEC1 keys, which Web Crypto cannot import, are named as such along with the
openssl pkcs8 -topk8 -nocryptline that converts them, instead of failing obscurely.HMAC secrets carry an explicit encoding (plain text / base64 / base64url / hex). A secret from
openssl rand -base64 32is bytes; HMAC-ing its 44 printed characters produces a token the real service rejects, with nothing on screen to say why.Verification does not trust the token
Reading the algorithm off the
algheader is the algorithm-confusion attack (an HS256 token handed to an RS256 verifier, signed with the RSA public key as the HMAC secret). You pick the algorithm you expect; a token that disagrees is reported as a mismatch.nonedecodes readably but never counts as verified.Failure modes are distinguished rather than flattened into one red "invalid": expired, not yet valid and failed claim check are amber — the signature held, the problem is elsewhere.
Decode
Gained a registered-claims table:
exp/nbf/iatas real times with "in 3 minutes" / "12 days ago".exp: 1758604262never answered "is this expired?".Elsewhere
expiresIntakes1h,7dor plain seconds; a bare number means from now, not a 1970 timestamp (the trapjsonwebtokenShim.tsdocuments).useSecretState, neversdk.storage.jwt_signandjwt_verifyrunning the same module. A failed verification comes back as a result ({valid, reason, message, header, payload}) rather than a thrown error — the caller asked a question and "no, it expired" is the answer.Testing
kid, every secret encoding, and thenonecases. They run under the Node environment for the realm reasonjsonwebtokenShim.test.tsdocuments.npm test→ 1452 passed ·tsc --noEmitclean ·npm run buildclean.h-ctlcontrol heights, focus-ring formula) green.🤖 Generated with Claude Code
https://claude.ai/code/session_01DvjATyCpgyTf5gzkhZ3nP9
Generated by Claude Code