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
1 file src/crypto/peachScript.test.ts+112 −0
src/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+});

view file