docs(06): verify phase 6 passed, mark HTTP-04/06 complete
This commit is contained in:
@@ -186,9 +186,9 @@ Which phases cover which requirements. Updated during roadmap creation.
|
||||
| HTTP-01 | Phase 3 | Complete |
|
||||
| HTTP-02 | Phase 3 | Complete |
|
||||
| HTTP-03 | Phase 6 | Complete |
|
||||
| HTTP-04 | Phase 6 | In Progress |
|
||||
| HTTP-04 | Phase 6 | Complete |
|
||||
| HTTP-05 | Phase 6 | Complete |
|
||||
| HTTP-06 | Phase 6 | In Progress |
|
||||
| HTTP-06 | Phase 6 | Complete |
|
||||
| HTTP-07 | Phase 6 | Complete |
|
||||
| HTTP-08 | Phase 6 | Complete |
|
||||
| HTTP-09 | Phase 6 | Complete |
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: verifying
|
||||
stopped_at: Completed 06-11-PLAN.md
|
||||
last_updated: "2026-09-21T17:49:46.655Z"
|
||||
status: ready_to_plan
|
||||
stopped_at: Phase 06 complete (14/14) — ready to discuss Phase 7
|
||||
last_updated: 2026-09-21T17:51:51.900Z
|
||||
last_activity: 2026-09-21
|
||||
progress:
|
||||
total_phases: 15
|
||||
@@ -21,13 +21,13 @@ progress:
|
||||
See: .planning/PROJECT.md (updated 2026-09-16)
|
||||
|
||||
**Core value:** An existing WinterCMS-shaped app can be ported plugin by plugin to a single Go binary without its frontend noticing: the PHP version's API contract is the acceptance test.
|
||||
**Current focus:** Phase 06 — http-routing-auth-groups-and-rate-limiting
|
||||
**Current focus:** Phase 7 — user plugin and authentication
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 06 (http-routing-auth-groups-and-rate-limiting) — EXECUTING
|
||||
Plan: 11 of 11
|
||||
Status: Phase complete — ready for verification
|
||||
Phase: 7
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: 2026-09-21
|
||||
|
||||
Progress: [██████████] 100%
|
||||
@@ -36,7 +36,7 @@ Progress: [██████████] 100%
|
||||
|
||||
**Velocity:**
|
||||
|
||||
- Total plans completed: 34
|
||||
- Total plans completed: 48
|
||||
- Average duration: 21 min
|
||||
- Total execution time: 104 min
|
||||
|
||||
@@ -49,6 +49,7 @@ Progress: [██████████] 100%
|
||||
| 03 | 4 | - | - |
|
||||
| 04 | 4 | - | - |
|
||||
| 05 | 6 | - | - |
|
||||
| 06 | 14 | - | - |
|
||||
|
||||
**Recent Trend:**
|
||||
|
||||
|
||||
@@ -1,90 +1,42 @@
|
||||
---
|
||||
phase: 06-http-routing-auth-groups-and-rate-limiting
|
||||
verified: 2026-09-21T12:44:44Z
|
||||
status: gaps_found
|
||||
score: 8/12 must-haves verified
|
||||
verified: 2026-09-21T16:00:00Z
|
||||
status: passed
|
||||
score: 12/12 must-haves verified
|
||||
overrides_applied: 0
|
||||
mvp_mode_note: "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."
|
||||
mvp_mode_note: "ROADMAP mode is mvp but the technical goal is not a valid User Story (user-story.validate valid=false); this re-verification retains the technical goal contract."
|
||||
re_verification:
|
||||
previous_status: gaps_found
|
||||
previous_score: 7/12
|
||||
previous_score: 8/12
|
||||
gaps_closed:
|
||||
- "Fixed-window admission is atomic and anonymous inline keys no longer trust Host or throttle parameters."
|
||||
- "NAT64 /96, local-use NAT64 /48, and 6to4 embedded-private addresses are classified at dial time."
|
||||
- "House and raw recovery buffer and discard partial responses before emitting clean 500 contracts."
|
||||
- "InvScope 401/403 bodies use wire.WriteJSON and contain no trailing newline."
|
||||
- "Body limit now wraps all named/factory middleware (surf/router.go wrap: bodyLimit applied after the middleware loop, inside recovery); TestBodyLimitBoundsBodyConsumingMiddleware and TestBodyLimitBoundsNamedMiddleware exist and pass."
|
||||
- "RegisterBucket rejects nil store, nil Key, Max<1, Decay<=0; resolve() overflow-checks minute conversion; Middleware fails closed (500) instead of pass-through; TestRegisterBucketRejectsInvalidDefinitions, TestValidateThrottleRejectsOverflowAndNilStore, TestMiddlewareFailsClosed pass."
|
||||
- "fetchguard/ip.go carries full IANA special-use IPv4/IPv6 tables (198.18.0.0/15, 192.0.0.0/24, 240.0.0.0/4, ...); dialControl rejects zoned addresses with errPrivateIP; TestDialControlRejectsZonedAndSpecialUse and IANA boundary tests pass."
|
||||
- "06-SECURITY-REVIEW.md reopened and re-closed with T-06-28..35 (34/34 closed) backed by passing tests."
|
||||
- "Warnings also fixed: ServeMux conflict returns an error (handleRoute), factories built once (r.built cache), bad body config fails boot."
|
||||
gaps_remaining: []
|
||||
regressions: []
|
||||
gaps:
|
||||
- truth: "The configured JSON body-size limit constrains the entire non-raw request pipeline before any named middleware can consume the body."
|
||||
status: failed
|
||||
reason: "Router.wrap installs bodyLimit around only the terminal constrained handler, then wraps authentication/plugin middleware outside it. A named middleware can read an oversized body in full before MaxBytesReader is installed."
|
||||
artifacts:
|
||||
- path: "surf/router.go"
|
||||
issue: "Lines 353-394 apply bodyLimit before the named-middleware loop, making the cap inner at runtime."
|
||||
- path: "surf/bodylimit_test.go"
|
||||
issue: "Existing tests read only in the terminal handler and do not cover body-consuming middleware."
|
||||
missing:
|
||||
- "Place the selected body limit outside all named middleware while keeping it inside recovery."
|
||||
- "Add an oversized-body regression whose named middleware reads r.Body."
|
||||
- truth: "Named and inline rate-limit definitions fail boot when their store, key, maximum, or duration cannot enforce a real limit."
|
||||
status: failed
|
||||
reason: "RegisterBucket accepts nil keys, non-positive maxima, and non-positive decay; inline minute multiplication can overflow; Middleware explicitly passes through on nil Key/store. The adversarial probe registered a zero-decay Max=1 bucket with no error and both requests returned 204."
|
||||
artifacts:
|
||||
- path: "surf/limiter.go"
|
||||
issue: "Lines 51-60, 63-79, 84-96, and 139-153 accept or fail open on invalid enforcement state."
|
||||
- path: "surf/limiter_test.go"
|
||||
issue: "Unknown/duplicate names are covered, but invalid named definitions, nil store, and duration overflow are not."
|
||||
missing:
|
||||
- "Reject nil store and named buckets with nil Key, Max < 1, or Decay <= 0."
|
||||
- "Overflow-check inline minute-to-duration conversion and remove runtime fail-open behavior."
|
||||
- "Add boot-failure tests for every invalid definition."
|
||||
- truth: "The outbound fetch helper rejects all private and non-public special-use destinations, including scoped IPv6 link-local addresses, at the actual dial boundary."
|
||||
status: failed
|
||||
reason: "The hand-written table allows special-use IPv4 ranges such as 198.18.0.0/15, 192.0.0.0/24, and 240.0.0.0/4. Zoned fe80::/10 addresses also evade Prefix.Contains. PublicOnlyMode probes reached dialing and returned network_error, not private_ip, for all four examples."
|
||||
artifacts:
|
||||
- path: "fetchguard/ip.go"
|
||||
issue: "Lines 5-23 cover only a subset of non-public IPv4/IPv6 space; zoned IPv6 is not normalized before prefix checks."
|
||||
- path: "fetchguard/fetch.go"
|
||||
issue: "Lines 145-163 pass the parsed zoned address into the incomplete classifier."
|
||||
- path: "fetchguard/ip_test.go"
|
||||
issue: "No IANA special-use boundary table or scoped-link-local regression exists."
|
||||
missing:
|
||||
- "Classify the complete intended non-public/special-use IPv4 and IPv6 ranges."
|
||||
- "Reject or safely normalize scoped IPv6 before classification."
|
||||
- "Add dial-time tests for benchmark/reserved ranges and fe80:: addresses with zones."
|
||||
- truth: "The Phase 6 security review truthfully reports every open security threat."
|
||||
status: failed
|
||||
reason: "06-SECURITY-REVIEW.md says verified with 26/26 closed and zero open, but independent source review and executable probes demonstrate open body-limit, limiter fail-open, and SSRF-bypass threats."
|
||||
artifacts:
|
||||
- path: ".planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md"
|
||||
issue: "Frontmatter and verdict claim zero open threats despite reproducible open security gaps."
|
||||
missing:
|
||||
- "Keep the review open until the implementation gaps and adversarial tests are resolved."
|
||||
- "Add threat rows/findings for body-consuming middleware, invalid limiter definitions, and remaining non-public IP representations."
|
||||
deferred:
|
||||
- truth: "Public/onboarding groups have reachable unauthenticated handlers."
|
||||
addressed_in: "Phase 13"
|
||||
evidence: "Phase 13 goal explicitly ports onboarding/public/invitation routes with their public buckets; Phase 6 currently declares empty builders only."
|
||||
evidence: "Phase 13 ports onboarding/public/invitation routes with public buckets."
|
||||
- truth: "Unknown and malformed ids on ownership-scoped resources both return 404."
|
||||
addressed_in: "Phase 12"
|
||||
evidence: "Phase 12 owns Collections and Albums; Phase 6 only supplies and tests the constraint primitive."
|
||||
- truth: "A production OAuth/RFC route is present in a raw group and can be inspected for absence of house middleware."
|
||||
evidence: "Phase 12 owns Collections and Albums; Phase 6 supplies the constraint primitive."
|
||||
- truth: "A production OAuth/RFC route exists in the raw group."
|
||||
addressed_in: "Phase 8"
|
||||
evidence: "Phase 8 goal owns the OAuth2.1 endpoints; Phase 6 has an empty GroupRaw and synthetic framework/route-table tests."
|
||||
evidence: "Phase 8 owns the OAuth2.1 endpoints; Phase 6 supplies the raw-group contract."
|
||||
- truth: "Manual-cover and Discogs production callers use fetchguard."
|
||||
addressed_in: "Phase 12 / Phase 14"
|
||||
evidence: "Phase 12 owns cover handling and Phase 14 owns the Discogs integration; no production caller imports fetchguard yet."
|
||||
evidence: "Cover handling and Discogs integration are owned there."
|
||||
---
|
||||
|
||||
# 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.
|
||||
**Verified:** 2026-09-21
|
||||
**Status:** passed
|
||||
**Re-verification:** Yes, after gap-closure plans 06-12..06-14
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
@@ -92,133 +44,50 @@ ROADMAP marks this phase `mode: mvp`, but `gsd-sdk query user-story.validate` re
|
||||
|
||||
| # | 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. |
|
||||
| 1 | JWT and token groups share the genres handler; subsets mutually exclusive | VERIFIED (public groups deferred) | Unchanged from prior verification; tests pass. |
|
||||
| 2 | Five named buckets and inline throttles enforce live limits/keys, including stacking | VERIFIED | Valid definitions plus now-rejected invalid ones. |
|
||||
| 3 | Response conventions and raw/house panic behavior | VERIFIED | wire tests and recovery tests pass. |
|
||||
| 4 | Fetch helper is an SSRF boundary (allow-list, non-public IP rejection incl. special-use and zoned, cap, timeout) | VERIFIED | `fetchguard/ip.go` IANA tables; `dialControl` rejects zone; tests `TestDialControlRejectsZonedAndSpecialUse`, `TestIsReservedOrPrivateIANABoundaries`, `TestIsReservedOrPrivateIgnoresZone`. |
|
||||
| 5 | OpenAPI, path CORS, and body limits enforce the deployment contract | VERIFIED | Body cap now outermost of named middleware (router.go wrap, cap applied after loop, before locale/recover); body-consuming middleware test passes. |
|
||||
| 6 | Guard registry unifies users, exact error contracts | VERIFIED | Unchanged; typed-nil now rejected (8a9449d). |
|
||||
| 7 | `name:param` middleware via factories | VERIFIED | Factories cached in `r.built`, built once. |
|
||||
| 8 | Fixed-window limiter is a safe primitive | VERIFIED | Invalid definitions fail boot; runtime fail-closed. |
|
||||
| 9 | Raw routes refuse house middleware, bare recovery | VERIFIED | Unchanged. |
|
||||
| 10 | Route-table auth-group isolation | VERIFIED | Unchanged. |
|
||||
| 11 | Security review accurate | VERIFIED | 34/34 closed, mapped to existing passing tests; contradicting code paths independently confirmed fixed. |
|
||||
| 12 | Packages and regressions run cleanly | VERIFIED | `go vet ./...` clean and `go test ./... -count=1 -race -short` pass in summercms.go and fonoteka.go. |
|
||||
|
||||
**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.
|
||||
**Score:** 12/12
|
||||
|
||||
### 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.
|
||||
All seven IDs (HTTP-03..HTTP-09) appear in PLAN frontmatter and in REQUIREMENTS.md mapped to Phase 6; none 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. |
|
||||
| Requirement | Status | Evidence |
|
||||
| --- | --- | --- |
|
||||
| HTTP-03 | SATISFIED (public handlers deferred to Phase 13) | Shared handler, exclusivity tests. |
|
||||
| HTTP-04 | SATISFIED | Five buckets, stacking, invalid definitions rejected. |
|
||||
| HTTP-05 | SATISFIED | Single accessor, exact denial bodies. |
|
||||
| HTTP-06 | SATISFIED (OAuth routes deferred to Phase 8) | wire helpers, raw infrastructure. |
|
||||
| HTTP-07 | SATISFIED | Allow-list, cap, timeout, full non-public/zoned rejection. |
|
||||
| HTTP-08 | SATISFIED (warning: OpenAPI covers one live route) | Generation/validation script and artifact. |
|
||||
| HTTP-09 | SATISFIED | CORS values and body cap enforced over named middleware. |
|
||||
|
||||
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.
|
||||
Note: REQUIREMENTS.md traceability still shows HTTP-04 and HTTP-06 as "In Progress" while their checklist boxes are checked. This is metadata only; it should be updated to Complete.
|
||||
|
||||
### 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.
|
||||
No unreferenced TBD/FIXME/XXX in phase files. All previously listed blockers and warnings (body cap order, limiter fail-open, overflow, IP table, ServeMux panic, typed-nil guard, factories built twice, zero body config) verified fixed in source.
|
||||
|
||||
### 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.
|
||||
None.
|
||||
|
||||
### 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.
|
||||
All four gaps from the prior verification are closed in code and covered by regression tests; no regressions found. Remaining items are deferred by roadmap to Phases 8, 12, 13, 14.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-09-21T12:44:44Z_
|
||||
_Verifier: the agent (gsd-verifier)_
|
||||
_Verified: 2026-09-21_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
|
||||
Reference in New Issue
Block a user