fix(enforce): round a positive remaining TTL up — never 0, which the helper reads as permanent (#615) - #620
Conversation
…es a permanent kernel element Refs #615 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
…ver 0, which the helper reads as permanent (#615) Ban, Sync and the covered-add retry sent int64(TTL.Seconds()); a ban with under a second left at reconcile time truncated to 0, and 0 means "no timeout" to the helper, so the kernel got a permanent element for an IP whose ban was over. ttlSeconds() keeps 0 for permanent, ceils anything positive and clamps at 1 s. Closes #615 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
|
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. |
There was a problem hiding this comment.
The fix correctly addresses issue #615 by ensuring sub-second TTLs never truncate to 0, which the helper interprets as permanent. The implementation properly rounds up positive TTLs to at least 1 second using math.Ceil, includes comprehensive test coverage at both unit and e2e levels, and updates the relevant invariant documentation. The defensive check at line 152 is documented as protection against future changes rather than a logic error.
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.
… becoming permanent (#615 hardening) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k
What & why
Closes #615 (Phase 2 of #605, invariant A2/A5).
NftablesEnforcersentTTLSeconds: int64(t.TTL.Seconds())fromBan, theSyncadd pass and the covered-add retry. A ban with less than one second remaining when a reconcile ran truncated to0, and0means permanent to the helper (element withouttimeout, cache deadline zero). The helper's own migration and rollback paths already rounded up; the client did not.Acceptance criteria restated:
Reconcilethe element is never permanent and is gone two seconds later (TestBanLifecycle_SubSecondTTLNeverBecomesPermanent; fails ondevbefore the fix, commit 1).Changes
internal/enforce/nftables.go:ttlSeconds()— 0 stays 0 (permanent), positive rounds up and clamps at 1 s; used byBan,Syncadd and the covered retry.internal/enforce/ttl_test.go: table test on the request the mock helper records (600 ms → 1, 90.2 s → 91, 1 h → 3600, 0 → 0) and theSyncpath.docs/internal/INVARIANTS.md: A2/A5 updated.Feed elements (
feeds.go) keep their own whole-minute TTLs; unchanged.Tests
make lint testgreen locally (-race):go test -race ./...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)
ActiveBansremaining-TTL computation unchanged (store_test, e2e A scenarios); helperadddeadline =nowFn + TTLSecondsunchanged (enforcerdtests; e2eStrikeTTLReaperLadderasserts kernel expiry == ladder TTL for whole-second TTLs); deferred retry (gatedban.go:storedBanTarget) feedsBan→ covered byTestBanLifecycle_GateRefusalIsDeferredNotLost(remaining ≈ 4m50s → 290 s); edge enforcers consume no TTL (unchanged); the reconcile's repair accounting unchanged (sync_repair_test). Rounding up by < 1 s lengthens a ban by at most 999 ms — inside the reaper's one-minute tolerance and never past the store's own view as read by the helper's next list.internal/e2e.TestBanLifecycle_SubSecondTTLNeverBecomesPermanent.Security review (per docs/internal/SECURITY-REVIEW.md)
mathfrom stdlib.secs < 1clamp is reachable only for0 < ttl < 1s, whereCeilalready yields 1 — kept as an explicit guard against a future change to the rounding).Self-assessment: no step toward lock-out, injection, privilege escalation, secret exfiltration or evasion.
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01KCu1p3UrUGSzWVwhNuJ47k