From ad8b90db50106de2a9f15a0d744aed32fa0325b6 Mon Sep 17 00:00:00 2001 From: Rinjani Analytics Date: Wed, 17 Jun 2026 15:05:33 +0700 Subject: [PATCH] test(feed-engine): parser robustness / fuzz suite + regexExtract guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The engine is the parse seam for untrusted upstream feed bytes. This locks its hostile-input contract (353 generated cases, ~0.5s, no infra): - extractRecords (text/csv) never throws on any bytes (empty, binary, unicode, 200KB, unterminated quotes, ragged rows). - applyTransforms is now TOTAL — no op throws on any input (14 ops × 22 inputs). - runEngine isolates bad records: good rows in `records`, bad in `errors`, invariant read === ok + failed; a malformed payload throws for the caller. - getPath never throws; a malicious `__proto__` CSV header can't pollute Object.prototype (verified). Fix: regexExtract guards `new RegExp(pattern)` (try/catch → undefined) so a malformed manifest pattern yields "no match" instead of throwing — makes applyTransforms total. Parity suites unaffected (381 engine tests green). Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/feed-engine/src/transforms.ts | 7 ++++++- packages/feed-engine/test/robustness.test.ts | Bin 0 -> 7386 bytes 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 packages/feed-engine/test/robustness.test.ts diff --git a/packages/feed-engine/src/transforms.ts b/packages/feed-engine/src/transforms.ts index aee5ab9..0107b6a 100644 --- a/packages/feed-engine/src/transforms.ts +++ b/packages/feed-engine/src/transforms.ts @@ -40,7 +40,12 @@ const REGISTRY: Record = { if (typeof v !== "string") return v; const { pattern, group = 0 } = (arg ?? {}) as { pattern?: string; group?: number }; if (!pattern) return v; - const m = v.match(new RegExp(pattern)); + // Guard `new RegExp` so a malformed manifest pattern yields "no match" + // instead of throwing — keeps applyTransforms a TOTAL function (it never + // throws on any input), so a bad pattern can't take down a record. + let re: RegExp; + try { re = new RegExp(pattern); } catch { return undefined; } + const m = v.match(re); return m ? m[group] : undefined; }, diff --git a/packages/feed-engine/test/robustness.test.ts b/packages/feed-engine/test/robustness.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..250e7d3ab2234d39f61aaf455bec1fddf46f6849 GIT binary patch literal 7386 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 XeUwzxv+DV}{m$Jq`-?GC(=7i7PwFnf literal 0 HcmV?d00001