Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.1 KiB
phase, slug, status, threats_open, asvs_level, created
| phase | slug | status | threats_open | asvs_level | created |
|---|---|---|---|---|---|
| 17 | ui-optimization-polish | verified | 0 | 1 | 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
- All threats have a disposition (mitigate / accept / transfer)
- Accepted risks documented in Accepted Risks Log
threats_open: 0confirmedstatus: verifiedset in frontmatter
Approval: verified 2026-06-18