fix flaky tampered-token test: flip a significant signature character
All checks were successful
CI / Build container images (pull_request) Successful in 3m27s
CI / Auth e2e pack (pull_request) Successful in 7m51s
CI / Lint, typecheck, test (pull_request) Successful in 4m49s
CI / Import/export fidelity gate (pull_request) Successful in 1m1s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 4m54s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m51s
CI / Import/export fidelity gate (push) Successful in 54s

The tampered-token case flipped the LAST base64url character of the
signature. Its low bits are padding that decoders ignore, so whenever a
signature ends in 'A' (~1/16 of tokens) the flip to 'B' decodes to the
same bytes and the token verifies — jose compares decoded bytes, unlike
the pre-#188 homegrown code that compared encoded strings. Reproduced
deterministically (20/20 A-ending signatures accepted the flip); CI run
477 and one local full-suite failure were this, not load. Flipping the
first character makes the tamper always significant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
This commit is contained in:
Claude Fable 5 2026-07-30 09:40:05 +02:00
parent d32c8c3730
commit 214e707102

View File

@ -108,8 +108,12 @@ describe('collab authentication', () => {
name: 'page-x', name: 'page-x',
token: async () => { token: async () => {
const [h, p, s = ''] = (await signCollabToken(claims, secret, 60)).split('.'); const [h, p, s = ''] = (await signCollabToken(claims, secret, 60)).split('.');
// Flip the last character of the signature. // Flip the FIRST signature character — its bits are all significant.
const flipped = s.slice(0, -1) + (s.endsWith('A') ? 'B' : 'A'); // The last character is not: its low bits are base64url padding that
// decoders ignore, so a last-char flip of a signature ending in 'A'
// decodes to the same bytes and verifies (jose compares decoded
// bytes, unlike the pre-#188 code that compared encoded strings).
const flipped = (s.startsWith('A') ? 'B' : 'A') + s.slice(1);
return `${h}.${p}.${flipped}`; return `${h}.${p}.${flipped}`;
}, },
}, },