20 KiB
phase, verified, status, score, overrides_applied, mvp_mode_note, re_verification, gaps, deferred
| phase | verified | status | score | overrides_applied | mvp_mode_note | re_verification | gaps | deferred | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-http-routing-auth-groups-and-rate-limiting | 2026-09-21T12:44:44Z | gaps_found | 8/12 must-haves verified | 0 | ROADMAP mode is mvp but the technical goal is not a valid User Story (user-story.validate valid=false); this requested re-verification retains the prior technical goal contract. |
|
|
|
Phase 6: HTTP routing, auth groups and rate limiting Verification Report
Phase Goal: The three mutually exclusive auth groups share handlers with correct subsets, rate-limit buckets are ported 1:1, OAuth/RFC routes are structurally raw, and the auth registry, limiter, and SSRF fetch helper form secure shared infrastructure. Verified: 2026-09-21T12:44:44Z Status: gaps_found Re-verification: Yes — all four prior implementation gaps are closed; new adversarial checks expose four blocking concerns.
ROADMAP marks this phase mode: mvp, but gsd-sdk query user-story.validate reports valid=false because the goal is not in User Story form. Consistent with the prior technical verification and the explicit re-verification request, this report evaluates the supplied technical contract and records the metadata discrepancy.
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | JWT and personal-token groups share the genres handler; implemented subsets are mutually exclusive | ✓ VERIFIED (later public groups deferred) | routes.go:10-16 binds one handler value twice; TestGenresSharedHandler and TestFullRouteTableAuthGroupMutualExclusivity pass. Public/onboarding builders remain empty and are deferred to Phase 13. |
| 2 | Five named buckets and inline throttles enforce documented live limits/keys, including stacking | ✓ VERIFIED for valid definitions | Plugin.Buckets() matches PHP's five names, values, and key composition; atomic, stacking, Host-rotation, shared-inline-budget, and request-61 tests pass. Invalid definitions still fail open under truth 8. |
| 3 | Response conventions and raw/house panic behavior hold | ✓ VERIFIED | wire tests cover [], +00:00, tri-state null, no-newline JSON; partial-write panic tests prove clean opaque/bare 500s. Actual OAuth endpoints are deferred to Phase 8. |
| 4 | Fetch helper is an SSRF boundary with allow-list, non-public-IP rejection, cap, and timeout | ✗ FAILED | Transition fixes pass, but 198.18.0.1, 192.0.0.1, 240.0.0.1, and [fe80::1%eth0] reach dialing and map to network_error, not private_ip. |
| 5 | OpenAPI/type validation, path CORS, and production body limits exist and enforce the deployment contract | ✗ FAILED | OpenAPI 3 artifact and CORS/config values exist; body-consuming middleware bypasses the 134217728-byte cap because the limiter is installed inside named middleware. |
| 6 | Guard registry unifies JWT/personal-token users and preserves exact error contracts | ✓ VERIFIED | One bouncer.User accessor; JWT legacy equivalence, token hash/usability/stamp, and exact InvScope 401/403 tests pass. |
| 7 | name:param middleware resolves through factories |
✓ VERIFIED | Router.wrap uses strings.Cut; inv.scope, throttle, and body.limit are wired through factories and factory tests pass. |
| 8 | Fixed-window limiter is a safe enforcement primitive | ✗ FAILED | Atomic admission and stable keys are fixed, but malformed named buckets, nil store/key, and overflowing durations can disable enforcement without boot failure. |
| 9 | Raw routes refuse house-tagged middleware and use bare recovery | ✓ VERIFIED | Capability registration, raw refusal, sticky raw inheritance, and bare panic recovery tests pass. |
| 10 | Route table excludes JWT middleware from token routes and vice versa | ✓ VERIFIED | Real assembled-router isolation tests pass. |
| 11 | Phase 6 security review accurately maps and closes current threats | ✗ FAILED | Threat IDs are mapped, but the zero-open verdict is contradicted by reproduced security gaps. |
| 12 | Phase packages and route regressions run cleanly | ✓ VERIFIED | Both repository gates plus nested fonoteka/user module tests pass under -race -short; vet passes. Tests omit the failing adversarial paths above. |
Score: 8/12 truths verified
Deferred Items
| # | Item | Addressed In | Evidence |
|---|---|---|---|
| 1 | Reachable public/onboarding handlers | Phase 13 | Later goal explicitly names onboarding/public/invitation routes and public buckets. |
| 2 | Ownership-resource malformed/unknown ID parity | Phase 12 | Collections and Albums are implemented there. |
| 3 | Real OAuth/RFC raw routes | Phase 8 | OAuth2.1 endpoint implementation belongs there; Phase 6 supplies the raw-group contract. |
| 4 | Production fetchguard callers | Phase 12 / 14 | Manual cover and Discogs caller implementation belongs to those phases. |
Required Artifacts
The SDK's PLAN paths include repository prefixes and therefore report false missing files from this repository root; artifacts were resolved manually in summercms.go and sibling ../fonoteka.go.
| Artifact | Expected | Status | Details |
|---|---|---|---|
bouncer/registry.go, guard.go, context.go |
Named guards and unified request identity | ✓ VERIFIED | Substantive, plugin-boot wired, and tested; typed-nil registration remains a warning. |
../fonoteka.go/.../token_guard.go |
Real inv_token verification | ✓ VERIFIED | SHA-256 lookup, usability checks, one last-used stamp, user lookup, and credential propagation. |
../fonoteka.go/.../token_scope.go |
Exact PHP 401/403 scope gate | ✓ VERIFIED | Uses wire.WriteJSON; untrimmed byte tests pass. |
surf/limiter.go, limiter_store.go |
Named/inline fixed-window enforcement | ✗ DEFECTIVE | Real buckets work and admission is atomic; malformed enforcement configuration fails open. |
surf/routetable.go, pact/capabilities.go |
Route/raw inspection | ✓ VERIFIED | Used by router, route:list, and isolation tests. |
wire/response.go |
JSON/time/nullable helpers | ✓ VERIFIED | Used by genre controller and independently tested. |
surf/cors.go, bodylimit.go |
PHP CORS/body caps | ✗ DEFECTIVE | CORS and values flow; body cap is installed after named middleware has already run. |
fetchguard/fetch.go, policy.go, ip.go |
Guarded outbound fetch | ✗ DEFECTIVE | Host/scheme/cap/timeout/redirect and transition checks exist; remaining non-public representations are allowed to dial. |
../fonoteka.go/docs/openapi.json |
Generated OpenAPI 3 artifact | ✓ VERIFIED (limited surface) | Valid OpenAPI 3.0.3 with the annotated live JWT genres route; personal-token path/security schemes are absent. |
06-SECURITY-REVIEW.md |
Current threat verdict | ✗ STALE | Claims 26/26 closed despite currently reproducible open threats. |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
routes.go |
controllers.ListGenres |
Same handler variable on JWT/token groups | WIRED | Both live routes share the handler. |
routes.go |
guard → limiter → scope | Ordered middleware list | WIRED | 1-60 unauthenticated requests are 401; request 61 is exact 429. |
plugin.go |
five limiter buckets | surf.BucketProvider |
WIRED-BUT-UNSAFE | Five valid definitions register; invalid definitions are not rejected. |
token_guard.go |
models.ApiToken |
Hash lookup/usability/stamp | WIRED | Real DB-backed tests cover the data path. |
token_scope.go |
bouncer context / wire | user+credential reads and WriteJSON | WIRED | Exact denial and allowed-scope tests pass. |
routes.go |
GroupRaw |
Empty OAuth landing group | PARTIAL / DEFERRED | Framework contract is wired; there is no production OAuth route until Phase 8. |
fetch.go |
ip.go |
DialControl classification | WIRED-BUT-INCOMPLETE | Actual dial target is checked, but the classifier is not complete and mishandles zones. |
router.go |
bodylimit.go |
MaxBytesReader wrapper | MISORDERED | Cap wraps only the terminal handler, not named middleware. |
genre_controller.go |
wire.WriteJSON / GORM |
DB query to response DTO | WIRED | Real dynamic genre data flows to JSON. |
Data-Flow Trace (Level 4)
| Artifact | Data Variable | Source | Produces Real Data | Status |
|---|---|---|---|---|
| Genres handler | rows |
GORM genre/count query scoped by user/collection | Yes | ✓ FLOWING |
| Token guard | token, user |
GORM hash/user queries | Yes | ✓ FLOWING |
| Limiter | counter entry | MemoryStore keyed by resolver | Yes for valid definitions | ⚠️ FLOWING, CONFIG FAIL-OPEN |
| CORS/body config | corsCfg, defaultBytes |
production YAML through compass | Yes | ✗ BODY CAP MISORDERED |
| Fetch helper | dial address / response stream | HTTPS transport | Yes in tests; no production caller yet | ✗ CLASSIFIER INCOMPLETE / CALLER DEFERRED |
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
| Prior gap repairs | Targeted go test for atomic limiter, inline keys, transitions, partial panic recovery, raw refusal, and InvScope under -race |
All selected packages ok |
✓ PASS |
| Framework repository | go test ./... -count=1 -race -short |
All packages pass | ✓ PASS |
| Fonoteka root repository | go test ./... -count=1 -race -short |
Root/parity packages pass | ✓ PASS |
| Nested app modules | go test ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... -count=1 -race -short |
All packages pass | ✓ PASS |
| Vet | go vet ./... in both roots plus nested app modules |
Exit 0 | ✓ PASS |
| Body-limit boundary | /tmp/phase06_bodylimit_probe.go with limit 4 and 10-byte body |
middleware consumed 10; status 200; no handler error | ✗ FAIL |
| Invalid limiter definition | /tmp/phase06_adversarial_probe.go zero-decay Max=1 bucket |
registration error nil; statuses [204 204] |
✗ FAIL |
| Special-use/scoped IP rejection | /tmp/phase06_fetch_probe.go |
all four inputs returned network_error, not private_ip |
✗ FAIL |
| Semantic route conflict | /tmp/phase06_adversarial_probe.go |
ServeMux conflict panicked instead of returning an error | ⚠️ WARNING |
Probe Execution
No phase probe is declared and no scripts/*/tests/probe-*.sh exists. Step 7c is not applicable.
Requirements Coverage
All seven requirement IDs declared across Phase 6 plans are present in REQUIREMENTS.md and mapped to Phase 6. No additional Phase 6 requirement ID is orphaned.
| Requirement | Source Plan | Description | Status | Evidence |
|---|---|---|---|---|
| HTTP-03 | 06-01, 06-05, 06-11 | Three auth groups share handlers with subsets | ◐ PARTIAL / DEFERRED | JWT/token sharing and exclusivity pass; public/onboarding handlers are Phase 13. |
| HTTP-04 | 06-02, 06-05..07, 06-11 | Named/inline rate limiting and stacking | ✗ BLOCKED | Five live definitions and atomic traffic behavior pass; invalid definitions can silently disable enforcement. |
| HTTP-05 | 06-01, 06-05, 06-10, 06-11 | Named guards share current-user accessor | ✓ SATISFIED | JWT and inv_token use one registry/context accessor; exact denial tests pass. |
| HTTP-06 | 06-03, 06-05, 06-09, 06-10, 06-11 | Response conventions and raw OAuth boundary | ✓ SATISFIED / DEFERRED ROUTES | Helpers and raw infrastructure pass; actual OAuth handlers are Phase 8. |
| HTTP-07 | 06-04, 06-05, 06-08, 06-11 | Guarded outbound fetch | ✗ BLOCKED | Host/cap/timeout/redirect and transition checks exist, but additional non-public/scoped targets are dialed. |
| HTTP-08 | 06-03, 06-05, 06-11 | Swag/OpenAPI and TypeScript validation | ✓ SATISFIED (warning) | Generation/validation script and valid OpenAPI 3 artifact exist; document covers only one live route. |
| HTTP-09 | 06-03, 06-05, 06-11 | PHP-matching CORS and body limits | ✗ BLOCKED | Values and path CORS match; named middleware can bypass the body cap. |
REQUIREMENTS.md remains internally inconsistent for HTTP-04 and HTTP-06: their checklist entries are checked while traceability still says In Progress. This report does not modify requirements metadata.
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
surf/router.go |
353-394 | body cap inside named middleware | 🛑 Blocker | auth/plugin middleware can consume unbounded input |
surf/limiter.go |
63-96 | invalid bucket accepted; runtime pass-through | 🛑 Blocker | security control silently disables |
surf/limiter.go |
139-153 | unchecked duration multiplication | 🛑 Blocker | overflow can produce an ineffective window |
fetchguard/ip.go |
5-54 | incomplete non-public table; zone-insensitive prefix checks | 🛑 Blocker | user URL can dial non-public destinations |
06-SECURITY-REVIEW.md |
frontmatter/verdict | zero-open assertion contradicted by code | 🛑 Blocker | security sign-off is not auditable |
surf/router.go |
338-349 | ServeMux semantic conflict can panic | ⚠️ Warning | plugin route input can crash assembly |
bouncer/registry.go |
28-47 | typed-nil guard is accepted | ⚠️ Warning | later authentication can panic |
bouncer/jwt.go |
146-159 | fractional float subject truncation | ⚠️ Warning | signed numeric subject can resolve another ID |
surf/router.go |
489-493 | middleware factories constructed twice | ⚠️ Warning | allocation/side effects can duplicate at boot |
surf/router.go |
440-443 | missing/malformed body config becomes zero | ⚠️ Warning | typo or missing config disables the cap |
No unreferenced TBD, FIXME, or XXX marker was found in the Phase 6 implementation files. Scaffolding-generated placeholder text outside this phase is not a runtime stub.
Disconfirmation pass: the passing body-limit tests exercise only the terminal handler; the passing fetchguard tests omit multiple non-public and zoned forms; the passing limiter tests cover unknown/duplicate names but not invalid definitions. These are precisely the cases where the green suite overstates the security contract.
Human Verification Required
None. Production body-size values were previously operator-confirmed and are present in config. Current failures are programmatically reproducible and need implementation/test changes, not subjective UAT.
Gaps Summary
All four gaps from the prior verification were genuinely repaired. Phase 6 still cannot pass because three security-load-bearing primitives remain unsafe at their boundaries: body limits do not constrain named middleware, limiter misconfiguration fails open, and fetchguard allows additional non-public/scoped destinations to reach the dial attempt. Consequently the zero-open security review is stale. Later-phase route and caller work remains deferred only where the roadmap explicitly owns it; it does not excuse these shared-infrastructure failures.
Verified: 2026-09-21T12:44:44Z Verifier: the agent (gsd-verifier)