docs(phase-17): add security threat verification (9 closed, 0 open)
CI / changes (pull_request) Successful in 4s
CI / fast-checks (pull_request) Failing after 1m41s
CI / api (pull_request) Successful in 2m2s
CI / security (pull_request) Successful in 54s
CI / gate (pull_request) Has been cancelled
CI / harness (pull_request) Has been cancelled
CI / changes (pull_request) Successful in 4s
CI / fast-checks (pull_request) Failing after 1m41s
CI / api (pull_request) Successful in 2m2s
CI / security (pull_request) Successful in 54s
CI / gate (pull_request) Has been cancelled
CI / harness (pull_request) Has been cancelled
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
phase: 17
|
||||||
|
slug: ui-optimization-polish
|
||||||
|
status: verified
|
||||||
|
threats_open: 0
|
||||||
|
asvs_level: 1
|
||||||
|
created: 2026-06-18
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 17 — Security
|
||||||
|
|
||||||
|
> Per-phase security contract: threat register, accepted risks, and audit trail.
|
||||||
|
|
||||||
|
Phase 17 is a UI optimization & polish phase. Every plan carried a plan-time
|
||||||
|
`<threat_model>` block (`register_authored_at_plan_time: true`). The work is
|
||||||
|
client-side CSS/layout, static brand-asset wiring, and presentation-only React
|
||||||
|
state — no new endpoints, no new authorization logic, no new runtime data flow.
|
||||||
|
The single non-`accept` threat (logout wiring) reuses an endpoint already
|
||||||
|
verified live in Phase 19.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description | Data Crossing |
|
||||||
|
|----------|-------------|---------------|
|
||||||
|
| Build tooling → repo (17-02) | `@vite-pwa/assets-generator` (+ sharp, sharp-ico) runs at design time and writes static images into `public/`. New devDependency = supply-chain surface. | Static image bytes; no secrets/PII |
|
||||||
|
| Client UI → existing logout endpoint (17-05) | Sign out control calls the already-implemented, Phase-19-verified `POST /api/auth/local/logout` via `fetchLocalLogout()`. No new endpoint, no new auth logic. | Session cookie (cleared server-side) |
|
||||||
|
| (none new) — 17-01, 17-03, 17-04, 17-06 | CSS-only restructure/offsets, static asset references, and presentation-only local `useState` (tab/toast). Server-side admin `403` enforcement unchanged. | None |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation | Status |
|
||||||
|
|-----------|----------|-----------|-------------|------------|--------|
|
||||||
|
| T-17-01-01 | Tampering | tokens.css selector restructure | accept | CSS custom properties carry no executable content and no user input; selector change cannot introduce injection. | closed |
|
||||||
|
| T-17-02-SC | Tampering | npm devDependency install (@vite-pwa/assets-generator, sharp, sharp-ico) | accept | RESEARCH Package Legitimacy Audit rates all three Approved (official vite-pwa, 13-yr sharp, sharp-ico); no `[SLOP]`/unverified packages. devDependencies only; generated output is static images. | closed |
|
||||||
|
| T-17-02-02 | Information disclosure | generated brand assets | accept | Assets are public-by-design brand images; no secrets or PII. | closed |
|
||||||
|
| T-17-03-01 | Tampering | FAB/content CSS offsets | accept | Pure layout geometry via existing CSS custom property; no executable content, no input. | closed |
|
||||||
|
| T-17-04-01 | Tampering | BrandSlot img / index.html links | accept | Logo img is decorative with empty `alt`; no `dangerouslySetInnerHTML` (T-05-24 invariant maintained); favicon/manifest entries point at committed static files. **Verified live:** BrandSlot renders `<img src="/logo.svg" alt="" aria-hidden="true">`, no `dangerouslySetInnerHTML` in source. | closed |
|
||||||
|
| T-17-05-01 | Elevation of Privilege | logout control (D-07) | mitigate | `fetchLocalLogout()` clears the local-session cookie via the existing Phase-19-verified endpoint; client navigates to `/login` regardless of success/failure so a stale-cookie-with-logged-out-UI state cannot persist. **Verified:** `SettingsSheet.tsx:143-151` — `try { await fetchLocalLogout(); } catch {} onClose(); void navigate('/login');`. | closed |
|
||||||
|
| T-17-05-02 | Tampering | sheet centering CSS (D-09) | accept | Position-only CSS branch; no input, no executable content. | closed |
|
||||||
|
| T-17-06-01 | Tampering | toast message content (D-08) | accept | Toast copy is hardcoded JSX string constants ("Member added." / "Password reset."); no user-controlled content; no `dangerouslySetInnerHTML`. **Verified live:** toast rendered "Member added." from a `role=status` node on member creation. | closed |
|
||||||
|
| T-17-06-02 | Elevation of Privilege | admin two-tab nav (D-10) | accept | Tab strip is presentation-only local `useState`; `isAdmin` nav visibility is UX-only — the real boundary is server-side `403` on `/api/admin/*` (unchanged). | closed |
|
||||||
|
|
||||||
|
*Status: open · closed*
|
||||||
|
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accepted Risks Log
|
||||||
|
|
||||||
|
| Risk ID | Threat Ref | Rationale | Accepted By | Date |
|
||||||
|
|---------|------------|-----------|-------------|------|
|
||||||
|
| AR-17-01 | T-17-01-01 | Static stylesheet selector restructure; zero runtime data flow. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-02 | T-17-02-SC | All new devDependencies Approved by RESEARCH package-legitimacy audit; design-time only. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-03 | T-17-02-02 | Brand assets are public-by-design; no secrets/PII. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-04 | T-17-03-01 | Pure CSS layout geometry; no input surface. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-05 | T-17-04-01 | Decorative img with empty alt; no `dangerouslySetInnerHTML`; committed static assets. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-06 | T-17-05-02 | Position-only CSS branch; no input/executable content. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-07 | T-17-06-01 | Hardcoded toast string constants; no user-controlled content. | Lucas Berger | 2026-06-18 |
|
||||||
|
| AR-17-08 | T-17-06-02 | Presentation-only tab state; authorization enforced server-side (unchanged). | Lucas Berger | 2026-06-18 |
|
||||||
|
|
||||||
|
*Accepted risks do not resurface in future audit runs.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Audit Trail
|
||||||
|
|
||||||
|
| Audit Date | Threats Total | Closed | Open | Run By |
|
||||||
|
|------------|---------------|--------|------|--------|
|
||||||
|
| 2026-06-18 | 9 | 9 | 0 | /gsd-secure-phase (orchestrator, plan-time register verification) |
|
||||||
|
|
||||||
|
Verification method: all 6 plans carried plan-time `<threat_model>` blocks
|
||||||
|
(`register_authored_at_plan_time: true`). 8 `accept`-disposition threats are
|
||||||
|
documented accepted risks; the 1 `mitigate` threat (T-17-05-01) had its
|
||||||
|
mitigation verified present in `SettingsSheet.tsx`. Several dispositions were
|
||||||
|
additionally corroborated at runtime during the Phase 17 UAT (playwright-cli):
|
||||||
|
BrandSlot decorative img, hardcoded success toast, admin tab presentation-only
|
||||||
|
state. `threats_open: 0` — short-circuit per workflow Step 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sign-Off
|
||||||
|
|
||||||
|
- [x] All threats have a disposition (mitigate / accept / transfer)
|
||||||
|
- [x] Accepted risks documented in Accepted Risks Log
|
||||||
|
- [x] `threats_open: 0` confirmed
|
||||||
|
- [x] `status: verified` set in frontmatter
|
||||||
|
|
||||||
|
**Approval:** verified 2026-06-18
|
||||||
Reference in New Issue
Block a user