From aff657cc2823ae7a8e85446e668aa9d0afd88190 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Fri, 10 Jul 2026 00:16:29 +0800 Subject: [PATCH] fix: ci run without build lib for snapshot --- .../workflow/workflow-workerthread/README.md | 2 +- .../workflow-workerthread/src/host.ts | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/workflow/workflow-workerthread/README.md b/packages/workflow/workflow-workerthread/README.md index 2c556fc35d..1ee959cc0c 100644 --- a/packages/workflow/workflow-workerthread/README.md +++ b/packages/workflow/workflow-workerthread/README.md @@ -8,7 +8,7 @@ Workflow scripts are **model-written** — the same trust level as the model's e - **The host never blocks**: `start()` returns without running any script code on the host; a synchronous spin anywhere in the script occupies the worker's loop, not the harness's. - **Termination is real**: a script that outlives its post-cancel grace is `worker.terminate()`d — nothing of it survives `dispose()`, where an in-process engine could only abandon the spin on its own loop. -- **No ambient credentials**: the worker spawns with an EMPTY environment (`env: {}` plus hermetic `execArgv`, the same stance as `dsh-code-runtime-worker`), so an escapee reading `process.env` finds no harness secrets — ambient-channel hardening only; the process-wide privileges above (fs and the rest) remain, so a genuine sandbox is still the engine swap. +- **No ambient credentials**: the worker spawns with an EMPTY environment (`env: {}` plus hermetic `execArgv`, the same stance as `dsh-code-runtime-worker`; the unbuilt dev shape forwards exactly one loader variable, `TSX_TSCONFIG_PATH` — path plumbing, not a secret), so an escapee reading `process.env` finds no harness secrets — ambient-channel hardening only; the process-wide privileges above (fs and the rest) remain, so a genuine sandbox is still the engine swap. - **Serialization by construction**: everything crossing the thread is structured-clone data, and plain JSON before that — the `materializeFromRealm` walk rejects loud what JSON cannot carry, which is also what makes every postMessage hop total. What the seam guarantees regardless, because benign scripts hit these constantly: `result` never rejects, a dropped hook promise never becomes an unhandled rejection, values JSON cannot carry are rejected **loud** instead of silently mangled, and hook misuse is fatal instead of dissolving into a per-item `null`. Genuine sandboxing (containing what an escaped script may touch) remains an isolated-vm/separate-process engine swap behind the seam, still deferred. diff --git a/packages/workflow/workflow-workerthread/src/host.ts b/packages/workflow/workflow-workerthread/src/host.ts index 9f39cd80e4..6b6391a02e 100644 --- a/packages/workflow/workflow-workerthread/src/host.ts +++ b/packages/workflow/workflow-workerthread/src/host.ts @@ -64,9 +64,11 @@ import type { ChildStartRequest, WorkerInit } from './types.ts' * (`DEEPSEEK_API_KEY` et al.) must not ride along — the same stance as * `dsh-code-runtime-worker`, stronger than the scrubbed env the * defensive-patterns rule requires for spawned commands (a shell needs PATH; - * this worker needs nothing). This closes the AMBIENT channel only — an - * escapee still holds process-wide privileges like fs access (the README's - * trust premise stands). + * this worker needs nothing). Sole exception: the unbuilt shape forwards + * `TSX_TSCONFIG_PATH` when the parent carries it (loader plumbing the paths + * map depends on outside the repo cwd, not a secret). This closes the + * AMBIENT channel only — an escapee still holds process-wide privileges + * like fs access (the README's trust premise stands). * @param init - the run payload, passed as `workerData`. * @returns the entry URL and the Worker options to spawn it with. */ @@ -76,10 +78,19 @@ function resolveWorkerSpawn(init: WorkerInit): { entry: URL; options: WorkerOpti return { entry: new URL('./worker.js', import.meta.url), options: { workerData: init, env: {}, execArgv: [] } } } // Lazy tsx resolution: only the unbuilt shape needs it, so the built - // bundle never requires tsx to be installed. + // bundle never requires tsx to be installed. TSX_TSCONFIG_PATH is the one + // variable forwarded through the scrub: tsx finds a tsconfig by searching + // UP from the worker's cwd, and a parent running with its cwd outside the + // repo (the ACP snapshot harness pins the tsconfig through this exact + // variable) would otherwise lose the dsh-* paths map and resolve workspace + // imports to unbuilt lib/ bundles. Loader plumbing, not a secret. return { entry: new URL('./worker.ts', import.meta.url), - options: { workerData: init, env: {}, execArgv: ['--import', fileURLToPath(import.meta.resolve('tsx'))] }, + options: { + workerData: init, + env: process.env.TSX_TSCONFIG_PATH === undefined ? {} : { TSX_TSCONFIG_PATH: process.env.TSX_TSCONFIG_PATH }, + execArgv: ['--import', fileURLToPath(import.meta.resolve('tsx'))], + }, } }