Skip to content

feat(detection): yield the event loop, cap per-request scan time, add semantic-only header opt-out - #132

Merged
rennf93 merged 5 commits into
masterfrom
feat/scan-yield-budget-semantic-optout
Oct 15, 2026
Merged

rennf93 merged 5 commits into
masterfrom
feat/scan-yield-budget-semantic-optout

Conversation

@rennf93

@rennf93 rennf93 commented Oct 15, 2026

Copy link
Copy Markdown
Member

Closes #131

TypeScript port of Guard-Core/guard-core#167. Scans yield the event loop, a per-request CPU time budget caps total scan time and is named in the trigger when it tips a request, an opt-in semantic-only header exclusion skips only the semantic analyzer, and the Cookie header is scanned one name=value pair at a time.

@github-actions github-actions Bot added documentation Docs, README, CHANGELOG, governance files area: detection-engine Touches packages/core/src/detection-engine/ area: handlers Touches packages/core/src/handlers/ area: decorators Touches packages/core/src/decorators/ area: models Touches packages/core/src/models/ area: utils Touches packages/core/src/utils.ts package: core Changes inside @guardcore/core tests Test suite changes (vitest) area: core-subsystems labels Oct 15, 2026
…t scan time

The regex scan awaited only microtasks, so one request's scan held the event loop for its whole duration. It now hands the loop back every ~2 ms of scanning. Add detectionMaxScanSeconds (default 10.0, 0.1 to 60), a per-request cap on the summed regex scan time. Once spent, remaining patterns and values are skipped and a weighted pattern_timeout threat (scan_time_budget_exhausted) is recorded like a per-pattern timeout, so padding a request cannot skip detection.
… a request

When a per-pattern timeout or the spent scan budget was what pushed a request over the threshold, the trigger named only the first threat, so the block read as an ordinary pattern match. The trigger now appends the first pattern_timeout threat behind a non-timeout first threat, and a leading timeout reads 'Pattern exceeded scan time budget', matching the reference.
Add excludedSemanticDetectionHeaders (and detectionExclusion({ semanticHeaders })): header names whose values skip the semantic analyzer only, matched case-insensitively and merged between the global and route sets. Regex category scanning of those headers is unchanged. Default empty.
As one value, the semicolons between cookies counted as command separators in the semantic score, so an ordinary logged-in header (a JWT session cookie plus theme and _ga) reached 0.71 and was blocked. Each pair now takes the normal header path and counts against the value budget. Empty pairs are skipped and only spaces around a pair are trimmed, so a CR or LF stays in the scanned value. Ports guard-core #162.
detectionMaxScanSeconds summed the wall time of each pattern, so on a busy or CPU-starved host ordinary requests exceeded the default and failed closed. The budget now records the process CPU time (user plus system) spent in each synchronous scan; a worker-pool scan, whose CPU is not observable from the caller, is charged its wall time. Event loop slicing stays wall based.
@rennf93
rennf93 force-pushed the feat/scan-yield-budget-semantic-optout branch from 42d30d3 to 0532349 Compare October 15, 2026 06:47
@rennf93
rennf93 merged commit 635927e into master Oct 15, 2026
16 checks passed
@rennf93
rennf93 deleted the feat/scan-yield-budget-semantic-optout branch October 15, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core-subsystems area: decorators Touches packages/core/src/decorators/ area: detection-engine Touches packages/core/src/detection-engine/ area: handlers Touches packages/core/src/handlers/ area: models Touches packages/core/src/models/ area: utils Touches packages/core/src/utils.ts documentation Docs, README, CHANGELOG, governance files package: core Changes inside @guardcore/core tests Test suite changes (vitest)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A single request's regex scan can hold the event loop, with no per-request budget or semantic-only header opt-out

1 participant