| T-03-01 |
Tampering |
drizzle-kit push |
mitigate |
Human checkpoint + hand-applied additive DDL; runtime CMD is node dist/index.js (Dockerfile:46); db:push manual-only npm script |
closed |
| T-03-02 |
Info Disclosure |
calendar_outbox payload/etag |
accept |
Outbox rows are server-side only; never returned to the frontend |
closed |
| T-03-03 |
Tampering |
VEVENT field serialization |
mitigate |
ical.js ICAL.Component/Property/Recur for all serialization; no hand-rolled ICS (vevent.ts:89-148) |
closed |
| T-03-04 |
Spoofing |
etag forgery to bypass conflict |
mitigate |
etag sourced server-side from calendarEvents.etag; never read from request body (write.ts:62-86, outboxWorker.ts:264-279) |
closed |
| T-03-05 |
EoP |
write.ts called w/ another member's calendar |
accept |
Low-level primitive; ownership enforced at the route layer (T-03-06) |
closed |
| T-03-06 |
EoP |
write to another member's personal calendar |
mitigate |
Route lookup scoped and(eq(uid), or(eq(userId,current), eq(isShared,true))) + 403 on miss; CR-01 deterministic orderBy(...desc).limit(1) closes shared-account IDOR (events.ts:251-260,342-368,474-497) |
closed |
| T-03-07 |
Info Disclosure |
sync-status leaks another member's row |
mitigate |
WHERE and(eq(userId,current), eq(uid)) (events.ts:547) |
closed |
| T-03-08 |
Tampering |
XSS/oversized payload via title/location/description |
mitigate |
zod bounds (title 255, loc/desc 2000); IN-03 worker re-validates outbox payload + hard-fails invalid rows before VEVENT build (events.ts:100-109, outboxWorker.ts:70-82,231-234,323-326) |
closed |
| T-03-09 |
Tampering |
SQLi via uid/calendarUrl |
mitigate |
Drizzle parameterized queries incl. bound sql\`` params; no string interpolation (events.ts:181-198) |
closed |
| T-03-10 |
Spoofing |
client-supplied etag bypass |
mitigate |
etag read server-side at enqueue; client never supplies it (events.ts:407,432,507) |
closed |
| T-03-11a |
EoP |
writable-calendars surfaces another member's personal calendar |
mitigate |
WHERE or(eq(userId,current), eq(isShared,true)) (events.ts:600) |
closed |
| T-03-11b |
Repudiation |
silent last-write-wins on concurrent edit |
mitigate |
412→conflict:true→mark failed, no overwrite + targeted resync; CR-02 fresh-etag re-read joins calendars on (userId,url)+limit(1) (outboxWorker.ts:265-279,362-370,543-548) |
closed |
| T-03-12 |
DoS |
poison row retrying forever |
mitigate |
MAX_ATTEMPTS=5 + bounded backoff + dead-letter (outboxWorker.ts:40,46,578-587) |
closed |
| T-03-13 |
Info Disclosure |
logging decrypted app password |
mitigate |
Decrypt local-only; per-item catches log err.message only (outboxWorker.ts:127,174-177,608-611; poller.ts:70-74) |
closed |
| T-03-14 |
Tampering |
partial-failure data loss on edit-as-move |
mitigate |
create-before-delete + durable sibling-status gate + create-fail skips delete; CR-01 _preservedRrule re-applied via validated passthrough (outboxWorker.ts:336-353,462-524) |
closed |
| T-03-15 |
Tampering |
XSS via form title/location/description |
mitigate |
All fields plain-text JSX children; no dangerouslySetInnerHTML in apps/pwa/src (EventForm.tsx:557,591,729,752,798) |
closed |
| T-03-16 |
EoP |
client offers non-writable calendar in picker |
mitigate |
Picker only from authoritative fetchWritableCalendars; server re-enforces (client.ts:273-284, EventForm.tsx:182-187) |
closed |
| T-03-17 |
Tampering |
accidental/irreversible delete |
mitigate |
Mandatory two-tap dialog; no single-tap; no "don't ask again" (DeleteConfirmationDialog.tsx:78-81) |
closed |
| T-03-18 |
Repudiation |
silent data loss on failed delete sync |
mitigate |
failed/dead toast persists until dismiss; invalidates ['events'] so server refetch restores (SyncStateToast.tsx:59,201-222) |
closed |
| T-03-19 |
Info Disclosure |
another member's sync-status in toast |
mitigate |
Toast queries own lastSyncedUid; server scopes by member (SyncStateToast.tsx:41, events.ts:547) |
closed |
| T-03-20 |
Spoofing |
SW caches shell for /callback, breaks OIDC |
mitigate |
navigateFallbackDenylist: [/^\/callback/, /^\/api\//, /^\/health/] (vite.config.ts:16-20) |
closed |
| T-03-21 |
Tampering |
SW caches authenticated API responses |
mitigate |
runtimeCaching: [] (vite.config.ts:22) |
closed |
| T-03-22 |
Info Disclosure |
manifest/icons leak secrets |
accept |
Static public assets only; no secrets in manifest |
closed |
| T-03-23 |
Spoofing |
dev-auth bypass active in live deploy |
mitigate |
First guard NODE_ENV==='production'→no-op; prod mounts OIDC unconditionally; WR-04 moved worker startup into isMainModule() gate without altering middleware mount order (devBypass.ts:61, index.ts:38,46-48,104-114) |
closed |
| T-03-24 |
Info Disclosure |
OIDC redirect_uri mismatch leaks codes |
mitigate |
OIDC_AUTH_EXTERNAL_URL MANDATORY = public URL (middleware.ts:12, index.ts:44-45); deployment-config responsibility, no code gap |
closed |
| T-03-25 |
Tampering |
SW intercepts /callback in live build |
mitigate |
Same denylist verified vs production build (vite.config.ts:16-20); Gate 2 row 4 confirmed standalone login |
closed |