Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-SECURITY-REVIEW.md
Jakub Zych 92fb6e323f docs(09-12): record the phase 9 acceptance evidence
- 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.
2026-09-27 03:03:09 +02:00

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`.