A body-consuming named middleware cannot read past the limit on default and body.limit:N routes, and raw routes are unaffected
Every invalid limiter definition (nil store, nil Key, Max<1, Decay<=0, inline overflow) is a boot-time error covered by a test
An IANA boundary table and zoned fe80 dial-time tests prove the fetchguard classifier
Each Plan 06-12 warning fix (typed-nil guard, ServeMux conflict error, factories built once, missing body config, fractional JWT sub) has a regression test
06-SECURITY-REVIEW.md lists T-06-28 through T-06-35, cites the named passing tests, and its totals/verdict match the post-fix evidence
Bring full unit and regression coverage to the gaps fixed in 06-12 and 06-13, then rewrite the security review truthfully (lean-mode rule 3: tests are the last plan).
Purpose: close verification truths 4, 5, 8, 11. Output: test files and a rewritten 06-SECURITY-REVIEW.md. Code commit (tests) and docs commit (review) are separate.
@CLAUDE.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-VERIFICATION.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-12-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-13-SUMMARY.md
Task 1: surf and bouncer regression tests
surf/bodylimit_test.go, surf/limiter_test.go, surf/router_test.go, bouncer/registry_test.go, bouncer/jwt_test.go
existing tests in each file (helper names, how routers are built), surf/router.go wrap/compile, surf/limiter.go, bouncer/jwt.go subject
- TestBodyLimitBoundsBodyConsumingMiddleware: limit 4, named middleware io.ReadAll(r.Body) on 10-byte body gets *http.MaxBytesError (record it), for default limit and for a body.limit:N override; a raw route with the same middleware reads all bytes; a panic in that middleware still yields the clean 500
- TestRegisterBucketRejectsInvalid: table for nil Key, Max 0, Max -1, Decay 0, Decay negative, nil store, each error non-nil and names plugin and bucket; TestValidateThrottleRejectsOverflowAndNilStore; TestMiddlewareFailsClosed proves misconfigured limiter yields 500 and next is never called (no 204 pass-through)
- TestBuildRouterFailsOnMissingBodyConfig: missing key, zero, negative, non-numeric each error; valid config passes
- TestCompileRouteConflictReturnsError: two overlapping semantic patterns give error, no panic
- TestFactoriesBuiltOncePerName: counting factory invoked exactly once across BuildRouter+compile for a repeated name:param
- registry: typed-nil pointer/func/map guard rejected, valid guard accepted; jwt: sub 12.5, NaN-equivalent, 2^60 float, -1, json.Number "1.5" rejected, sub 12 and "12" accepted
Write the tests above using the existing test helpers in each file; plain func TestX(t *testing.T), testify only if already imported there. Table-driven with subtests. Do not modify production code; if a test exposes a defect, stop and report it instead of loosening the test. Run with -race.
go vet ./surf/... ./bouncer/... && go test ./surf/... ./bouncer/... -count=1 -race -short
- `go test ./surf/... ./bouncer/... -run 'TestBodyLimitBoundsBodyConsumingMiddleware|TestRegisterBucketRejectsInvalid|TestMiddlewareFailsClosed|TestFactoriesBuiltOncePerName|TestCompileRouteConflictReturnsError|TestBuildRouterFailsOnMissingBodyConfig' -v` lists each PASS
- `go test ./surf/... -cover` reports statement coverage of surf/limiter.go and surf/bodylimit.go functions at 100% for the changed branches (check with -coverprofile / go tool cover -func)
- bouncer tests for typed-nil and fractional subject pass
Every surf/bouncer gap and warning has a named regression.
Task 2: fetchguard IANA boundary and zoned dial-time tests
fetchguard/ip_test.go, fetchguard/fetch_test.go
fetchguard/ip.go, fetchguard/ip_test.go, fetchguard/fetch_test.go (TestDialControlRejectsUnsafeIPv6Transitions pattern)
- TestIsReservedOrPrivateIANABoundaries: for every prefix in the 06-13 table assert first address, last address and one interior address are non-public, plus the address immediately before and after each range is public when it is not itself in another listed range (for example 198.17.255.255 and 198.20.0.0 public; 239.255.255.255 multicast blocked)
- Public controls 8.8.8.8, 1.1.1.1, 2606:4700:4700::1111 allowed; IPv4-mapped forms of blocked addresses blocked
- TestIsReservedOrPrivateIgnoresZone: fe80::1%eth0 and %1, and a zoned public address classifies as its unzoned form
- TestDialControlRejectsZonedAndSpecialUse: dialControl returns errPrivateIP for [fe80::1%eth0]:443, 198.18.0.1:443, 192.0.0.1:443, 240.0.0.1:443, and passes 8.8.8.8:443; one PublicOnlyMode Fetch case maps to ReasonPrivateIP (not network_error) for those four probe inputs
Write the table-driven tests. Do not modify production code. Fetch-level cases must not perform real network I/O (the dial control rejects before connect; use literal IP URLs with the mode/allow-list used by existing tests).
go vet ./fetchguard/... && go test ./fetchguard/... -count=1 -race -short
- Named tests TestIsReservedOrPrivateIANABoundaries, TestIsReservedOrPrivateIgnoresZone, TestDialControlRejectsZonedAndSpecialUse pass
- `go tool cover -func` shows isReservedOrPrivate and dialControl at 100%
Classifier and dial boundary fully tested against the previously bypassing inputs.
Task 3: Reopen and rewrite 06-SECURITY-REVIEW.md (docs commit, separate from code)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
06-SECURITY-REVIEW.md, 06-VERIFICATION.md, 06-12-SUMMARY.md, 06-13-SUMMARY.md, and the actual test names created in Tasks 1 and 2
Rewrite the review in the existing structure. First state honestly in a "Reopened" note that the 2026-09-21 zero-open verdict was contradicted by verification and record that audit-trail row as superseded (append, do not delete history). Add threat rows and Findings sections for T-06-28 (body-consuming middleware bypassing the cap), T-06-29 (invalid or overflowing limiter definitions failing open), T-06-30 (remaining non-public IPv4/IPv6 special-use ranges), T-06-31 (zoned IPv6 evading prefix checks), and warning-class threats T-06-32 (typed-nil guard), T-06-33 (fractional JWT subject), T-06-34 (ServeMux conflict panic), T-06-35 (missing body config becomes zero). Each row: category, plan of origin (06-12 or 06-13), disposition mitigate, and Proof citing the exact named passing tests from Tasks 1-2 plus source location. Add trust-boundary rows for body -> named middleware, plugin bucket definition -> limiter, and non-public IP representations -> dial. Update T-06-12 finding to note the earlier proof only covered the terminal handler. Recompute totals (34 threats: verify count from the register), frontmatter status, verdict, scope, accepted risks (unchanged 4), and append an audit-trail row with the date and results of the final gates run in this task: `go test ./... -count=1 -race -short` and `go vet ./...` in both summercms.go and ../fonoteka.go. If any gate fails, leave the affected threats open and status blocked rather than verified.
test $(grep -c '^| T-06-' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md) -ge 34 && grep -v '^#' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md | grep -c 'T-06-35'
- Register contains rows T-06-28 through T-06-35, each with a named test in Proof
- Frontmatter threats_total equals the actual row count and threats_open reflects gate results
- Audit trail has a new row and retains the superseded 2026-09-21 row
- Every test name cited exists (grep each name in the repo returns a match)
Review is truthful, reopened, and re-closed only on passing evidence.
<threat_model>
Trust Boundaries
Boundary
Description
test evidence -> security sign-off
review verdict must follow executed proof
STRIDE Threat Register
Threat ID
Category
Component
Disposition
Mitigation Plan
T-06-36
Repudiation
06-SECURITY-REVIEW.md
mitigate
verdict derived from gate output; cited tests verified by grep; superseded history retained
T-06-37
Tampering
test suite
mitigate
tests never loosen production checks; defects found are reported, not masked
</threat_model>
`go vet ./... && go test ./... -count=1 -race -short` in summercms.go and fonoteka.go. Tests commit and review commit are separate; no co-author tags.
<success_criteria>
Verification truths 4, 5, 8, 11 are supportable by named tests and an honest review.
</success_criteria>
Create `.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-14-SUMMARY.md` when done