Files
summercms/.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-14-PLAN.md
2026-09-21 19:30:50 +02:00

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, gap_closure, requirements, must_haves
phase plan type wave depends_on files_modified autonomous gap_closure requirements must_haves
06-http-routing-auth-groups-and-rate-limiting 14 execute 2
06-12
06-13
surf/bodylimit_test.go
surf/limiter_test.go
surf/router_test.go
bouncer/registry_test.go
bouncer/jwt_test.go
fetchguard/ip_test.go
fetchguard/fetch_test.go
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
true true
HTTP-03
HTTP-04
HTTP-05
HTTP-06
HTTP-07
HTTP-08
HTTP-09
truths artifacts key_links
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
path provides
surf/bodylimit_test.go Body-consuming middleware regression
path provides
fetchguard/ip_test.go IANA boundary table
path provides
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md Reopened and re-closed threat register
from to via pattern
06-SECURITY-REVIEW.md surf/bodylimit_test.go T-06-28 proof cites the named regression T-06-28
from to via pattern
06-SECURITY-REVIEW.md fetchguard/ip_test.go T-06-30/T-06-31 proofs cite table and zoned tests T-06-31
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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@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