From e16cfb830206a0806b8d58db3da8fc87566e79fa Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Sun, 18 Oct 2026 21:53:03 -0700 Subject: [PATCH] fix(ai): reject non-ASCII base64 that Node decodes by its low byte Node reads code units above 0xFF by their low byte, so U+012B decodes as '+' and keeps the decoded length the input predicts. Require ASCII (~1-2 ms for 43M chars) before the length check. --- src/backend/util/mediaSniff.test.ts | 1 + src/backend/util/mediaSniff.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/backend/util/mediaSniff.test.ts b/src/backend/util/mediaSniff.test.ts index 14f657f0a..8e95f7dd7 100644 --- a/src/backend/util/mediaSniff.test.ts +++ b/src/backend/util/mediaSniff.test.ts @@ -35,6 +35,7 @@ describe('decodeStrictBase64', () => { ['the URL-safe alphabet', 'a-b_'], ['whitespace', 'YW Jj'], ['a character outside the alphabet', 'YW*j'], + ['a non-ASCII character whose low byte is in the alphabet', 'YWJī'], ['padding mid-string', 'YQ==YWJj'], ['three pad characters', 'Y==='], ])('rejects %s', (_label, value) => { diff --git a/src/backend/util/mediaSniff.ts b/src/backend/util/mediaSniff.ts index f87d3807c..d0587eeab 100644 --- a/src/backend/util/mediaSniff.ts +++ b/src/backend/util/mediaSniff.ts @@ -59,6 +59,9 @@ const MP4_BRANDS = new Set([ */ export function decodeStrictBase64(value: string): Buffer | null { if (value.length === 0 || value.length % 4 !== 0) return null; + // Node decodes a code unit above 0xFF by its low byte (`ī` as `+`), so + // only ASCII is decoded. + if (Buffer.byteLength(value, 'utf8') !== value.length) return null; // Node also decodes the URL-safe alphabet. if (value.includes('-') || value.includes('_')) return null; const padding = value.endsWith('==') ? 2 : value.endsWith('=') ? 1 : 0;