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