--- phase: 06-http-routing-auth-groups-and-rate-limiting plan: 06 subsystem: api-security tags: [rate-limiting, middleware-order, personal-token, security-review, httptest] requires: - phase: 06-http-routing-auth-groups-and-rate-limiting provides: inv_token guard, InvScope, FixedWindowLimiter, fonoteka-api-token bucket, assembled route tests provides: - personal-token route order that rate-limits unauthenticated deny traffic before InvScope - assembled-router proof of 401 through request 60 and exact 429 on request 61 - T-06-21 and T-06-22 security evidence with 21 threats closed and zero open affects: [phase-07-user-plugin, phase-08-oauth, personal-token-routes, security-review] tech-stack: added: [] patterns: - personal-token middleware order is inv_token -> throttle: -> inv.scope: - deny-path limiter regressions use one fresh surf.Assemble handler and stable RemoteAddr key-files: created: [] modified: - plugins/golem15/fonoteka/routes.go - plugins/golem15/fonoteka/routes_isolation_test.go - .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md key-decisions: - "Keep inv_token outermost so valid credentials populate bouncer.Credential before the limiter selects tok:" - "Place throttle before InvScope so missing and invalid credentials consume the per-IP deny-path budget" patterns-established: - "Any live route combining inv_token, a named throttle, and inv.scope declares them in that exact source order" - "Rate-limit exhaustion tests exercise the real plugin set and production route rather than hand-composed middleware" requirements-completed: [HTTP-04] duration: 1h 29m completed: 2026-09-20 --- # Phase 6 Plan 06: Personal-token deny-path rate-limit gap closure Summary **Personal-token genres now preserves per-token keying while bounding unauthenticated 401 traffic at 60 requests per minute, with assembled-route and security-review evidence** ## Performance - **Duration:** 1h 29m - **Started:** 2026-09-20T10:01:25Z - **Completed:** 2026-09-20T11:30:57Z - **Tasks:** 2 - **Files modified:** 3 ## Accomplishments - Reordered the live personal-token genres middleware to `inv_token`, `throttle:fonoteka-api-token`, `inv.scope:read`, preserving valid-token `tok:` keys while limiting unauthenticated fallback traffic by client IP. - Added `TestPersonalTokenGenresUnauthenticatedRequestsAreRateLimited`, which drives 61 same-IP requests through one fresh handler returned by `surf.Assemble`: requests 1-60 retain the PHP-compatible 401 body and request 61 receives the exact 429 body and exhausted-limit headers. - Extended `06-SECURITY-REVIEW.md` through Plan 06-06 with T-06-21/T-06-22, 21 closed threats, zero open, and no new accepted risk. - Passed the targeted regression, `go vet ./...`, and repository-wide `go test ./... -short` in `fonoteka.go`. ## Task Commits Each task was committed atomically: 1. **Task 1: Repair personal-token middleware order and prove deny-path throttling** - `33f721d` (fix, fonoteka.go) 2. **Task 2: Extend the Phase 6 security review through gap closure** - `3423da2` (docs, summercms.go) **Plan metadata:** (this commit) ## Files Created/Modified - `plugins/golem15/fonoteka/routes.go` - Declares the live personal-token middleware in credential, limiter, then scope order. - `plugins/golem15/fonoteka/routes_isolation_test.go` - Proves the assembled production route returns 401 through request 60 and exact 429 on request 61. - `.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md` - Maps T-06-21/T-06-22 to the runtime-order invariant and regression evidence. ## Decisions Made - `inv_token` remains before the limiter so valid credentials populate `bouncer.Credential` before the bucket closure resolves `tok:`. - `throttle:fonoteka-api-token` remains before `inv.scope:read` so missing and invalid credentials consume the per-IP fallback bucket before the scope gate returns 401. ## Deviations from Plan None - plan executed exactly as written. ## Issues Encountered - The delegated executor stopped returning progress after partially editing the sibling `fonoteka.go` repository. After the configured stall threshold and user-approved recovery, execution switched inline; the partial diff was inspected, retained, validated, and committed without duplication. - The sandboxed full test run could not access the Docker daemon used by the parity harness. The same required command passed after rerunning with approved Docker access. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - HTTP-04's verification blocker and code-review CR-01 are directly closed. - Phase 6 is ready for re-verification; no Plan 06-06 implementation blockers remain. ## Self-Check: PASSED - FOUND: `../fonoteka.go/plugins/golem15/fonoteka/routes.go` - FOUND: `../fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go` - FOUND: `.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md` - FOUND: `33f721d` in `fonoteka.go` - FOUND: `3423da2` in `summercms.go` - PASS: targeted assembled-router regression - PASS: `go vet ./...` in `fonoteka.go` - PASS: `go test ./... -short` in `fonoteka.go` - PASS: T-06-21/T-06-22 evidence and audit total 21 closed / 0 open --- *Phase: 06-http-routing-auth-groups-and-rate-limiting* *Completed: 2026-09-20*