Where: packages/safe-bash/src/shell/runtime.ts ~:3583-3601 (per-call admit() loop over all args + scanGetopts) and src/shell/getopts.ts scanGetopts (validateString over every positional argument on every call, each char a work.step() with yieldTurn every 128). Per-call cost ∝ total argv bytes, so a while getopts loop over n options is O(n²). A single long clustered word (-abc…) is also O(len²).
PoC (~120 bytes, default limits):
set -- $(for i in $(seq 1 1000); do printf -- "-a "; done); while getopts "a" o; do :; done; echo $OPTIND
x=$(printf -- "-abcdefghij%.0s" {1..300}); set -- $x; while getopts "abcdefghij" o; do :; done
Measured: 250 opts → 1 958 ms; 500 → 4 506 ms (re-verified independently: 8 843 ms vs 1 696 ms for a plain 500-iteration while loop); 1000 → 13 435 ms (control: plain 1000-iteration loop 2 062 ms); 2000 → maxCpuMs at 30 007 ms. Clustered 3.3 KB word → 28 397 ms.
Impact: (d) — ~120-byte script consumes the whole per-exec CPU budget; interruptible, so bounded by the wall clock.
Fix: validate/charge arguments once per set/positional change (cache by identity) and have scanGetopts scan only the current token.
Found in security audit v3 (2026-09-07).
Where:
packages/safe-bash/src/shell/runtime.ts~:3583-3601(per-calladmit()loop over allargs+scanGetopts) andsrc/shell/getopts.tsscanGetopts(validateStringover every positional argument on every call, each char awork.step()withyieldTurnevery 128). Per-call cost ∝ total argv bytes, so awhile getoptsloop over n options is O(n²). A single long clustered word (-abc…) is also O(len²).PoC (~120 bytes, default limits):
Measured: 250 opts → 1 958 ms; 500 → 4 506 ms (re-verified independently: 8 843 ms vs 1 696 ms for a plain 500-iteration
whileloop); 1000 → 13 435 ms (control: plain 1000-iteration loop 2 062 ms); 2000 →maxCpuMsat 30 007 ms. Clustered 3.3 KB word → 28 397 ms.Impact: (d) — ~120-byte script consumes the whole per-exec CPU budget; interruptible, so bounded by the wall clock.
Fix: validate/charge arguments once per
set/positional change (cache by identity) and havescanGetoptsscan only the current token.Found in security audit v3 (2026-09-07).