- Security review names the test that fails if each high control is removed. - Validation rows now point at the phase gate commands. - Roadmap shows 12/12 plans executed.
5.2 KiB
5.2 KiB
phase, reviewed, threats_open
| phase | reviewed | threats_open |
|---|---|---|
| 09 | 2026-09-27 | 0 |
Phase 09 Security Review
Fresh review of the backend admin surface after plan 09-12. A high threat is mitigated only when the named test fails if that control is removed.
| Threat | Severity | Disposition | Production control | Executable evidence | Result |
|---|---|---|---|---|---|
| T-09-01 | high | mitigated | Separate frontend and backend secrets, required audience, and distinct guard registries | go test ./bouncer -run '^TestPhase09GuardIsolation$' -count=1 |
pass |
| T-09-02 | high | mitigated | Backend guard, then controller lookup, then permission, before schema or query work | go test ./cabana -run '^TestPhase09PermissionMatrix$' -count=1 |
pass |
| T-09-03 | high | mitigated | Auth logs record outcome, method, path, remote, and admin id only | go test ./cabana -run '^TestPhase09SecurityCoverage$' -count=1 |
pass |
| T-09-04 | medium | mitigated | Create and reset commands validate role codes and do not echo passwords | `go test ./cabana -run 'Test(AdminCreate | ResetPassword)' -count=1` |
| T-09-05 | high | mitigated | Strict form compiler rejects unknown keys, types, partials, and providers | `go test ./cabana -run '^TestFormSchema(Compile | Rejects)' -count=1` |
| T-09-06 | medium | mitigated | Schema locale comes from the request, not a shared cache of translated text | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestPhase09AssembledAcceptance$' -count=1 |
pass |
| T-09-07 | high | mitigated | List identifiers are compiled allowlists; injected sort and path ids fail closed | go test ./cabana -run '^TestPhase09SecurityCoverage$' -count=1 |
pass |
| T-09-08 | medium | mitigated | Page size must be a compiled option; adjacent pages stay stable | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestPhase09AssembledAcceptance$' -count=1 |
pass |
| T-09-09 | high | mitigated | Writable projection drops protected, unknown, case-variant, and nested keys | go test ./cabana -run '^TestPhase09SecurityCoverage$' -count=1 |
pass |
| T-09-10 | high | mitigated | Hook failure aborts the create and leaves no row | go test ./cabana -run '^TestPhase09SecurityCoverage$' -count=1 |
pass |
| T-09-11 | high | mitigated | Album lookup is collection-scoped on the server | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestAlbumsAdmin(CollectionMatch|CrossCollection)$' -count=1 |
owned by 09-06; not re-run in 09-12 |
| T-09-12 | high | mitigated | Normalized email association requires exactly one active match | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestAlbumsAdminAmbiguousEmail$' -count=1 |
owned by 09-06; not re-run in 09-12 |
| T-09-13 | high | mitigated | Every generated artists route is inside the backend group and denies an empty grant | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestPhase09Security' -count=1 |
pass |
| T-09-14 | high | mitigated | Duplicate controller ids and routes fail activation | go test ./party -run '^TestActivateDuplicateIDRejected$' -count=1 and go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestStylesAdminDuplicateRegistration$' -count=1 |
owned by 09-08; not re-run in 09-12 |
| T-09-15 | medium | mitigated | Style option values keep their YAML kinds | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestStylesAdminTypedOptions$' -count=1 |
owned by 09-09; not re-run in 09-12 |
| T-09-16 | high | mitigated | Relation mutations reject unknown pivot fields | go test ./cabana -run '^TestPhase09SecurityCoverage$' -count=1 |
pass |
| T-09-17 | high | mitigated | Relation reads and writes require the operation permission before SQL | `go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestCollectionsAdmin(RelationPermissions | CrossScope |
| T-09-18 | high | mitigated | Settings writes are permission-first and fillable-only | go test ../fonoteka.go/plugins/golem15/fonoteka -run '^TestAdminSettings' -count=1 |
owned by 09-11; not re-run in 09-12 |
| T-09-19 | medium | mitigated | Navigation and settings lists are filtered to granted entries | go test ./cabana -run '^TestPhase09PermissionMatrix$' -count=1 |
pass |
| T-09-20 | high | mitigated | The phase gate rejects skipped tests and zero-test runs | scripts/check-phase9.sh --self-test |
pass |
| T-09-21 | high | mitigated | Fresh admin migration rollback keeps other histories, and OpenAPI lists every D-09 route | scripts/check-phase9.sh --postgres and scripts/check-phase9.sh --openapi |
pass |
| T-09-SC | high | mitigated | No package was added. OpenAPI generation reuses pinned swag v1.16.6 and openapi-typescript 7.13.0 | scripts/check-phase9.sh --openapi |
pass |
Residual risk
Browser rendering of these schemas is Phase 10. This review does not claim a visual check.
Removal check
Removing the backend audience check fails TestPhase09GuardIsolation. Mounting a protected route without the backend middleware fails TestPhase09PermissionMatrix and TestPhase09SecurityRoutes. Dropping an admin path from the OpenAPI document fails TestPhase09ContractInventory. A skipped PostgreSQL test fails scripts/check-phase9.sh.