From c8239a3bab05562cdae8957ed761b8f27606c372 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 29 Sep 2026 13:48:24 -0500 Subject: [PATCH] fix(runner): reject dev-only exceptions under wildcard exports Treat unexpanded wildcard export targets as potentially exposing every scanned source, so they cannot bypass the reviewed private eval harness boundary. Co-Authored-By: Paperclip --- .../scripts/check-forbidden-imports.test.mjs | 11 +++++++++++ .../scripts/lib/forbidden-imports.mjs | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/paperclip-runner/scripts/check-forbidden-imports.test.mjs b/packages/paperclip-runner/scripts/check-forbidden-imports.test.mjs index 59a620f4f93..faa3b01543c 100644 --- a/packages/paperclip-runner/scripts/check-forbidden-imports.test.mjs +++ b/packages/paperclip-runner/scripts/check-forbidden-imports.test.mjs @@ -190,3 +190,14 @@ test("direct or transitive public harness exposure revokes the dev-only exceptio } } }); + + +test("unexpanded wildcard exports cannot expose the reviewed development harness", async (t) => { + const violations = await fixtureBoundary(t, { + manifest: { ...reviewedManifest, exports: { "./eval/*": "./dist/eval/*.js" } }, + files: { [harness]: `import "${kernel}";` }, + }); + assert.equal(violations.length, 1); + assert.equal(violations[0].specifier, kernel); + assert.match(violations[0].reason, /outside the standalone boundary/); +}); diff --git a/packages/paperclip-runner/scripts/lib/forbidden-imports.mjs b/packages/paperclip-runner/scripts/lib/forbidden-imports.mjs index 2fda4a5377a..be6b0f68305 100644 --- a/packages/paperclip-runner/scripts/lib/forbidden-imports.mjs +++ b/packages/paperclip-runner/scripts/lib/forbidden-imports.mjs @@ -117,7 +117,12 @@ function publicSourceGraph(packageRoot, manifest, specifiers) { .map((suffix) => `${stem}${suffix}`)].filter((candidate) => known.has(candidate)); }; const targets = (value) => typeof value === "string" ? [value] : value && typeof value === "object" ? Object.values(value).flatMap(targets) : []; - const exportSources = (value) => targets(value).flatMap((target) => sourceCandidates(resolve(packageRoot, target))); + const exportSources = (value) => targets(value).flatMap((target) => { + // Wildcard exports can expose the development harness. Until this gate + // resolves their full mapping, conservatively treat all sources as public. + if (target.includes("*")) return [...known]; + return sourceCandidates(resolve(packageRoot, target)); + }); const queue = exportSources(manifest.exports); const reachable = new Set(); while (queue.length > 0) {