gsd-security-auditor verified all 13 plan-time STRIDE threats against the implementation: 8 mitigate confirmed in code (file:line evidence), 5 accept documented, 4 supply-chain checks (zero new deps). threats_open: 0. ASVS L1, block_on high — no high-severity gaps. Post-review fixes (WR-01 blank-TZ guard, WR-02 INSERT IGNORE) verified in code; D-07 boundary confirmed via git diff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.3 KiB
6.3 KiB
phase, audited, status, asvs_level, block_on, register_authored_at_plan_time, threats_total, threats_closed, threats_open, threats_accepted, supply_chain_checks
| phase | audited | status | asvs_level | block_on | register_authored_at_plan_time | threats_total | threats_closed | threats_open | threats_accepted | supply_chain_checks |
|---|---|---|---|---|---|---|---|---|---|---|
| 18-auto-timezone-detection-and-ability-to-change-timezone | 2026-06-15 | secured | 1 | high | true | 13 | 13 | 0 | 4 | 4 |
Phase 18 — Auto Timezone Detection & Change Timezone: Security Audit
Audited: 2026-06-15 ASVS Level: 1 block_on: high Compared against: origin/main..HEAD Status: SECURED — 13/13 threats closed (8 mitigate verified, 5 accept documented), 4× T-18-SC supply-chain verified
This audit verifies each declared threat mitigation EXISTS in the implemented code. It does not scan for new vulnerabilities. Implementation files were not modified.
Threat Verification
| Threat ID | Category | Disposition | Status | Evidence |
|---|---|---|---|---|
| T-18-01 | Tampering | mitigate | CLOSED | householdTimezone.ts:25-35 reads only; never returns an unvalidated forwarded write. resolveHouseholdTimezone (:49-61) guarantees non-empty fallback (trim guard). All writes go through Plan 02 IANA-validated path. |
| T-18-02 | DoS | accept | CLOSED | Accepted risk logged below. Single PK lookup per scheduler tick (reminderScheduler.ts:250). |
| T-18-03 | Elevation of Privilege | mitigate | CLOSED | admin.ts:42 adminRouter.use('*', requireAdmin) is the FIRST router statement, before all routes. GET (:200), PUT (:224), POST seed (:251) all appended after it → inherit the guard. 403 tests exist (admin.test.ts:603-625, 711-720). |
| T-18-04 | Tampering | mitigate | CLOSED | admin.ts:56-62 timezoneSchema uses .refine(isValidIanaTimezone); PUT (:224) and seed (:251) both bind it via zValidator('json', timezoneSchema). isValidIanaTimezone (householdTimezone.ts:70-77) is eval-free try/catch on Intl.DateTimeFormat. Invalid → 400 before any DB write. |
| T-18-05 | Tampering | mitigate | CLOSED | Seed (admin.ts:262-266) uses INSERT IGNORE; an existing row is silently ignored (value preserved, D-03 no-overwrite). seeded derived from affectedRows === 1. Cannot overwrite an existing value. |
| T-18-06 | Information Disclosure | accept | CLOSED | Accepted risk logged below. Grep confirms no console.log of request bodies in admin.ts (only a T-10-10 comment reference). Timezone is non-sensitive. |
| T-18-07 | Injection | mitigate | CLOSED | PUT upsert (admin.ts:227-230) uses Drizzle .insert().onDuplicateKeyUpdate — parameterized, key is hard-coded literal 'household_timezone'. Seed (:262-263) uses a sql template where INSERT IGNORE is a literal keyword, column identifiers via sql.identifier, and ${timezone} is a bound parameter (not string-concatenated) and IANA-validated upstream. |
| T-18-08 | Tampering | mitigate | CLOSED | Broker sites consume getHouseholdTimezone(db) only (reminderScheduler.ts:250, outboxWorker.ts:391). resolveHouseholdTimezone (householdTimezone.ts:49-61) trims stored value and process.env.TZ; empty/blank falls through to a valid Intl zone — fallback can never return ''/invalid. |
| T-18-09 | Tampering (regression) | mitigate | CLOSED | git diff --name-only origin/main..HEAD excludes eventDateTime.ts and hydrateEvents.ts (D-07 boundary intact). No timed-write/display path touched. |
| T-18-10 | DoS | accept | CLOSED | Accepted risk logged below. Per-drain-cycle memoization (outboxWorker.ts:385-395, created :774) shares one app_config read across both all-day branches (:527, :634). |
| T-18-11 | Elevation of Privilege | accept | CLOSED | Accepted risk logged below. Client gate is UX-only; server requireAdmin (admin.ts:42) is the real control. Client (client.ts:498) documents server enforcement. |
| T-18-12 | Tampering | mitigate | CLOSED | Client setAdminTimezone (client.ts:501-511) sends raw input to server; server timezoneSchema.refine (admin.ts:56-62) is authoritative (400 on invalid). Free-text input (AdminPage.tsx:387-406) is not the security boundary. |
| T-18-13 | Information Disclosure | mitigate | CLOSED | Timezone rendered as plain-text JSX (AdminPage.tsx:431 Use detected: {detectedTz}) and as controlled input value={effectiveTimezoneInput} (:390). Grep confirms NO dangerouslySetInnerHTML in AdminPage.tsx. |
| T-18-SC (×4, plans 01-04) | Supply chain | mitigate | CLOSED | git diff origin/main..HEAD against all package.json / pnpm-lock.yaml returns EMPTY — zero new dependencies. PWA picker uses built-in Intl. |
Accepted Risks Log
- T-18-02 (DoS — DB read per scheduler tick): Single primary-key lookup on
app_configper 60s scheduler interval. Negligible load; read-per-run chosen so timezone changes propagate within one tick without a worker restart. Accepted. - T-18-06 (Information Disclosure — log/echo of submitted timezone): Timezone identifiers are non-sensitive (not credentials or PII). No
noEchoHookrequired; verified no body logging in handlers. Accepted. - T-18-10 (DoS — extra DB read at 3 call sites): Mitigated in practice by per-drain-cycle memoization; PK lookups on a 60s interval are negligible. Accepted.
- T-18-11 (Elevation of Privilege — client renders admin UI from isAdmin flag): The client
isAdmingate is a UX convenience only. A forged request still hits server-siderequireAdmin→ 403 (T-18-03). The UI gate is not relied upon as a security control. Accepted.
Unregistered Flags
None. All four plan SUMMARY ## Threat Flags sections declare "No new threat surface beyond the plan's threat model." No new endpoints, auth paths, file-access patterns, or schema changes appeared during implementation that lack a mapped threat ID.
Notes
- Post-review fixes (18-REVIEW-FIX.md: WR-01, WR-02, IN-01/02/03) were verified in code, not accepted on documentation alone:
- WR-01 empty/blank TZ guard present at
householdTimezone.ts:50-58(relevant to T-18-08). - WR-02
INSERT IGNORE+ affectedRows-derivedseededpresent atadmin.ts:262-266(relevant to T-18-05).
- WR-01 empty/blank TZ guard present at
- D-07 boundary independently confirmed via
git diff --name-only. - Zero-dependency claim independently confirmed via empty manifest/lockfile diff.
Security Audit 2026-06-15
| Metric | Count |
|---|---|
| Threats found | 13 |
| Closed | 13 |
| Open | 0 |
| Accepted risks | 4 |
| Supply-chain checks | 4 |