Skip to content

safe-bash brace expansion: ledger reservation per character ≈ 2 KB heap per source byte — 59 KB {a}{a}… script = fatal OOM on a 128 MB isolate under default limits #665

Description

@kamilio

Where: packages/safe-bash/src/shell/brace-expansion.ts:39-42admit() calls allocation.reserve(64, 0) per character/node; each reservation creates a record + closure in ValueScope.reserve (src/shell/value-state.ts ~L120) that is only released when the outer allocation.close() runs at generator end. Each 64-byte charge costs ~600 B of real heap, so the 16 MiB maxExpansionBytes / 262 144 maxParseUnits admit ~150 MB of bookkeeping. Parsing alone is fine (parseShellUnit of the same script: 28 ms, ~0 MB) — the blowup is in expansion admission.

PoC (59 KB source, ≤ maxSourceBytes, default limits):

echo {a}{a}{a}…×20000          # expands to literal text, no cardinality
echo {{{…×30000 a }}}…×30000   # nested

Run: NODE_OPTIONS=--max-old-space-size=128 npx tsx harness.mts "echo $(printf '{a}%.0s' $(seq 1 20000)) | wc -c"

Measured: {a}×10000 → +57 MB; ×20000 → +120 MB, exit 0, 608 ms; ×50000 → +140 MB then maxParseUnits; nested ×20000 → +67 MB; {,×20000 → +104 MB. 128 MB isolate: {a}×20000 FATAL ERROR: … heap out of memory, nested ×30000 FATAL (re-verified independently); ×10000 survives (49 MB). cloudflareWorkerLimits: caps at ~36–43 MB then maxParseUnits (survives).

Impact: (d) — ~2 KB of heap per source byte; 60 KB of source crashes the isolate under default limits before any expansion output exists. Introduced with brace expansion (e4b598542, #637).

Fix: charge one aggregated reservation per part (count units in a plain integer and reserve once), instead of a ledger record per character; or cap brace tokens per word (e.g. 4 096) with ShellLimitError.

Found in security audit v3 (2026-09-07).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions