From 80e7e71e7afe3f79ccc6460b36f0a6973b9e1bc3 Mon Sep 17 00:00:00 2001 From: PJ Date: Mon, 1 Jun 2026 21:24:34 +0530 Subject: [PATCH] fix(web-runtime): propagate extractor getter throws and unpoison locked global Stop swallowing getter errors in evaluateExtractors so the cross-extractor read guard aborts loudly, matching goja's PushSnapshot. Make the __sanderling__ lock configurable (still non-writable) so a shared test process can reinstall a fake. --- pkg/spec/src/web-runtime.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/spec/src/web-runtime.ts b/pkg/spec/src/web-runtime.ts index aa136d8..4926985 100644 --- a/pkg/spec/src/web-runtime.ts +++ b/pkg/spec/src/web-runtime.ts @@ -368,11 +368,16 @@ const runtime = { // the host invoking the extractor/next-action callbacks. defineLockedGlobal("__sanderling__", runtime); +// writable:false stops a page script from shadowing the runtime via plain +// assignment (the realistic in-page threat). configurable:true is required so +// unit tests sharing one process can reinstall a fake via defineProperty; a +// non-configurable lock would poison globalThis.__sanderling__ for every later +// test in the run. function defineLockedGlobal(name: string, value: unknown): void { Object.defineProperty(globalThis, name, { value, writable: false, - configurable: false, + configurable: true, enumerable: false, }); } @@ -384,12 +389,14 @@ function evaluateExtractors(): Record { const entry = extractors[i]; if (!entry) continue; entry.previousValue = entry.currentValue; + // Let getter throws propagate, matching the goja side, where a getter + // error aborts PushSnapshot rather than yielding undefined. Swallowing + // here would silence the cross-extractor read guard (and every other + // author error) on web only, breaking cross-engine parity. let value: unknown; extracting = true; try { value = entry.getter(state); - } catch { - value = undefined; } finally { extracting = false; }