Skip to content

feat(detection): port the per-request scan time budget (detection_max_scan_seconds) - #80

Merged
rennf93 merged 1 commit into
masterfrom
feat/scan-time-budget
Oct 15, 2026
Merged

rennf93 merged 1 commit into
masterfrom
feat/scan-time-budget

Conversation

@rennf93

@rennf93 rennf93 commented Oct 15, 2026

Copy link
Copy Markdown
Member

Closes #79

I ported the per-request scan time budget from guard-core #167 (the event-loop-yield half of that change does not apply to PHP, a scan never shares a loop with other requests).

What changed:

  • SecurityConfig::detectionMaxScanSeconds: default 10.0, bounds 0.1 to 60.0, error detection_max_scan_seconds must be within [0.1, 60.0], carried by with().
  • SusPatterns charges each pattern scan's CPU time to a per-request budget (beginScan / endScan, opened by SuspiciousActivityCheck from the live config) and checks it between patterns. Once spent, the remaining patterns and values are skipped and each scanned view records {type: pattern_timeout, pattern: scan_time_budget_exhausted, match: '', position: 0, execution_time: 0.0, category: custom, weight: resolved weight, 1.0 by default}, also listed in timeouts. Outside a request scan (a direct detect() call) nothing is capped, like the reference.
  • No unconditional block: is_threat still follows anomaly >= detectionThreatScoreThreshold or a semantic hit. With a spent budget on a benign request, threshold 1.0 blocks with URL path: Pattern exceeded scan time budget: 'scan_time_budget_exhausted' and threshold 5.0 does not.
  • Reason text matches the reference: the budget message alone, or <first threat message>; Pattern exceeded scan time budget: '...' when another threat came first.

CPU clock: getrusage() user plus system time of the process. hrtime() is a wall clock, so it would charge a request for time spent descheduled. Measured on PHP 8.5 / macOS: a 200 ms usleep moves hrtime by 0.205 s and getrusage by 0.0000 s, a 100 ms busy loop moves both by 0.100 s, and getrusage() costs about 0.55 microseconds per call. PHP scans synchronously on one thread, so the delta across one pattern scan is that scan's CPU time (the reference's time.thread_time), which also holds in long-running workers where the process counter is cumulative. Where getrusage() is missing or fails the monotonic wall clock stands in, which only over-counts, so the cap still bounds a scan (documented in docs/configuration.md).

Threshold wiring: detectionThreatScoreThreshold is read in SusPatterns::detect, the only is-threat decision, and both GuardEngine and CheckFactory pass it in. It is fixed at engine construction, which is fine because dynamic rules cannot change it. A new test drives a GuardEngine at 1.0 and 5.0 to confirm it.

Tests: bin/test_scan_time_budget.php (36 assertions, fake clock like the reference's _FakeClock) is wired into the CI test job, the coverage suite list and the Makefile. Coverage waivers keyed to SusPatterns.php and SecurityConfig.php lines were re-anchored (318/319 and 1120/1144, identical code to the old 200/201 and 1105/1129). I also updated the stale logSensitiveHeaders note in docs/configuration.md to point at excludedDetectionHeaders.

@github-actions github-actions Bot added documentation Docs site, README, governance files area: detection Touches src/Detection/ or src/Support/ (detectors, pattern tables, NFKC) area: pipeline Touches src/Pipeline/ (checks and the pipeline) area: engine Touches src/Engine/, src/Config/, or src/Routing/ tests Test suite changes ci GitHub Actions workflows labels Oct 15, 2026
@rennf93
rennf93 merged commit bcbf1db into master Oct 15, 2026
27 checks passed
@rennf93
rennf93 deleted the feat/scan-time-budget branch October 15, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: detection Touches src/Detection/ or src/Support/ (detectors, pattern tables, NFKC) area: engine Touches src/Engine/, src/Config/, or src/Routing/ area: pipeline Touches src/Pipeline/ (checks and the pipeline) ci GitHub Actions workflows documentation Docs site, README, governance files tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(detection): port the per-request scan time budget (detection_max_scan_seconds)

1 participant