Skip to content

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

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 #166

A single request's regex scan could hold the event loop, nothing capped total scan time per request, and a header could not be exempted from semantic scoring alone. Scans now yield the event loop, detection_max_scan_seconds (default 10.0, range 0.1-60.0) caps per-request regex CPU time, and the trigger names the budget when it tips a request. excluded_semantic_detection_headers (opt-in, empty by default, with a route-level counterpart) skips only the semantic analyzer while regex scanning is unchanged. I added a test that the opt-out applies to each Cookie pair.

…can time, add semantic-only header opt-out

Await the shared regex executor in the async engine and yield between pattern slices so one request's scan no longer freezes the event loop (sync mirror keeps blocking).

Add detection_max_scan_seconds (default 10.0), a per-request total regex scan budget carried in request-scoped state. When spent, remaining patterns and values are skipped and a weighted pattern_timeout threat (scan_time_budget_exhausted) is recorded like a per-pattern timeout.

Add excluded_semantic_detection_headers (and detection_exclusion(semantic_headers=...)): opt-in header names whose values skip the semantic analyzer only; regex scanning is unchanged.
… 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. The sync tree is regenerated with scripts/unasync.py.
detection_max_scan_seconds summed the wall time of each pattern, so on a busy or CPU-starved host ordinary requests exceeded the default and failed closed (600 benign query params: 26.7 s wall for 3.7 s of CPU). The budget now accumulates thread CPU time: inline scans measure time.thread_time() deltas, and executor-run scans measure it inside the worker and return it, since contextvars do not cross into the executor. A scan that hits detection_compiler_timeout is charged that timeout. The per-pattern timeout is no longer clamped to the budget left (the units no longer match), and event loop slicing stays wall based. The sync tree is regenerated with scripts/unasync.py.
@github-actions github-actions Bot added documentation Docs, README, CHANGELOG, governance files area: detection-engine Touches guard_core/detection_engine/ area: handlers Touches guard_core/handlers/ area: decorators Touches guard_core/decorators/ area: sync Touches guard_core/sync/ (sync mirror generated by unasync) area: scripts Touches scripts/ tests Test suite changes labels Oct 15, 2026
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Detection gate report

All detection gate shards passed.

Shard arbiter-grammar

Gate green: no category regressed against its committed baseline and no benign string is newly flagged.

Total recall: n/a. Total false-positive rate: n/a.
Documented known gaps: 0. Documented known false positives: 0. Both stay in the denominators.

Full per-case output is in the job log.

Shard benchmarks-and-rest

Gate green: no category regressed against its committed baseline and no benign string is newly flagged.

Total recall: 378/386 (97.9%). Total false-positive rate: 23/370 (6.2%).
Documented known gaps: 8. Documented known false positives: 23. Both stay in the denominators.

Category Recall False positives
cmd_injection 38/46 (82.6%) 10/370 (2.7%)
cms_probing 36/36 (100.0%) 0/370 (0.0%)
code_injection 3/3 (100.0%) 0/370 (0.0%)
deserialization 18/18 (100.0%) 0/370 (0.0%)
dir_traversal 10/10 (100.0%) 0/370 (0.0%)
file_inclusion 16/16 (100.0%) 3/370 (0.8%)
file_upload 32/32 (100.0%) 0/370 (0.0%)
http_split 4/4 (100.0%) 0/370 (0.0%)
ldap 25/25 (100.0%) 0/370 (0.0%)
nosql 10/10 (100.0%) 0/370 (0.0%)
path_traversal 13/13 (100.0%) 0/370 (0.0%)
proto_pollution 8/8 (100.0%) 0/370 (0.0%)
recon 23/23 (100.0%) 0/370 (0.0%)
sensitive_file 16/16 (100.0%) 0/370 (0.0%)
sqli 38/38 (100.0%) 6/370 (1.6%)
ssrf 32/32 (100.0%) 4/370 (1.1%)
template 13/13 (100.0%) 1/370 (0.3%)
xml 4/4 (100.0%) 0/370 (0.0%)
xss 39/39 (100.0%) 0/370 (0.0%)

Full per-case output is in the job log.

Shard builtin-safety

Gate green: no category regressed against its committed baseline and no benign string is newly flagged.

Total recall: n/a. Total false-positive rate: n/a.
Documented known gaps: 0. Documented known false positives: 0. Both stay in the denominators.

Full per-case output is in the job log.

Shard cpu-load-builtin-safety

Gate green: no category regressed against its committed baseline and no benign string is newly flagged.

Total recall: n/a. Total false-positive rate: n/a.
Documented known gaps: 0. Documented known false positives: 0. Both stay in the denominators.

Full per-case output is in the job log.

Shard hardening-canaries

Gate green: no category regressed against its committed baseline and no benign string is newly flagged.

Total recall: n/a. Total false-positive rate: n/a.
Documented known gaps: 0. Documented known false positives: 0. Both stay in the denominators.

Full per-case output is in the job log.

@rennf93
rennf93 merged commit 04feb69 into master Oct 15, 2026
38 checks passed
@rennf93
rennf93 deleted the feat/scan-yield-budget-semantic-optout branch October 15, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: decorators Touches guard_core/decorators/ area: detection-engine Touches guard_core/detection_engine/ area: handlers Touches guard_core/handlers/ area: scripts Touches scripts/ area: sync Touches guard_core/sync/ (sync mirror generated by unasync) documentation Docs, README, CHANGELOG, governance files tests Test suite changes

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