Skip to content

fix(daemon): flush the aggregator by the longest window — hourly tiers regain their history (#610) - #618

Merged
evertramos merged 3 commits into
devfrom
fix/issue-610-flush-longest-window
Sep 15, 2026
Merged

evertramos merged 3 commits into
devfrom
fix/issue-610-flush-longest-window

Conversation

@evertramos

@evertramos evertramos commented Sep 11, 2026

Copy link
Copy Markdown
Owner

What & why

Closes #610 (Phase 2 of #605, invariant C3).

runFlush evicted every in-memory event older than Windows()[last]. Windows() is in rule order, and the embedded rules end with the mail rules' 300 s window, so every 10-minute flush cut the history to 5 minutes. Every hourly *_sustained rule (http_wp_probe_sustained 10/1h, http_xmlrpc_sustained, http_scanner_sustained, ssh_bruteforce_sustained, the mail/vaultwarden/nextcloud/keycloak sustained tiers) could see at most one flush interval — an attacker pacing one attempt every 5 minutes was invisible to the tier that exists for that pacing.

Acceptance criteria restated:

  • Harness, virtual clock: 9 wp-login.php hits one every 5 min with the 10-min flush tick in between, 10th at +45 min → http_wp_probe_sustained bans (TestDetection_HourlyTierSurvivesFlush; fails on dev before the fix, commit 1 of this PR).
  • Benchmark coverage of the flush: deferred to fix(bench): the benchmark flush is a no-op (no long-window split) and the corpus has no hour-scale legit scenario #623 — the bench builds its aggregator from every rule window (no long-window split), so a flush at MaxWindow() (7 days) is inert there; the earlier commit that added it was removed after the adversarial review (F3).

Changes

  • internal/aggregate: MaxWindow() (the value New already computed).
  • internal/daemon/daemon.go: runFlush body factored into flushAggregates(ctx, now); cutoff is now - MaxWindow().
  • internal/daemon/harness.go: FlushOnce seam; internal/e2e: httpHit helper + the scenario.
  • internal/daemon/flush_test.go: unit regression (skips if the rule order no longer has the bug shape).
  • docs/internal/INVARIANTS.md: C3 gap marked fixed.

Tests

  • Unit tests added/updated
  • New parser/rule? fixture — N/A
  • Parser change? fuzz — N/A
  • make lint test green locally (-race): go test -race ./... ok; go test -tags bench ./internal/bench/ ok; gofmt -l . empty; go vet ./... ok; golangci-lint run ./... (v2 image) 0 issues; ip-hygiene gate clean.

Blast radius (per docs/internal/INVARIANTS.md)

Security review (per docs/internal/SECURITY-REVIEW.md)

Self-assessment: no step toward lock-out, injection, privilege escalation, secret exfiltration or evasion — it removes an evasion (pacing under the hourly tier).

Checklist

  • Follows AGENTS.md Hard Rules
  • No hardening systemd directive removed
  • Docs updated (INVARIANTS.md; user docs already describe the hourly tiers as working)
  • New dependency justified (none)

🤖 Generated with Claude Code

https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k

evertramos and others added 2 commits September 11, 2026 17:52
…to the aggregator flush

Adds the FlushOnce harness seam (runFlush body factored into
flushAggregates, unchanged behaviour) and an nginx hit helper. The
scenario paces one wp-login attempt every 5 minutes with the 10-minute
flush tick in between; the 10th attempt within the hour must be banned
by http_wp_probe_sustained. Fails on dev: the flush cutoff is the LAST
configured window (300 s), not the longest.

Refs #610

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
… configured one (#610)

runFlush evicted everything older than Windows()[last]; Windows() is in
rule order and the embedded set ends with the mail rules' 300 s window,
so every 10 minutes the in-memory history was cut to 5 minutes and no
hourly *_sustained rule could ever see more than one flush interval: an
attacker pacing one attempt every 5 minutes was invisible to the tier
built for that pacing. Aggregator.MaxWindow() is now the flush horizon;
the benchmark replays the flush on the daemon's cadence so this class is
covered there too.

Closes #610

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
Copilot AI lite review requested due to automatic review settings September 11, 2026 20:56
@strix-security

Copy link
Copy Markdown

Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here.

So far, Strix has reviewed 49 pull requests, surfaced 8 security issues (1 critical/high) and blocked 3 risky merges across this workspace.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix correctly addresses issue #610 by ensuring the aggregator flush uses the longest configured window rather than the last window in rule order. The implementation is sound with comprehensive test coverage including unit tests, E2E tests, and benchmark updates. The changes properly restore the intended behavior where hourly detection rules can see their full hour of history instead of being limited to 5-10 minutes.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

…ollow-ups in INVARIANTS (#610)

The bench builds its aggregator from every rule window (no long-window
split), so MaxWindow() is 7 days and a flush at that horizon evicts
nothing — it proved no coverage. Real benchmark coverage of the flush is
#623. INVARIANTS C3 now lists #621#624.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants