3d3cfda6a120
fix: PCH-01-012 require Number.isInteger on i/t/s/S in parseRawScan and parseChunkEnvelope — reject NaN/Infinity/fractional chunk indices at parse time (v3.8.1)
- time
- 2026-06-10 19:53 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- pch-01 fix integration
- parent
- a384d9d6d53d
↓ peach-browser-3d3cfda6.tar.gzatlas at this save
files changed4 files
src/popup/codex/codex.adversarial.test.ts+10 −0src/popup/codex/pdf-import.test.ts+8 −0src/popup/codex/pdf-import.ts+3 −3src/popup/codex/restore.ts+3 −3src/popup/codex/codex.adversarial.test.ts+10 −0
@@ -264,6 +264,16 @@264264 const res = await buf.add(env);265265 expect(res.accepted).toBe(true);266266 });267+268+ // PCH-01-012: fractional i/t/s/S must be rejected by parseRawScan (NaN/Infinity269+ // are not JSON-representable, so fractional is the reachable hostile case).270+ it("parse_raw_scan_rejects_non_integer_index", async () => {271+ const env = await makeEnv({ index: 0, total: 5 });272+ expect(parseRawScan(JSON.stringify({ ...env, i: 1.5 }))).toEqual({ error: "parse_error" });273+ expect(parseRawScan(JSON.stringify({ ...env, t: 5.5 }))).toEqual({ error: "parse_error" });274+ expect(parseRawScan(JSON.stringify({ ...env, s: 0.5 }))).toEqual({ error: "parse_error" });275+ expect(parseRawScan(JSON.stringify({ ...env, S: 1.5 }))).toEqual({ error: "parse_error" });276+ });267277 });268278 269279 // ── verifyChunkIntegrity adversarial ────────────────────────────────────────src/popup/codex/pdf-import.test.ts+8 −0
@@ -86,6 +86,14 @@8686 expect(parseChunkEnvelope(JSON.stringify(broken))).toBeNull();8787 });8888 89+ // PCH-01-012: non-integer indices/stripe fields must be rejected at parse time.90+ it("parse_chunk_envelope_rejects_non_integer_stripe_fields", () => {91+ expect(parseChunkEnvelope(JSON.stringify({ ...v3, s: 0.5 }))).toBeNull();92+ expect(parseChunkEnvelope(JSON.stringify({ ...v3, S: 1.5 }))).toBeNull();93+ expect(parseChunkEnvelope(JSON.stringify({ ...v3, i: 1.5 }))).toBeNull();94+ expect(parseChunkEnvelope(JSON.stringify({ ...v3, t: 15.5 }))).toBeNull();95+ });96+8997 it("returns null for non-JSON input", () => {9098 expect(parseChunkEnvelope("not json at all")).toBeNull();9199 });src/popup/codex/pdf-import.ts+3 −3
@@ -646,15 +646,15 @@646646 647647 const baseShapeOk =648648 typeof obj.c === "string" &&649- typeof obj.i === "number" &&650- typeof obj.t === "number" &&649+ Number.isInteger(obj.i) && // PCH-01-012: reject NaN/Infinity/fractional indices650+ Number.isInteger(obj.t) &&651651 typeof obj.p === "boolean" &&652652 typeof obj.d === "string" &&653653 typeof obj.h === "string";654654 if (!baseShapeOk) return null;655655 656656 // v3 envelopes must also carry stripe metadata.657- if (obj.v === 3 && (typeof obj.s !== "number" || typeof obj.S !== "number")) {657+ if (obj.v === 3 && (!Number.isInteger(obj.s) || !Number.isInteger(obj.S))) {658658 return null;659659 }660660 src/popup/codex/restore.ts+3 −3
@@ -284,8 +284,8 @@284284 285285 const looksLikeEnvelope =286286 typeof obj.c === "string" &&287- typeof obj.i === "number" &&288- typeof obj.t === "number" &&287+ Number.isInteger(obj.i) && // PCH-01-012: reject NaN/Infinity/fractional indices288+ Number.isInteger(obj.t) &&289289 typeof obj.p === "boolean" &&290290 typeof obj.d === "string" &&291291 typeof obj.h === "string";@@ -296,7 +296,7 @@296296 297297 // v3-only fields must be shaped correctly when present.298298 if (obj.v === 3) {299- if (typeof obj.s !== "number" || typeof obj.S !== "number") {299+ if (!Number.isInteger(obj.s) || !Number.isInteger(obj.S)) {300300 return { error: "parse_error" };301301 }302302 }