Repository navigation
feat(detection): port the per-request scan time budget (detection_max_scan_seconds) - #80
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, errordetection_max_scan_seconds must be within [0.1, 60.0], carried bywith().SusPatternscharges each pattern scan's CPU time to a per-request budget (beginScan/endScan, opened bySuspiciousActivityCheckfrom 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 intimeouts. Outside a request scan (a directdetect()call) nothing is capped, like the reference.is_threatstill followsanomaly >= detectionThreatScoreThresholdor a semantic hit. With a spent budget on a benign request, threshold 1.0 blocks withURL path: Pattern exceeded scan time budget: 'scan_time_budget_exhausted'and threshold 5.0 does not.<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 msusleepmoveshrtimeby 0.205 s andgetrusageby 0.0000 s, a 100 ms busy loop moves both by 0.100 s, andgetrusage()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'stime.thread_time), which also holds in long-running workers where the process counter is cumulative. Wheregetrusage()is missing or fails the monotonic wall clock stands in, which only over-counts, so the cap still bounds a scan (documented indocs/configuration.md).Threshold wiring:
detectionThreatScoreThresholdis read inSusPatterns::detect, the only is-threat decision, and bothGuardEngineandCheckFactorypass it in. It is fixed at engine construction, which is fine because dynamic rules cannot change it. A new test drives aGuardEngineat 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 toSusPatterns.phpandSecurityConfig.phplines were re-anchored (318/319 and 1120/1144, identical code to the old 200/201 and 1105/1129). I also updated the stalelogSensitiveHeadersnote indocs/configuration.mdto point atexcludedDetectionHeaders.