Where: packages/safe-bash/src/shell/runtime.ts — the child-dispatch path used by env <cmd>, timeout <cmd>, and xargs (invokeChild at :1218, shebangTarget/shebangStage at :3028+, Runtime construction at :1255). Each child dispatch allocates ~726 KB transient (measured, --expose-gc diff: 300 × env true → peak +242 MB, +4 KB/iter retained after GC). Plain builtins cost ~7 KB/iter; function calls ~30 KB/iter — the invoke path is 25–100× heavier.
PoC (Node 22, --max-old-space-size=128 Workers-isolate proxy):
for i in $(seq 1 300); do env true; done
→ fatal V8 OOM at 128 MB heap (uncatchable). Calibration: 150 iters → +43 MB, exit 0; 200 iters → +91 MB; 300 iters → OOM. Same via for i in $(seq 1 300); do timeout 5 true; done and via seq 1 5000 | xargs -n 1 true (+1.4 GB at default heap, OOMs even --max-old-space-size=4096 at ~5 000 dispatches). All budgets green: 300 commands is 3% of maxCommands, zero output, zero expansion bytes. GC cannot keep up because the allocation rate (~726 KB/iter, allocations held live across the child await) outruns the scavenger on a constrained heap.
Impact: (d) — a ~45-byte script kills a Workers isolate (destroying co-resident tenants' in-flight work) in < 1 s of wall clock. env/timeout/xargs are default-registered commands, and xargs is the canonical fan-out tool, so any tenant script that processes a few hundred items via xargs -n 1 or a shell loop around timeout is a reliable isolate killer. On Node it multiplies RSS ~300× vs. the work performed.
Fix: profile the per-dispatch allocation (prime suspects: AbortSignal.any + cancellation owner/boundary graph, InvocationScope chains, Runtime field initialization, per-child env object cloning at :3067 { ...context.env, ...options.env, PWD } — with a large env this alone is O(env) per dispatch). Then: reuse a pooled child-Runtime template, avoid re-cloning env when unchanged (share the parent record), and/or charge each dispatch a flat cost against a budget (e.g. maxCommands priced in KB) so the loop hits a catchable ShellLimitError before the heap does.
Where:
packages/safe-bash/src/shell/runtime.ts— the child-dispatch path used byenv <cmd>,timeout <cmd>, andxargs(invokeChildat :1218,shebangTarget/shebangStageat :3028+, Runtime construction at :1255). Each child dispatch allocates ~726 KB transient (measured,--expose-gcdiff: 300 ×env true→ peak +242 MB, +4 KB/iter retained after GC). Plain builtins cost ~7 KB/iter; function calls ~30 KB/iter — the invoke path is 25–100× heavier.PoC (Node 22,
--max-old-space-size=128Workers-isolate proxy):→ fatal V8 OOM at 128 MB heap (uncatchable). Calibration: 150 iters → +43 MB, exit 0; 200 iters → +91 MB; 300 iters → OOM. Same via
for i in $(seq 1 300); do timeout 5 true; doneand viaseq 1 5000 | xargs -n 1 true(+1.4 GB at default heap, OOMs even--max-old-space-size=4096at ~5 000 dispatches). All budgets green: 300 commands is 3% ofmaxCommands, zero output, zero expansion bytes. GC cannot keep up because the allocation rate (~726 KB/iter, allocations held live across the child await) outruns the scavenger on a constrained heap.Impact: (d) — a ~45-byte script kills a Workers isolate (destroying co-resident tenants' in-flight work) in < 1 s of wall clock.
env/timeout/xargsare default-registered commands, andxargsis the canonical fan-out tool, so any tenant script that processes a few hundred items viaxargs -n 1or a shell loop aroundtimeoutis a reliable isolate killer. On Node it multiplies RSS ~300× vs. the work performed.Fix: profile the per-dispatch allocation (prime suspects:
AbortSignal.any+ cancellation owner/boundary graph,InvocationScopechains, Runtime field initialization, per-child env object cloning at :3067{ ...context.env, ...options.env, PWD }— with a large env this alone is O(env) per dispatch). Then: reuse a pooled child-Runtime template, avoid re-cloning env when unchanged (share the parent record), and/or charge each dispatch a flat cost against a budget (e.g.maxCommandspriced in KB) so the loop hits a catchableShellLimitErrorbefore the heap does.