Where: packages/safe-bash/src/shell/pattern.ts:16 — tokens() does work.allocation?.reserve(128 + pattern.length*64) (optional) and then Array.from(pattern) + one token object per code point. The work objects passed from src/shell/runtime.ts at the [[ conditional site (:1944), the case site (:2006) and the glob-segment site (:4986) carry no allocation, so nothing is charged. Contrast parameterPattern (:4586), which passes allocation: scratch and is correctly rejected for the same input.
PoC (~75 bytes, default limits):
a=$(printf "%01000000d" 0); a=$a$a; case $a in "$a") echo m;; esac
a=$(printf "%01000000d" 0); a=$a$a; [[ $a == "$a" ]] && echo m
Run: NODE_OPTIONS=--max-old-space-size=128 npx tsx harness.mts '<script>'
Measured (default heap, peak sampler): 1 MB pattern → +68 MB peak, 1.1 s, exit 0; 2 MB → +158 MB, 4.0 s, exit 0; 4 MB pattern in a 30-iteration loop → +505 MB. 128 MB isolate: 2 MB case and [[ == ]] → FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory (re-verified independently). Glob site: echo $a* with 1 MB → +63 MB (2 MB is rejected by maxExpansionBytes). cloudflareWorkerLimits: 1 MB → +59 MB (survives alone; two concurrent would not).
Impact: (d) — ~70–125× heap amplification from a tiny script with every budget green; uncatchable isolate crash on Workers-class hosts under default limits.
Fix: pass an allocation (a budget.values.scope()) in the three work objects, or make tokens() require one; pre-check pattern.length*64 against the arena before Array.from.
Found in security audit v3 (2026-09-07).
Where:
packages/safe-bash/src/shell/pattern.ts:16—tokens()doeswork.allocation?.reserve(128 + pattern.length*64)(optional) and thenArray.from(pattern)+ one token object per code point. Theworkobjects passed fromsrc/shell/runtime.tsat the[[conditional site (:1944), thecasesite (:2006) and the glob-segment site (:4986) carry noallocation, so nothing is charged. ContrastparameterPattern(:4586), which passesallocation: scratchand is correctly rejected for the same input.PoC (~75 bytes, default limits):
Run:
NODE_OPTIONS=--max-old-space-size=128 npx tsx harness.mts '<script>'Measured (default heap, peak sampler): 1 MB pattern → +68 MB peak, 1.1 s, exit 0; 2 MB → +158 MB, 4.0 s, exit 0; 4 MB pattern in a 30-iteration loop → +505 MB. 128 MB isolate: 2 MB
caseand[[ == ]]→FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory(re-verified independently). Glob site:echo $a*with 1 MB → +63 MB (2 MB is rejected bymaxExpansionBytes).cloudflareWorkerLimits: 1 MB → +59 MB (survives alone; two concurrent would not).Impact: (d) — ~70–125× heap amplification from a tiny script with every budget green; uncatchable isolate crash on Workers-class hosts under default limits.
Fix: pass an
allocation(abudget.values.scope()) in the threeworkobjects, or maketokens()require one; pre-checkpattern.length*64against the arena beforeArray.from.Found in security audit v3 (2026-09-07).