docs(phase-16.1): add security threat verification; fix stale validation row
This commit is contained in:
@@ -99,7 +99,7 @@ coverage:
|
||||
ref: "../sm-journal-plugin/classes/format_html_test.go#TestFormatHTMLKeepsRawHTML"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "../sm-journal-plugin/classes/format_html_test.go#TestFormatHTMLRejectsUnsafeHTML"
|
||||
ref: "../sm-journal-plugin/classes/format_html_test.go#TestFormatHTMLSanitizesUnsafeHTML"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D6
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
phase: "16.1"
|
||||
slug: "journal-admin-urgent-fixes-relation-search-list-state-post-f"
|
||||
status: draft
|
||||
# threats_open = count of OPEN threats at or above workflow.security_block_on severity (the blocking gate)
|
||||
threats_open: 0
|
||||
asvs_level: 1
|
||||
created: "2026-10-10"
|
||||
---
|
||||
|
||||
# Phase 16.1 — Security
|
||||
|
||||
> Per-phase security contract: threat register, accepted risks, and audit trail.
|
||||
|
||||
---
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description | Data Crossing |
|
||||
|----------|-------------|---------------|
|
||||
| Admin SPA → cabana list API | Authenticated backend users send search, sort and filter params that become SQL | Query params (untrusted input), list rows |
|
||||
| Post authors / import / Journal API → journal `content_html` | Markdown and raw HTML from admins, the import command and API writers is rendered and stored | Post HTML (stored, served to anonymous readers) |
|
||||
| Journal API → public Nuxt layer → browser | Stored HTML is rendered with `v-html` on public pages | Post and category HTML |
|
||||
| Admin browser storage | Remembered list queries persist across sessions on a shared browser | List query params per admin |
|
||||
| Release workstation → origin / UAT server | Tags, pushes, binary upload and restarts | Source, binary, server state |
|
||||
|
||||
---
|
||||
|
||||
## Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation | Status |
|
||||
|-----------|----------|-----------|----------|-------------|------------|--------|
|
||||
| T-16.1-01 | Tampering (SQLi) | modules/cabana/query.go relation search/sort | high | mitigate | Identifiers only from GORM's parsed relationship, quoted via `quotedIdent`/`qualifiedColumn`; polymorphic/unknown columns answer 422; search term `escapeLike`d and bound; sort limited to schema-declared columns and asc/desc; route behind `protect()` | closed |
|
||||
| T-16.1-02 | Tampering / EoP (stored XSS) | sm-journal-plugin classes/format_html.go | medium | mitigate | Planned regex `rejectUnsafe` proved bypassable (CR-01, 16.1-REVIEW.md). Replaced by goldmark + bluemonday allow-list on every host `content_html` writer (admin, import, API) and `CheckHTML` for categories (sm-journal-plugin a3551f2; decision note `.planning/notes/journal-html-sanitizing-bluemonday.md`); 35-payload token-checked tests; public layer replaced its regex gate with sanitize-html (vue-journal-layer 5beb1c5). Deployed to UAT: host cbb4e7d, binary e1ba3587…148ceb (server sha256 checked by the orchestrator 2026-10-09) | closed |
|
||||
| T-16.1-03 | Information Disclosure | admin/src/state/listState.ts | low | mitigate | Only list query params stored; browser storage limited to listState.ts/useSidebar.ts (enforced by scripts/check-phase10.sh). WR-03 cross-admin leak fixed in cd25af0: keys scoped `summer-admin.list:<adminId>:<controller>`, all keys cleared on logout, session expiry (`expireSession` via `onUnauthorized`) and sign-in as another admin; legacy keys swept | closed |
|
||||
| T-16.1-04 | Tampering | admin list state restore | low | mitigate | `readListState` validates shape and re-parses via `parseListQuery`; server re-validates page, per_page, sort and filters (422); a failed restored query is forgotten and the plain list reloads once (WR-02, cd25af0) | closed |
|
||||
| T-16.1-05 | DoS | cabana relation search | low | accept | See AR-01 | closed |
|
||||
| T-16.1-06 | Tampering (false-green release) | 16.1-02 release gate | medium | mitigate | Release sheet records `release-gate-ok`, counted smoke tests, vitest, dist and OpenAPI gates and the human decision verbatim (16.1-02-SUMMARY) | closed |
|
||||
| T-16.1-07 | Tampering / Repudiation | summercms v0.1.16 tag and pushes | high | mitigate | Annotated tag on d77393b containing 06d75cb, bd8626e, 9cac45c; origin tag object d8e3536 matches local; journal and host heads on origin | closed |
|
||||
| T-16.1-08 | Information Disclosure | UAT deploy | high | mitigate | DEPLOY.md rsync list excludes env files; no credentials in any SUMMARY; executors never requested or held credentials | closed |
|
||||
| T-16.1-09 | Tampering | UAT database | medium | mitigate | Read-only psql/log checks only; post data changed only by two human admin saves; `.prev` binary kept for rollback | closed |
|
||||
| T-16.1-10 | EoP | deploy sudo scope | low | accept | See AR-02 | closed |
|
||||
| T-16.1-11 | Repudiation (false green) | 16.1-VALIDATION.md | medium | mitigate | Row 16.1-03-03 updated 2026-10-10 to the post-CR-01 test names; re-run gives four top-level PASS lines | closed |
|
||||
| T-16.1-12 | Tampering / DoS | cabana DryRun tests | low | mitigate | DryRun DBs use `DryRun: true`, `DisableAutomaticPing`, `port=1` DSN; Postgres tests use testcontainers | closed |
|
||||
| T-16.1-SC | Tampering (supply chain) | go.mod / package.json | high | mitigate | summercms.go adds no dependency; sm-journal-plugin adds bluemonday v1.0.27 (+ aymerick/douceur v0.2.0, gorilla/css v1.0.1), named in the decision note, hash-pinned, `go mod verify` clean; vue-journal-layer adds sanitize-html 2.18.0 with decision note docs/decisions/0001-sanitize-html.md | closed |
|
||||
| T-16.1-13 (audit W-1) | Tampering (stored XSS, latent) | sm-journal-plugin translated `content_html` (API syncTranslations, import) | medium | mitigate | Fix in progress: sanitize or derive every translated `content_html` write | open — below high threshold (non-blocking) |
|
||||
| T-16.1-14 (audit W-2) | Tampering | sm-journal-plugin FormatHTML byte-preserving shortcut (duplicate attributes) | low | mitigate | Fix in progress: treat duplicate attributes as a change so the sanitized output is stored and CheckHTML refuses | open — below high threshold (non-blocking) |
|
||||
|
||||
*Status: open · closed · open — below high threshold (non-blocking)*
|
||||
*Severity: critical > high > medium > low — only open threats at or above workflow.security_block_on count toward threats_open*
|
||||
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*
|
||||
|
||||
---
|
||||
|
||||
## Accepted Risks Log
|
||||
|
||||
| Risk ID | Threat Ref | Rationale | Accepted By | Date |
|
||||
|---------|------------|-----------|-------------|------|
|
||||
| AR-01 | T-16.1-05 | Relation search uses EXISTS subqueries; the route is admin-only and page size is limited to schema options. The COUNT query evaluates EXISTS over every matching row, not one page; acceptable at current data sizes (≈100 posts) | 16.1-01 plan (accept disposition) | 2026-10-10 |
|
||||
| AR-02 | T-16.1-10 | Deploy uses a narrow sudo scope (nginx -t/reload, supervisorctl) documented in DEPLOY.md | 16.1-02 plan (accept disposition) | 2026-10-10 |
|
||||
|
||||
---
|
||||
|
||||
## Security Audit Trail
|
||||
|
||||
| Audit Date | Threats Total | Closed | Open | Run By |
|
||||
|------------|---------------|--------|------|--------|
|
||||
| 2026-10-10 | 13 (register) + 2 audit warnings | 13 | 2 (below high, non-blocking) | msd-security-auditor (ASVS L1, boundary checks for T-16.1-01/02/WR-03) |
|
||||
|
||||
Audit notes: the auditor flagged W-3 (public layer gate) and W-4 (no record of the a3551f2 UAT deploy). W-3 is resolved by vue-journal-layer 5beb1c5; W-4 is recorded in T-16.1-02 above. The admin preview (`modules/cabana/field_markdown.go`) stays in goldmark safe mode.
|
||||
|
||||
---
|
||||
|
||||
## Sign-Off
|
||||
|
||||
- [x] All threats have a disposition (mitigate / accept / transfer)
|
||||
- [x] Accepted risks documented in Accepted Risks Log
|
||||
- [x] `threats_open: 0` confirmed
|
||||
- [ ] `status: verified` set in frontmatter
|
||||
|
||||
**Approval:** pending (T-16.1-13/14 fixes and the combined release)
|
||||
@@ -54,7 +54,7 @@ Pre-existing red, not caused by this phase: `scripts/check-phase10.sh --hygiene`
|
||||
| 16.1-02-03 | 02 | 2 | D-02, D-03 | T-16.1-09, T-16.1-10 | Read-only SQL and log checks; only posts 53 and 64 are re-saved, by a human | integration (server, read-only) | `ssh grzybyfunkcjonalne-uat "psql -X -d grzybyfunkcjonalne -tAc ..."` (raw-HTML marker count 0, post 64 footer present) | ✅ | ✅ green (plan 02: SQL check 0:1, no SQLSTATE 42703 since restart) |
|
||||
| 16.1-03-01 | 03 | 3 | D-01 | T-16.1-11 | Storage helpers survive disabled storage; logout clears list keys only | unit (vitest) | `npm --prefix admin test -- tests/state/listState.test.ts tests/state/useAuth.test.ts tests/list/ListView.test.ts tests/form/FormView.test.ts` | ✅ created (plan 03) | ✅ green |
|
||||
| 16.1-03-02 | 03 | 3 | D-02, D-03 | T-16.1-11, T-16.1-12 | Polymorphic relations and unknown columns refused (422); search value never inlined into SQL | unit (DryRun) + Postgres (Docker host) | `go test -count=1 -run '^(TestListRelationSQL\|TestEnabledLocalesDefaultFirst)$' -v ./modules/cabana` | ✅ extend files from plan 01 | ✅ green (pure); Postgres test on a Docker host: manual |
|
||||
| 16.1-03-03 | 03 | 3 | D-03 | T-16.1-02, T-16.1-11 | Every unsafe case returns an error now that the gate is the only defence | unit + journal harness (Docker host) | `go -C ../sm-journal-plugin test -count=1 -run '^(TestFormatHTMLKeepsRawHTML\|TestFormatHTMLRejectsUnsafeHTML)$' -v ./classes/` | ✅ extend | ✅ green (pure); harness re-save test on a Docker host: manual |
|
||||
| 16.1-03-03 | 03 | 3 | D-03 | T-16.1-02, T-16.1-11 | Every unsafe case is sanitized (bluemonday, post-review CR-01 fix), scheme-like text and raw HTML survive, CheckHTML refuses unsafe category HTML | unit + journal harness (Docker host) | `go -C ../sm-journal-plugin test -count=1 -run '^(TestFormatHTMLSanitizesUnsafeHTML\|TestFormatHTMLKeepsSchemeLikeText\|TestFormatHTMLKeepsRawHTML\|TestCheckHTML)$' -v ./classes/` (four top-level PASS lines) | ✅ extend | ✅ green (pure); harness re-save test on a Docker host: manual |
|
||||
|
||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||
|
||||
@@ -92,7 +92,7 @@ Pre-existing red, not caused by this phase: `scripts/check-phase10.sh --hygiene`
|
||||
|
||||
## Recorded runs (plan 03, 2026-10-09, machine without Docker)
|
||||
|
||||
- Quick Go commands: four top-level `--- PASS` lines for `TestListRelationSearchSmoke`, `TestListRelationSQL`, `TestEnabledLocalesDefaultFirst` and `TestEnabledLocalesDefaultFirstSmoke`; `go -C ../sm-journal-plugin test -count=1 ./classes/` ok, with two `--- PASS` lines for `TestFormatHTMLKeepsRawHTML` and `TestFormatHTMLRejectsUnsafeHTML`.
|
||||
- Quick Go commands: four top-level `--- PASS` lines for `TestListRelationSearchSmoke`, `TestListRelationSQL`, `TestEnabledLocalesDefaultFirst` and `TestEnabledLocalesDefaultFirstSmoke`; `go -C ../sm-journal-plugin test -count=1 ./classes/` ok, with four top-level `--- PASS` lines for `TestFormatHTMLSanitizesUnsafeHTML`, `TestFormatHTMLKeepsSchemeLikeText`, `TestFormatHTMLKeepsRawHTML` and `TestCheckHTML` (updated 2026-10-10 after the CR-01 fix replaced `TestFormatHTMLRejectsUnsafeHTML`).
|
||||
- Full local suite: `go vet ./...`, `go -C ../sm-journal-plugin vet ./...`, `TestDocsTree`, `npm --prefix admin run typecheck`, `npm --prefix admin test` (76 files, 1144 tests) and `scripts/check-admin-dist.sh` ("dist matches a fresh build") printed `phase-16.1-local-gate-ok`.
|
||||
- Docker-host suites: still pending (manual row above). `TestListRelationSearchPostgres` and `TestPostsAdminResaveKeepsHTML` fail here with "failed to create Docker provider", as designed (fail, not skip).
|
||||
- Manual UAT rows: the plan 02 executor confirmed posts 53 and 64 by read-only SQL. The list-state, form-locale and search checks were reported by the human before the re-saves reached UAT, so they stay open for `/msd-verify-work`.
|
||||
|
||||
Reference in New Issue
Block a user