fix(score): saturate WAF request-score accumulator to prevent u16 overflow (panic / silent block-bypass) - #53
Open
seonghobae wants to merge 4 commits into
Open
Conversation
… overflow
`score_request` accumulated the u16 request score with plain `+=` across
built-in signatures, operator threat indicators, DNSBL hits, and the anomaly
heuristic. Threat-feed imports impose no count cap and each Critical match adds
100, so with enough matching indicators the accumulator exceeds u16::MAX:
- debug / overflow-checked builds panic ("attempt to add with overflow"),
violating the invariant that scoring never panics on arbitrary input; and
- release builds wrap the score down to a small value, so a maximally
malicious request scores below the block threshold and is silently NOT
blocked (security bypass).
Switch the four accumulation sites to `saturating_add` (the convention already
used everywhere else in the crate) so the score clamps at u16::MAX and a
saturated request still scores as blockable. Add a deterministic core
regression test (700 matching Critical indicators) that panics on the old code
and asserts saturation + block on the fix, and refresh the fuzz-target comment
whose cap rationale referenced the now-removed overflow.
Verification: cargo fmt --check, cargo clippy --locked --workspace
--all-targets -- -D warnings (clean), and cargo test -p waf-ids-core (9 passed,
incl. the new test + 3 proptest invariants) all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
Changes점수 누적 포화 처리
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Contributor
Author
Commercial-readiness re-triggerBranch updated onto latest Please re-review: coverage evidence previously passed; only approval from a non-author is still missing for merge. |
seonghobae
enabled auto-merge (squash)
July 31, 2026 13:28
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.
The defect
score_request(crates/waf-ids-core/src/lib.rs) accumulates theu16request score with plain+=across four sources — built-in signatures, operator threat indicators, DNSBL hits, and the anomaly heuristic. Nothing caps the number of stored threat indicators (upsert_threat/validate_threat_feed_importimpose none), and eachCriticalmatch adds 100. With enough matching indicators the accumulator exceedsu16::MAX(65535), and this is the only accumulator in the crate not usingsaturating_add:cargo testdev profile) panics withattempt to add with overflow— directly violating the scorer's stated invariant that scoring never panics on arbitrary input (the whole point of a WAF).scored.score >= route.block_threshold.unwrap_or(BLOCK_SCORE)) evaluates false and a maximally-malicious request is silently not blocked. Security bypass.Both existing guardrails deliberately dodge this rather than catch it: the libFuzzer target caps input at 32 and the proptest mirror caps
threats/dnsblat0..16, each with a comment noting the cap avoids "trivially overflowing the u16 score accumulator." So the path was acknowledged-but-unfixed and covered by no test.Reproduced in-container before the fix (700 matching
Criticalindicators):The fix
score.saturating_add(...)— the convention already used everywhere else in the crate (saturating_addatlib.rs:546,:906,:929, rootsrc/lib.rs:848). The score now clamps atu16::MAX, so a saturated malicious request still scores as blockable (>= BLOCK_SCORE).let mut score: u16 = 0;is now explicitly typed (the previous inference came from+=).score_request_saturates_instead_of_overflowing_on_many_matches(700 matchingCriticalindicators) — it panics on the old code and assertsscore == u16::MAXand>= BLOCK_SCOREon the fix.No behavior change for non-overflowing inputs; scores below
u16::MAXare unaffected.Verification (all three CI gates)
cargo fmt --check→ cleancargo clippy --locked --workspace --all-targets -- -D warnings→ clean (exit 0)cargo test -p waf-ids-core→ 9 passed (incl. the new regression test + the 3 proptest invariants)cargo test --locked --workspace→ the new test and all core tests pass. One unrelated pre-existing failure,load_surfaces_state_rewrite_failures, reproduces identically on the base branch in this sandbox: it sets a directory to0o500and expects the write to fail, but the sandbox runs as root (which bypasses permission bits), so it is a run-as-root artifact, not caused by this change, and passes in the repo's non-root CI.🤖 Generated with Claude Code
https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH
Generated by Claude Code
Summary by CodeRabbit
릴리스 노트
버그 수정
테스트