98fd3c5d6af4
add rune decoder fuzz corpus — 17 edge cases covering off-by-one, surrogates, BOM, RTL, whitespace, DoS timing
- time
- 2026-05-17 13:20 UTC · 5 months ago
- machine
- koh2
- parent
- 22eb4b086791
↓ peach-browser-98fd3c5d.tar.gzatlas at this save
files changed1 file
src/crypto/peachScript.test.ts+112 −0src/crypto/peachScript.test.ts+112 −0
@@ -60,3 +60,115 @@6060 expect(isPeachScript(mixed)).toBe(false);6161 });6262 });63+64+describe("peachScript fuzz corpus", () => {65+ // Off-by-one boundary — U+E100 is one above the valid ceiling U+E0FF66+ test("rejects U+E100 (off-by-one out of range)", () => {67+ expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow(68+ "INVALID_PEACHSCRIPT_CHARACTER"69+ );70+ });71+72+ test("rejects U+E200 (far out of range)", () => {73+ expect(() => decodePeachScript(String.fromCodePoint(0xe200))).toThrow(74+ "INVALID_PEACHSCRIPT_CHARACTER"75+ );76+ });77+78+ // Surrogate code points — below the valid PUA range U+E000..U+E0FF79+ test("rejects lone low surrogate U+DFFF", () => {80+ expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow(81+ "INVALID_PEACHSCRIPT_CHARACTER"82+ );83+ });84+85+ test("rejects lone high surrogate U+D800", () => {86+ expect(() => decodePeachScript(String.fromCodePoint(0xd800))).toThrow(87+ "INVALID_PEACHSCRIPT_CHARACTER"88+ );89+ });90+91+ // Real Mayan numerals are U+1D2E0..U+1D2F3 — outside PUA codec range92+ test("rejects U+1D2E0 (real Mayan numeral — outside codec range)", () => {93+ expect(() => decodePeachScript("\u{1D2E0}")).toThrow(94+ "INVALID_PEACHSCRIPT_CHARACTER"95+ );96+ });97+98+ // BOM and Unicode direction/zero-width marks mid-stream99+ test("rejects BOM (U+FEFF) mid-stream", () => {100+ const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));101+ // U+FEFF = BOM, inject between two valid runes102+ const withBOM = encoded[0] + String.fromCodePoint(0xfeff) + encoded[1];103+ expect(() => decodePeachScript(withBOM)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");104+ });105+106+ test("rejects RTL mark (U+200F) mid-stream", () => {107+ const encoded = encodePeachScript(new Uint8Array([0x41]));108+ // U+200F = RIGHT-TO-LEFT MARK109+ expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200f))).toThrow(110+ "INVALID_PEACHSCRIPT_CHARACTER"111+ );112+ });113+114+ test("rejects zero-width non-joiner (U+200C)", () => {115+ const encoded = encodePeachScript(new Uint8Array([0x41]));116+ // U+200C = ZERO WIDTH NON-JOINER117+ expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200c))).toThrow(118+ "INVALID_PEACHSCRIPT_CHARACTER"119+ );120+ });121+122+ // Whitespace — the raw decoder is strict; normalisation is a separate step123+ test("rejects space between valid rune characters", () => {124+ const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));125+ const withSpace = encoded[0] + " " + encoded[1];126+ expect(() => decodePeachScript(withSpace)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");127+ });128+129+ test("rejects newline between valid rune characters", () => {130+ const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));131+ const withNewline = encoded[0] + "\n" + encoded[1];132+ expect(() => decodePeachScript(withNewline)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");133+ });134+135+ // Boundary bytes must round-trip correctly136+ test("byte 0x00 round-trips correctly", () => {137+ const data = new Uint8Array([0x00]);138+ expect(decodePeachScript(encodePeachScript(data))).toEqual(data);139+ });140+141+ test("byte 0xFE round-trips correctly", () => {142+ const data = new Uint8Array([0xfe]);143+ expect(decodePeachScript(encodePeachScript(data))).toEqual(data);144+ });145+146+ test("byte 0xFF round-trips correctly", () => {147+ const data = new Uint8Array([0xff]);148+ expect(decodePeachScript(encodePeachScript(data))).toEqual(data);149+ });150+151+ // 1 KiB pseudo-random round-trip152+ test("1 KiB deterministic payload round-trips", () => {153+ const data = new Uint8Array(1024);154+ for (let i = 0; i < 1024; i++) data[i] = (i * 37 + 13) % 256;155+ expect(decodePeachScript(encodePeachScript(data))).toEqual(data);156+ });157+158+ // DoS resistance — the decoder must fail on the first invalid character,159+ // not after scanning the whole input. U+E100 > 0xE0FF fails on char 0.160+ test("rejects large all-invalid input in well under 50 ms", () => {161+ const invalid = String.fromCodePoint(0xe100).repeat(10_000);162+ const start = Date.now();163+ expect(() => decodePeachScript(invalid)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");164+ expect(Date.now() - start).toBeLessThan(50);165+ });166+167+ // Copy-paste hostility — smart quote prefix (U+201C = LEFT DOUBLE QUOTATION MARK)168+ test("rejects left double quotation mark (U+201C) prefix", () => {169+ const encoded = encodePeachScript(new Uint8Array([0x41]));170+ expect(() => decodePeachScript(String.fromCodePoint(0x201c) + encoded)).toThrow(171+ "INVALID_PEACHSCRIPT_CHARACTER"172+ );173+ });174+});