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

170 lines
18 KiB
Markdown

---
phase: 06-http-routing-auth-groups-and-rate-limiting
plan: 11
type: execute
wave: 7
depends_on: ["06-07", "06-08", "06-09", "06-10"]
files_modified:
- .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
autonomous: true
gap_closure: true
requirements: [HTTP-03, HTTP-04, HTTP-05, HTTP-06, HTTP-07, HTTP-08, HTTP-09]
must_haves:
truths:
- "The security review no longer claims zero open threats until all four corrective plans and both repositories' complete `go test ./... -count=1 -race -short` gates pass"
- "T-06-24 records the anonymous key as exactly `inline:domainless|<ClientIP>` and explicitly excludes the throttle parameter, `r.Host`, `Forwarded` host, and `X-Forwarded-Host` from bucket selection"
- "T-06-23 through T-06-27 map atomic admission, domainless inline keys, transition-address SSRF rejection, clean partial-write panic recovery, and exact InvScope bytes to named passing tests"
- "T-06-24 cites the named Plan 06-07 Host-rotation, cross-inline-parameter shared-budget, and authenticated `u:<id>` isolation regressions"
- "The review's verdict, scope, totals, accepted-risk count, and audit trail agree with the post-fix code and evidence"
- "Any failed corrective test leaves the review open/blocked rather than documenting a false verified state"
artifacts:
- path: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
provides: "Post-gap Phase 6 ASVS L1 threat map and evidence-backed verdict"
key_links:
- from: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
to: summercms.go/surf/limiter_test.go
via: "T-06-23/T-06-24 cite the coordinated concurrency plus the named `TestFixedWindowLimiterInlineThrottleKeys` Host-rotation, cross-inline-parameter shared-budget, and authenticated-principal isolation cases"
pattern: "TestFixedWindowLimiterInlineThrottleKeys"
- from: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
to: summercms.go/fetchguard/ip_test.go
via: "T-06-25 cites NAT64/6to4 embedded-private and public-control cases"
pattern: "T-06-25"
- from: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
to: summercms.go/surf/router_test.go
via: "T-06-26 cites raw and house partial-write panic regressions"
pattern: "T-06-26"
- from: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
to: fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_test.go
via: "T-06-27 cites exact no-newline 401/403 byte assertions"
pattern: "T-06-27"
---
<objective>
Refresh the Phase 6 ASVS L1 security review only after all corrective code and tests are green, replacing the stale zero-open conclusion with a complete post-gap threat map and auditable evidence.
Purpose: the review is itself a required phase artifact. Its verdict must describe the current code, not the pre-review snapshot that missed four security/contract failures.
Output: an updated `06-SECURITY-REVIEW.md` covering Plans 06-01 through 06-10 plus the Plan 06-11 review refresh, with all five new threats accurately closed or the phase explicitly blocked.
</objective>
<execution_context>
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
@/home/jin/.codex/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-CONTEXT.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-REVIEW.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-07-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-09-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-10-SUMMARY.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Re-run Phase 6 security gates and publish the post-gap threat verdict</name>
<files>.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md</files>
<read_first>
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md (current stale header, threat register, findings, accepts, and audit trail; preserve all still-valid evidence)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-VERIFICATION.md (authoritative four code gaps plus stale-review gap)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-REVIEW.md (CR-01 through CR-04 and WR-11 source evidence)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-07-PLAN.md and 06-07-SUMMARY.md (T-06-23/T-06-24 and actual test names/results)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-PLAN.md and 06-08-SUMMARY.md (T-06-25 and actual transition tests/results)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-09-PLAN.md and 06-09-SUMMARY.md (T-06-26 and actual recovery tests/results)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-10-PLAN.md and 06-10-SUMMARY.md (T-06-27 and actual byte-contract tests/results)
surf/limiter.go, surf/limiter_store.go, surf/limiter_test.go (executed atomic admission and stable-key code/evidence)
fetchguard/ip.go, fetchguard/ip_test.go, fetchguard/fetch_test.go (executed transition classifier and dial-time evidence)
surf/router.go, surf/router_test.go (executed buffer/discard recovery and partial-write evidence)
../fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go and token_scope_test.go (executed no-newline writer and exact-byte evidence)
</read_first>
<action>
Before editing the review, run the repository-level verification commands below. The authoritative suite gates are exactly `go test ./... -count=1 -race -short` from summercms.go and the same command from sibling fonoteka.go; package-targeted race runs or full suites without `-race` are not substitutes. If any command fails, do not set `status: verified`, do not set `threats_open: 0`, and do not add a zero-open audit row; stop and report the failing threat/test as blocking. High-severity T-06-23 through T-06-26 may not be accepted or deferred.
After all gates pass, update `06-SECURITY-REVIEW.md` frontmatter date/status/threat count and scope so it explicitly covers Plans 06-01 through 06-10 and the Plan 06-11 review refresh. Preserve every existing T-06-01..18, T-06-21, T-06-22, and T-06-SC row and its disposition unless the new code invalidates the old evidence; do not erase accepted-risk rationales or prior audit-trail rows.
Add five mitigate rows and matching detailed `Findings by Threat` sections using the actual executed test names from the four summaries: T-06-23 for atomic threshold check+increment and coordinated Max=1 evidence; T-06-24 for the server-controlled `inline:domainless|<ClientIP>` key; T-06-25 for RFC 6052 well-known/local-use NAT64 plus 6to4 embedded loopback/RFC1918/metadata rejection, public controls, and dialControl proof; T-06-26 for buffer/discard recovery with both raw and house partial-write-then-panic exact response assertions; T-06-27 for `wire.WriteJSON` and raw-byte 401/403 assertions with no trimming. For T-06-24, state explicitly that the anonymous key excludes the throttle `param`, `r.Host`, the `Forwarded` host parameter, and `X-Forwarded-Host`. Cite the exact executed names recorded by Plan 06-07's summary/source for all three inline-key regressions under `TestFixedWindowLimiterInlineThrottleKeys`: the Host-rotation subtest, the same-IP shared-budget subtest spanning different inline throttle parameters, and the authenticated-principal subtest proving independent `u:<id>` buckets. Copy the complete slash-qualified names from the executed test source/summary rather than describing unnamed cases. Do not reduce this to a Host-rotation citation or claim that a throttle parameter selects any portion of the anonymous key. Cite concrete source identifiers and named tests, not only plan numbers.
Update the trust-boundary table for concurrent limiter admission, IPv6 transition decoding, buffered response commit, and token-scope serialization. Update summary prose, accepted-risk count, closed/open totals, and Security Audit Trail consistently. With the existing 21 reviewed IDs plus five new mitigations, the successful review total is 26 threats, 26 closed, zero open; T-06-SC remains one of the 26 and no new accepted risk is introduced. Do not change `06-VERIFICATION.md`; re-verification remains the verifier's next step.
</action>
<verify>
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go &amp;&amp; go test ./... -count=1 -race -short &amp;&amp; go vet ./... &amp;&amp; cd ../fonoteka.go &amp;&amp; go test ./... -count=1 -race -short &amp;&amp; go vet ./... &amp;&amp; cd ../summercms.go &amp;&amp; test "$(awk -F'|' '/^\| T-06-(23|24|25|26|27) / {c++} END {print c+0}' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md)" -eq 5 &amp;&amp; rg -n '26 \| 26 \| 0|threats_open: 0|Plans 06-01 through 06-10' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md &amp;&amp; rg -n 'inline:domainless\|&lt;ClientIP&gt;' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md &amp;&amp; rg -n 'TestFixedWindowLimiterInlineThrottleKeys/.+(Host|host)' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md &amp;&amp; rg -n 'TestFixedWindowLimiterInlineThrottleKeys/.+(param|policy)' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md &amp;&amp; rg -n 'TestFixedWindowLimiterInlineThrottleKeys/.+(principal|authenticated|user)' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md &amp;&amp; rg -n 'u:&lt;id&gt;' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md &amp;&amp; ! rg -n 'inline:&lt;param&gt;\|&lt;ClientIP&gt;|param\+ClientIP|anonymous (bucket|key).*(uses|includes|contains|combines).*(throttle )?param' .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md</automated>
</verify>
<acceptance_criteria>
- The review update occurs after all four plan summaries exist and `go test ./... -count=1 -race -short` plus `go vet ./...` exit 0 in both summercms.go and fonoteka.go.
- The Threat Register contains exactly one row each for T-06-23, T-06-24, T-06-25, T-06-26, and T-06-27, all disposition `mitigate`, each naming executed source/test evidence.
- Findings by Threat contains substantive sections for all five new IDs: coordinated concurrency; exact `inline:domainless|<ClientIP>` construction; named Host-rotation, cross-inline-parameter shared-budget, and authenticated `u:<id>` isolation regressions; all three transition prefixes; both partial-write route kinds; and raw exact 401/403 bytes.
- T-06-24 says the anonymous signature excludes throttle `param`, `r.Host`, `Forwarded` host, and `X-Forwarded-Host`; the source/verification grep rejects stale `inline:<param>|<ClientIP>`, `param+ClientIP`, or equivalent parameter-selected anonymous-key claims.
- Frontmatter, summary, accepted-risk log, threat totals, and the newest audit-trail row agree on 26 total, 26 closed, zero open only when every gate passed.
- Every earlier threat row and audit-trail history remains present; no high-severity gap is silently accepted, deferred, or omitted.
- `06-VERIFICATION.md` is not edited by this plan.
</acceptance_criteria>
<done>The Phase 6 security review truthfully reflects the corrected code and supplies auditable, named test evidence for every previously unresolved security/contract gap.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| implementation evidence -> security verdict | A stale or optimistic review can falsely release security-load-bearing primitives to dependent phases |
| test gates -> documentation state | Zero-open status is allowed only when the exact adversarial tests and both repository suites pass |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-06-23 | Denial of Service | concurrent limiter admission | mitigate | Require atomic Attempt implementation and coordinated Max=1 passing evidence before review closure |
| T-06-24 | Denial of Service | anonymous inline key selection | mitigate | Require exact `inline:domainless|<ClientIP>` key, exclude throttle param and all request/forwarded Host inputs, and cite named Host-rotation, cross-inline-parameter shared-budget, and authenticated `u:<id>` isolation regressions |
| T-06-25 | Elevation of Privilege / Information Disclosure | transition-address SSRF classification | mitigate | Require NAT64 /96, local-use NAT64 /48, and 6to4 embedded-private plus dialControl tests |
| T-06-26 | Information Disclosure | raw/house panic recovery | mitigate | Require partial-write status/header/body discard tests for both route kinds |
| T-06-27 | Tampering | personal-token denial serialization | mitigate | Require `wire.WriteJSON` and exact untrimmed 401/403 byte comparisons |
| T-06-SC | Tampering | package supply chain | accept | Gap plans add no dependencies; retain the existing Phase 6 package-legitimacy disposition |
</threat_model>
<source_coverage_audit>
| Source | ID | Feature / Requirement | Plan | Status | Notes |
|--------|----|-----------------------|------|--------|-------|
| GOAL | Phase 6 | Secure shared auth groups, 1:1 rate limiting, raw OAuth boundary, and SSRF guard | 06-07..06-11 | COVERED | Four defects fixed in parallel; review refresh follows all code/test plans |
| REQ | HTTP-03 | Mutually exclusive groups share handlers | 06-11 | COVERED (existing + regression gate) | Implemented by 06-01/06-05; full suite confirms no regression |
| REQ | HTTP-04 | Named/inline rate limits and stacking | 06-07, 06-11 | COVERED | Atomic admission and exact `inline:domainless|<ClientIP>` anonymous keys close the current blockers without trusting param or Host input |
| REQ | HTTP-05 | Unified guard registry/current-user accessor | 06-10, 06-11 | COVERED | Existing registry retained; exact personal-token denial contract is verified |
| REQ | HTTP-06 | Response conventions and raw exemption | 06-09, 06-10, 06-11 | COVERED | Clean partial-write recovery and no-newline TokenScope bytes |
| REQ | HTTP-07 | Guarded outbound fetch | 06-08, 06-11 | COVERED | Transition addresses receive embedded IPv4 classification at dial time |
| REQ | HTTP-08 | OpenAPI/type generation | 06-11 | COVERED (existing + regression gate) | Implemented by 06-03; no authoritative current gap requests replanning |
| REQ | HTTP-09 | CORS/body limits | 06-11 | COVERED (existing + regression gate) | Implemented by 06-03; no authoritative current gap requests replanning |
| RESEARCH | Fixed-window semantics | First-hit fixed window and PHP headers/body | 06-07 | COVERED | Atomic API preserves the established sequential contract |
| RESEARCH | SSRF dial-time guard | Actual connected address is classified | 06-08 | COVERED | Transition extraction feeds the existing dial-time classifier |
| CONTEXT | D-01..D-05 | Five buckets, fixed-window parity, stdlib store, trusted ClientIP, parameterized names | 06-07 | COVERED | No bucket limit/key ownership or middleware syntax is reduced |
| CONTEXT | D-06..D-10 | Guard registry, real token guard, exact InvScope bodies, no OAuth stub, unchanged JWT | 06-10 | COVERED | D-08 exact bytes repaired; remaining decisions preserved |
| CONTEXT | D-11..D-14 | AllowHosts/PublicOnly, dial-time rejection, typed failures, caps/timeouts | 06-08 | COVERED | Transition forms added without changing caller scope or limits |
| CONTEXT | D-15..D-18 | Group subsets, raw bare recovery, response conventions/OpenAPI, CORS/body limits | 06-09, 06-11 | COVERED | D-16 is upheld after partial writes; unrelated existing outputs regression-tested |
| CONTEXT | Deferred Ideas | Later route handlers/call sites | — | EXCLUDED | Explicitly out of Phase 6 and absent from authoritative `gaps:` |
| REVIEW | Warnings outside verifier gaps | WR-01..WR-10 except promoted WR-11 | — | EXCLUDED | Gap-closure mode plans only authoritative `06-VERIFICATION.md` gaps; these remain review backlog, not silently claimed fixed |
</source_coverage_audit>
<verification>
Run `go test ./... -count=1 -race -short` and `go vet ./...` in both repositories before documentation can claim verified/zero-open. Validate five new unique threat rows, matching detailed findings, consistent 26/26/0 totals, preserved prior evidence, and a scope statement covering the corrective plans. Assert positively that T-06-24 names `inline:domainless|<ClientIP>` and the three Plan 06-07 inline-key regressions, and fail if the review contains `inline:<param>|<ClientIP>`, `param+ClientIP`, or an equivalent parameter-selected anonymous-key claim. If any command or assertion fails, leave the verdict open and stop.
</verification>
<success_criteria>
- The stale Phase 6 security verdict is replaced by evidence matching the post-gap code.
- T-06-23 through T-06-27 are each closed by concrete named tests, never by assertion alone.
- T-06-24 documents only `inline:domainless|<ClientIP>`, explicitly excludes throttle param and all request/forwarded Host input, and cites the named Host-rotation, cross-inline-parameter shared-budget, and authenticated `u:<id>` isolation tests.
- The successful audit is internally consistent at 26 total / 26 closed / 0 open with no new accepted risk.
- A failed full-repository `-race` suite, vet gate, or anonymous-key source assertion in either repository cannot produce a verified/zero-open document.
- All four source categories are fully covered without planning deferred items or non-authoritative warnings.
</success_criteria>
<output>
Create `.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-11-SUMMARY.md` when done.
</output>