- 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.
43 lines
5.2 KiB
Markdown
43 lines
5.2 KiB
Markdown
---
|
|
phase: "09"
|
|
reviewed: "2026-09-27"
|
|
threats_open: 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` | owned by 09-02; not re-run in 09-12 |
|
|
| T-09-05 | high | mitigated | Strict form compiler rejects unknown keys, types, partials, and providers | `go test ./cabana -run '^TestFormSchema(Compile|Rejects)' -count=1` | owned by 09-03; not re-run in 09-12 |
|
|
| 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|ForgedPivot)$' -count=1` | owned by 09-10; not re-run in 09-12 |
|
|
| 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`.
|