From 214e707102823ef09532951e29d48a3a9c0ed89b Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Thu, 30 Jul 2026 09:40:05 +0200 Subject: [PATCH] fix flaky tampered-token test: flip a significant signature character MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ --- apps/collab/src/auth.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/collab/src/auth.test.ts b/apps/collab/src/auth.test.ts index 457e5fc..8d9de9e 100644 --- a/apps/collab/src/auth.test.ts +++ b/apps/collab/src/auth.test.ts @@ -108,8 +108,12 @@ describe('collab authentication', () => { name: 'page-x', token: async () => { const [h, p, s = ''] = (await signCollabToken(claims, secret, 60)).split('.'); - // Flip the last character of the signature. - const flipped = s.slice(0, -1) + (s.endsWith('A') ? 'B' : 'A'); + // Flip the FIRST signature character — its bits are all significant. + // 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}`; }, },