diff --git a/.planning/phases/17-ui-optimization-polish/17-SECURITY.md b/.planning/phases/17-ui-optimization-polish/17-SECURITY.md new file mode 100644 index 0000000..eefcfe0 --- /dev/null +++ b/.planning/phases/17-ui-optimization-polish/17-SECURITY.md @@ -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 +`` 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 ``, 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 `` 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