Skip to content

perf(unbounded-consumption): short-circuit once length proves a block - #7

Merged
aymandakirgh merged 1 commit into
mainfrom
fix/unbounded-consumption-short-circuit
Jun 18, 2026
Merged

perf(unbounded-consumption): short-circuit once length proves a block#7
aymandakirgh merged 1 commit into
mainfrom
fix/unbounded-consumption-short-circuit

Conversation

@aymandakirgh

Copy link
Copy Markdown
Owner

Why

scanUnboundedConsumption runs on RAW (untruncated) input by design — the orchestrator passes raw input here (src/aegis-guard.ts:217) so the oversized-payload signal isn't hidden by maxInputLength truncation. But when input.length > maxLength (already a block, score 90), the detector still did full O(n) work over the entire body:

  • longestCharRun(input) — walks every character
  • maxTokenCount(input) — builds a Map over every whitespace-delimited token (and the split array)
  • the UNBOUNDED_REQUEST regex over the full raw input

On a multi-MB body that's hundreds of ms of synchronous, event-loop-blocking CPU for zero extra detection benefit — the verdict is already "block". Measured on main:

  • ~7MB input of 1M distinct tokens → ~190ms
  • ~10MB single-char input → ~113ms

What

After the cheap input.length > maxLength check trips, return immediately with score: 90 and the length signal, skipping the char/token/regex scans. The block verdict and score are unchanged, so detection semantics are identical; only the worst-case per-request work is now bounded. Inputs within maxLength (≤ maxLength chars) still get the full char-run / token-repeat / unbounded-request analysis.

One detail-only change: an oversized input that also had e.g. a char-run previously listed both signals in details; it now lists just length=.... The safe/score/threatType are identical and no test asserted on the combined string.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npm test — 314 passed / 1 skipped (added 5 regression tests in tests/unbounded-consumption.test.ts, incl. a <50ms bound on a 1M-distinct-token body)
  • Probe (node/tsx) before vs after:
    • 1M-distinct-token (~7.9MB): 188ms → ~0ms, still {safe:false, score:90}
    • 10MB single char: 113ms → ~0ms, still {safe:false, score:90}
    • within-limit char-run / token-repeat / unbounded-request / benign: unchanged

🤖 Generated with Claude Code

WHY: scanUnboundedConsumption runs on RAW (untruncated) input by design
(the orchestrator bypasses maxInputLength truncation here so the size
signal is not hidden). When input.length already exceeds maxLength the
verdict is a block (score 90) — yet the detector still walked every
character (longestCharRun) and built a Map over every whitespace-
delimited token (maxTokenCount) and ran the UNBOUNDED_REQUEST regex over
the full body. On a multi-MB body that is hundreds of ms of synchronous,
event-loop-blocking CPU for no extra detection benefit. Measured: a ~7MB
input of 1M distinct tokens ~190ms; a ~10MB single-char input ~113ms.

WHAT: After the cheap length check trips, return immediately with
score 90 and the length signal, skipping the O(n) char/token/regex
scans. The verdict and score are unchanged (still blocked), so detection
semantics are identical; only the worst-case per-request work is now
bounded. Inputs within maxLength still get the full char-run /
token-repeat / unbounded-request analysis. Adds regression tests pinning
the short-circuit (still blocks, score 90, fast on a multi-MB body) and
confirming within-limit detection paths still fire.

Co-authored-by: mattia-mamini-gh <281593356+mattia-mamini-gh@users.noreply.github.com>
@aymandakirgh
aymandakirgh merged commit 6978d99 into main Jun 18, 2026
3 checks passed
@aymandakirgh
aymandakirgh deleted the fix/unbounded-consumption-short-circuit branch June 18, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant