style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -19,36 +19,36 @@ closed: 2026-06-09
|
||||
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description | Data Crossing |
|
||||
|----------|-------------|---------------|
|
||||
| Browser → API (`/api/*`) | OIDC session cookie (Authelia) or dev-bypass; all list/item routes gated | List names, item text, sharing state |
|
||||
| API → MariaDB | Drizzle parameterized queries (mysql2) | List/item/share rows |
|
||||
| API → SSE clients | `GET /api/sse/lists` per-list-channel fan-out, scoped by `getAccessibleListIds` | Minimal `{type, listId, payload}` event envelopes |
|
||||
| npm registry → build | New deps (react-router, dnd-kit, fractional-indexing) installed during phase | Third-party source |
|
||||
| Boundary | Description | Data Crossing |
|
||||
| ------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------- |
|
||||
| Browser → API (`/api/*`) | OIDC session cookie (Authelia) or dev-bypass; all list/item routes gated | List names, item text, sharing state |
|
||||
| API → MariaDB | Drizzle parameterized queries (mysql2) | List/item/share rows |
|
||||
| API → SSE clients | `GET /api/sse/lists` per-list-channel fan-out, scoped by `getAccessibleListIds` | Minimal `{type, listId, payload}` event envelopes |
|
||||
| npm registry → build | New deps (react-router, dnd-kit, fractional-indexing) installed during phase | Third-party source |
|
||||
|
||||
---
|
||||
|
||||
## Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation | Status |
|
||||
|-----------|----------|-----------|-------------|------------|--------|
|
||||
| T-04-01 | Tampering | drizzle-kit push truncating populated tables | mitigate | generate+migrate only; `0001_lists_schema.sql` and `0002_yielding_mattie_franklin.sql` are additive (CREATE TABLE / ALTER TABLE MODIFY), no DROP/TRUNCATE | closed |
|
||||
| T-04-01b | Spoofing/AuthZ | unauthenticated SSE subscription | mitigate | `resolveUserId → 401`; endpoint behind OIDC middleware; client `withCredentials` | closed |
|
||||
| T-04-02 | Information Disclosure | scoped fan-out leak (D-04) — load-bearing | mitigate | per-list channel `list:${listId}` + `getAccessibleListIds`; GET /api/lists scoped | closed |
|
||||
| T-04-03 | Information Disclosure | getAccessibleListIds over-returning ids | mitigate | scoped to owner_id OR list_shares.userId; deduped via Set | closed |
|
||||
| T-04-04 | Denial of Service | EventEmitter max-listeners | accept | `setMaxListeners(200)` headroom | closed (accepted) |
|
||||
| T-04-05 | Elevation of Privilege | accessing/mutating another member's list via direct id | mitigate | `checkListAccess` on every list + item handler; DELETE list owner-only; 403 otherwise; `isShared` reconciliation now owner-gated at `lists.ts:336` (plan 04-07) | closed |
|
||||
| T-04-06 | Tampering | XSS via list name / item text | mitigate | plain-text JSX children only; no `dangerouslySetInnerHTML` in ListCard/ItemRow | closed |
|
||||
| T-04-07 | Tampering | overposting on PATCH | mitigate | zod `patchListSchema` (name/isShared) + `patchItemSchema` exactly-one-of(checked/text/position) | closed |
|
||||
| T-04-08 | Elevation of Privilege | self-adding to / manipulating list_shares | mitigate | Owner-only guard at `lists.ts:336`: `if (patch.isShared !== undefined && !access.isOwner) return 403`. A non-owner sharee can no longer delete or insert `list_shares` via PATCH `{ isShared }`. Verified by two negative tests (`lists.test.ts:452`, `lists.test.ts:477`): sharee → 403 + `list_shares` unchanged. (plan 04-07) | closed |
|
||||
| T-04-09 | Tampering | resurrecting a deleted item via in-flight edit (D-09) | mitigate | DELETE final; PATCH fetches row first, 404 if missing; no upsert path | closed |
|
||||
| T-04-10 | Denial of Service | pathological zipper inserts growing rank | accept | VARCHAR(255) headroom; fractional-indexing graceful degradation | closed (accepted) |
|
||||
| T-04-11 | Denial of Service | EventSource reconnect storm | mitigate | `es.close()` before setTimeout; bounded backoff; give up after `MAX_ATTEMPTS=6` | closed |
|
||||
| T-04-12 | Information Disclosure | over-broad SSE event payload | mitigate | payload is `{type, listId, payload:{id,...}}` minimal; per-channel scoped | closed |
|
||||
| T-04-SC | Tampering | npm supply chain (react-router, dnd-kit, fractional-indexing) | mitigate | RESEARCH legitimacy audit + blocking human checkpoint (04-01 Task 1) before install | closed |
|
||||
| Threat ID | Category | Component | Disposition | Mitigation | Status |
|
||||
| --------- | ---------------------- | ------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
|
||||
| T-04-01 | Tampering | drizzle-kit push truncating populated tables | mitigate | generate+migrate only; `0001_lists_schema.sql` and `0002_yielding_mattie_franklin.sql` are additive (CREATE TABLE / ALTER TABLE MODIFY), no DROP/TRUNCATE | closed |
|
||||
| T-04-01b | Spoofing/AuthZ | unauthenticated SSE subscription | mitigate | `resolveUserId → 401`; endpoint behind OIDC middleware; client `withCredentials` | closed |
|
||||
| T-04-02 | Information Disclosure | scoped fan-out leak (D-04) — load-bearing | mitigate | per-list channel `list:${listId}` + `getAccessibleListIds`; GET /api/lists scoped | closed |
|
||||
| T-04-03 | Information Disclosure | getAccessibleListIds over-returning ids | mitigate | scoped to owner_id OR list_shares.userId; deduped via Set | closed |
|
||||
| T-04-04 | Denial of Service | EventEmitter max-listeners | accept | `setMaxListeners(200)` headroom | closed (accepted) |
|
||||
| T-04-05 | Elevation of Privilege | accessing/mutating another member's list via direct id | mitigate | `checkListAccess` on every list + item handler; DELETE list owner-only; 403 otherwise; `isShared` reconciliation now owner-gated at `lists.ts:336` (plan 04-07) | closed |
|
||||
| T-04-06 | Tampering | XSS via list name / item text | mitigate | plain-text JSX children only; no `dangerouslySetInnerHTML` in ListCard/ItemRow | closed |
|
||||
| T-04-07 | Tampering | overposting on PATCH | mitigate | zod `patchListSchema` (name/isShared) + `patchItemSchema` exactly-one-of(checked/text/position) | closed |
|
||||
| T-04-08 | Elevation of Privilege | self-adding to / manipulating list_shares | mitigate | Owner-only guard at `lists.ts:336`: `if (patch.isShared !== undefined && !access.isOwner) return 403`. A non-owner sharee can no longer delete or insert `list_shares` via PATCH `{ isShared }`. Verified by two negative tests (`lists.test.ts:452`, `lists.test.ts:477`): sharee → 403 + `list_shares` unchanged. (plan 04-07) | closed |
|
||||
| T-04-09 | Tampering | resurrecting a deleted item via in-flight edit (D-09) | mitigate | DELETE final; PATCH fetches row first, 404 if missing; no upsert path | closed |
|
||||
| T-04-10 | Denial of Service | pathological zipper inserts growing rank | accept | VARCHAR(255) headroom; fractional-indexing graceful degradation | closed (accepted) |
|
||||
| T-04-11 | Denial of Service | EventSource reconnect storm | mitigate | `es.close()` before setTimeout; bounded backoff; give up after `MAX_ATTEMPTS=6` | closed |
|
||||
| T-04-12 | Information Disclosure | over-broad SSE event payload | mitigate | payload is `{type, listId, payload:{id,...}}` minimal; per-channel scoped | closed |
|
||||
| T-04-SC | Tampering | npm supply chain (react-router, dnd-kit, fractional-indexing) | mitigate | RESEARCH legitimacy audit + blocking human checkpoint (04-01 Task 1) before install | closed |
|
||||
|
||||
*Status: open · closed*
|
||||
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*
|
||||
_Status: open · closed_
|
||||
_Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)_
|
||||
|
||||
---
|
||||
|
||||
@@ -65,11 +65,12 @@ The owner-only guard was added immediately after the `checkListAccess` block and
|
||||
// T-04-08 / T-04-05: owner-only guard for isShared mutations.
|
||||
// A sharee may rename a list (patch.name) but must never mutate list_shares.
|
||||
if (patch.isShared !== undefined && !access.isOwner) {
|
||||
return c.json({ error: 'Only the list owner can change sharing settings' }, 403)
|
||||
return c.json({ error: 'Only the list owner can change sharing settings' }, 403);
|
||||
}
|
||||
```
|
||||
|
||||
**Test coverage (WR-04 now covered):**
|
||||
|
||||
- `lists.test.ts:452` — sharee sends `{ isShared: false }` → 403; `list_shares` row still exists (length 1). Proves the `db.delete(listShares)` path is unreachable for non-owners.
|
||||
- `lists.test.ts:477` — sharee sends `{ isShared: true }` → 403; share count unchanged. Proves the `db.insert(listShares)` path is unreachable for non-owners.
|
||||
- Pre-existing owner-toggle tests (false→true, true→false) and sharee-rename test continue to pass.
|
||||
@@ -105,21 +106,21 @@ any threat disposition under `block_on: high`.
|
||||
|
||||
## Accepted Risks Log
|
||||
|
||||
| Risk ID | Threat Ref | Rationale | Accepted By | Date |
|
||||
|---------|------------|-----------|-------------|------|
|
||||
| AR-04-04 | T-04-04 | Single-process household app; `setMaxListeners(200)` (100 members × 2 devices) is generous headroom; Redis fan-out deferred (D-18) | Plan (04-02) | 2026-06-09 |
|
||||
| AR-04-10 | T-04-10 | VARCHAR(255) rank headroom; fractional-indexing degrades gracefully; rebalance via generateNKeysBetween available if ever needed (out of scope) | Plan (04-05) | 2026-06-09 |
|
||||
| Risk ID | Threat Ref | Rationale | Accepted By | Date |
|
||||
| -------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------- |
|
||||
| AR-04-04 | T-04-04 | Single-process household app; `setMaxListeners(200)` (100 members × 2 devices) is generous headroom; Redis fan-out deferred (D-18) | Plan (04-02) | 2026-06-09 |
|
||||
| AR-04-10 | T-04-10 | VARCHAR(255) rank headroom; fractional-indexing degrades gracefully; rebalance via generateNKeysBetween available if ever needed (out of scope) | Plan (04-05) | 2026-06-09 |
|
||||
|
||||
*Accepted risks do not resurface in future audit runs.*
|
||||
_Accepted risks do not resurface in future audit runs._
|
||||
|
||||
---
|
||||
|
||||
## Security Audit Trail
|
||||
|
||||
| Audit Date | Threats Total | Closed | Open | Run By |
|
||||
|------------|---------------|--------|------|--------|
|
||||
| 2026-06-09 | 14 | 13 | 1 | gsd-security-auditor |
|
||||
| 2026-06-09 | 14 | 14 | 0 | gsd-verifier (re-verification after plan 04-07) |
|
||||
| Audit Date | Threats Total | Closed | Open | Run By |
|
||||
| ---------- | ------------- | ------ | ---- | ----------------------------------------------- |
|
||||
| 2026-06-09 | 14 | 13 | 1 | gsd-security-auditor |
|
||||
| 2026-06-09 | 14 | 14 | 0 | gsd-verifier (re-verification after plan 04-07) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user