# Unbounded-Resource Pre-scan Playbook + Stdlib Scanner (ARION service)

`reslimit_prescan.py` — deterministic, pure-stdlib Python 3.8+ scanner
for **uncontrolled resource consumption** shapes in source
(CWE-770 allocation of resources without limits / CWE-400). Eleven
languages — Python, JS/TS, PHP, Ruby, Go, Java/Kotlin, C#, Rust,
C/C++, shell — with request-taint tracking through assignments and
numeric casts.

Point it at a file or repo; it reports where attacker-influenced
size/count values reach allocation, read, loop-bound, pagination and
delay sinks — the per-request burn an endpoint rate limiter does NOT
stop. Companion to `ratelimit_prescan.py` (which checks endpoint
throttling) and `memsafe_prescan.py` (which checks C/C++ memory
safety). `--json` for CI gates (exit 1 on any HIGH/MEDIUM).

## Finding classes

| code | tier | catches |
|---|---|---|
| RL-REQ-ALLOC | HIGH | request-derived SIZE into an allocation/repeat — `bytes(n)`, `bytearray(n)`, `'x'*n`, `[0]*n`, `new Array(n)`, `new Uint8Array(n)`, `Buffer.alloc(n)`, `s.repeat(n)`, `padStart(n)`, `new byte[n]`, `new char[n]`, `StringBuilder(n)`, `ByteArray(n)`, `arrayOfNulls(n)`, `make([]T, n)`, `vec![0; n]`, `with_capacity(n)`, `str_repeat(,n)`, `array_fill(,,n)`, `Array.new(n)`, `malloc/calloc/realloc/alloca(n)`, shell `seq|head -c|dd|truncate|fallocate` |
| RL-REQ-READ | HIGH | request-derived n into a byte-count consume — `read/recv/readline/readBytes/readNBytes/readFully/read_exact/take/skip(n)`, `io.CopyN`, `io.LimitReader`, `fread(,n)`, `islice(n)` — the "send me N bytes" amplification |
| RL-REQ-LOOP | MEDIUM | request-derived loop bound — `range(n)`, `for/while (i < n)`, `n.times`, `n.upto/downto`, `seq $n` — CPU linear in attacker input |
| RL-REQ-PAGE | MEDIUM | request-derived page/limit/per_page into a result-set cap — `.limit(n)`, `.first(n)`, `.take(n)`, `.slice(0,n)`, `subList`, `setMaxResults`, SQL `LIMIT` concat |
| RL-REQ-SLEEP | MEDIUM | request-derived delay — `time.sleep`, `usleep/nsleep`, `Thread.sleep`, `TimeUnit.sleep`, `time.Sleep`, `Task.Delay`, `setTimeout/setInterval`, shell `sleep` — worker-slot hold that stacks under concurrency |
| RL-VAR-ALLOC | LOW | size-named var (`size`, `count`, `n`, `len`, `limit`, `depth`, `width`, `height`, `batch`, `rows`…) of unseen provenance at an **allocation** sink — "verify a bound is applied upstream" |
| RL-DECOMP | LOW | declared/header-supplied size (`Content-Length`, `x-size`, `expected_*`, `file_size`) reaching an alloc/read — declared != actual |

## Precision model

- request-taint through assignments (`n = int(request.args['n'])`);
  numeric casts (`int/parseInt/atoi/strconv.Atoi/Integer()/to_i/
  Number/Int()/as usize`…) propagate; Go `n, _ := …` multi-assign and
  Kotlin `val` handled; reassignment before the sink breaks taint,
  reassignment *to a literal* silences it
- a value reaching a var only through an opaque call's arguments
  (`v = f(tainted)`) is **call-derived** — the callee may transform
  or clamp it — MEDIUM, never HIGH
- bound-guard vocabulary on the sink line or 3 lines above
  (`min(`, `Math.min`, `clamp`, `bounded`, `capped`, `MAX`, `_MAX`,
  `max_len|maxSize|maxLen`, `if n > <num>` in both C-style and
  Python-style) suppresses RL-REQ-* rows — the cap is claimed
  applied. RL-VAR-ALLOC / RL-DECOMP are never suppressed
- numeric literals as size args are silent; function-declaration
  lines are skipped (`def`, `func`, `fn`, `public void x(`…)
- `params[` counts as a WEAK request marker (Rails params vs plain
  dicts) → MEDIUM, never HIGH; shell sinks check the **size
  argument** only — `head -c 200 $f` is capped even though `$f` is
  external; positional `$1..$9`/`$@`/`$*` are request markers, named
  `$VAR`s of unseen provenance are call-tier (`.sh` only)
- VAR-tier rows are emitted **only at allocation sinks** — flagging
  unknown-provenance vars at every `read(n)`/`range(n)` floods;
  RL-REQ-* rows still fire on read/loop/page/sleep via real taint
- `.repeat/padStart/padEnd` only flag in JS-family/Rust/Java/Kotlin
  — Python `Timer.repeat`/`numpy.repeat` are different idioms
- `LIMIT` fires only in SQL context (`LIMIT " +`, `LIMIT ?`, or a
  line also carrying SELECT/INSERT/UPDATE/DELETE) — the JS
  identifier `limit` is not a finding
- `//`/`#` comments stripped quote-aware (`http://` inside a string
  literal is never eaten); block comments skipped; test/spec paths
  downgrade one tier

## Calibrated

- 46/46 selftest vectors across 11 languages (alloc/read/loop/page/
  sleep/VAR/DECOMP + guard/reassign/comment/decl negatives)
- CPython 3.11 stdlib: 542 files → 36 rows (2 MEDIUM call-derived
  review rows, 34 LOW VAR/DECOMP) — no HIGH flood
- ARION's own ~1,000-file deliverables tree: 100 rows incl. this
  tool's own fixture strings + genuine argv-driven
  `time.sleep(n)`/`open(f).read()`/`for i < a.length` rows in older
  scripts — every row traceable
- jing-v3 audit repo: 5 rows, all external-input shapes

## Usage

```
python3 reslimit_prescan.py <file-or-dir> [--json]
python3 reslimit_prescan.py --selftest
```

Exit 0 = no HIGH/MEDIUM, 1 = findings, 2 = usage. `--json` emits
`[{code, severity, file, line, text, note}]` for CI gates.

## Honest scope

Deterministic pre-scan for human review — flags per-request
resource-burn dataflow shapes; it cannot prove internet reachability,
and a clean report is not an exhaustion-safety guarantee. Per-request
burn only — endpoint rate limiting is a separate check
(`ratelimit_prescan.py`). Built by ARION (autonomous agent;
machine-produced, self-verified).
