From de07fa0e0e2d4c4db35cd06d12b3395144df5632 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 28 Apr 2026 16:10:17 -0400 Subject: [PATCH] docs: capture IDAA IDB audit results and layout security model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TODO__Agents.md: mark IDAA IDB caching item complete (audited 2026-04-28); all protection layers confirmed in place, no code changes needed - GUIDE__SvelteKit2_Svelte5_DexieJS.md: add "SvelteKit Layout Hierarchy: Security and Execution Order" section explaining execution order, auth-gate consequences, pre-gate risks in +page.ts/+layout.ts, and the reactivity-guard vs auth-guard distinction for IDAA $effect blocks - BOOTSTRAP__AI_Agent_Quickstart.md: add Mistake #7 — treating $effect blocks as auth bypass risks vs understanding the real layout hierarchy Co-Authored-By: Claude Sonnet 4.6 --- .../BOOTSTRAP__AI_Agent_Quickstart.md | 10 +++ .../GUIDE__SvelteKit2_Svelte5_DexieJS.md | 73 +++++++++++++++++++ documentation/TODO__Agents.md | 26 +++---- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/documentation/BOOTSTRAP__AI_Agent_Quickstart.md b/documentation/BOOTSTRAP__AI_Agent_Quickstart.md index 789b417e..ebb3f8d8 100644 --- a/documentation/BOOTSTRAP__AI_Agent_Quickstart.md +++ b/documentation/BOOTSTRAP__AI_Agent_Quickstart.md @@ -253,6 +253,16 @@ These are real incidents — know them before you start. 6. **Deleting files with `rm`** — always move to `~/tmp/agents_trash`. A deleted file may contain context that's not recoverable from git if it was gitignored. +7. **Treating `$effect` blocks as auth bypass risks** — a `$effect` inside a child + component cannot bypass a parent `+layout.svelte` auth gate. Children only mount if + the parent calls `{@render children?.()}`. Adding redundant auth guards to `$effect` + blocks that can only run after the parent gate already passed is unnecessary — and + misleads future readers into thinking the parent gate is not sufficient on its own. + The **real** pre-gate risk is `+page.ts` / `+layout.ts`: universal load functions run + before any layout mounts and also fire during SvelteKit link prefetch. Keep those files + clean of data loads in private modules. See `GUIDE__SvelteKit2_Svelte5_DexieJS.md` → + "SvelteKit Layout Hierarchy: Security and Execution Order" for the full explanation. + --- ## 8. Source Layout (Quick Reference) diff --git a/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md b/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md index d7aa3828..c811627c 100644 --- a/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md +++ b/documentation/GUIDE__SvelteKit2_Svelte5_DexieJS.md @@ -233,6 +233,79 @@ export function createLiveQueryStore(query: () => T | Promise) { The `createLiveQueryStore` function creates a readable store that automatically updates whenever the data in the `friends` table changes. The `$friends` variable in the component will always contain the latest data from the database. +## SvelteKit Layout Hierarchy: Security and Execution Order + +Understanding *when* SvelteKit code runs is critical for private-data modules like IDAA. + +### Execution order on any navigation + +```text +1. +layout.ts / +page.ts ← run FIRST — before any component mounts + also fired by SvelteKit link prefetch (on hover) +2. Parent +layout.svelte mounts → its $effect blocks run +3. Child +layout.svelte mounts → only if parent called {#render children?.()} +4. +page.svelte mounts → only if every parent in the chain rendered children +5. $effect blocks in all of the above run after mount +``` + +### The auth-gate consequence + +A `{:else if authenticated} {@render children?.()}` block in a `+layout.svelte` +controls whether **everything below it** ever mounts. If the gate blocks rendering, +no child layout or page component instantiates — their `$effect` blocks, event +handlers, and liveQuery closures never run. + +```svelte + +{:else if $ae_loc.trusted_access || $idaa_loc.novi_verified} + {@render children?.()} ← children only mount if this branch runs +{:else} +

Access Denied

← children never mount; their $effects never run +{/if} +``` + +**`$effect` blocks inside a child component cannot bypass a parent layout auth gate.** +They are already inside the gate. Adding redundant auth guards to `$effect` blocks +that only run after a parent has already verified access is unnecessary — and misleads +future readers into thinking the parent gate alone is not sufficient. + +### Where the actual pre-gate risk lives: `+page.ts` / `+layout.ts` + +Universal load functions run *before* components mount and *before* layout effects +execute. They also fire during SvelteKit link prefetch — triggered by the user +hovering a link, even if they never navigate. This makes them unsafe for private data: + +```text +User hovers an /idaa/ link → + SvelteKit prefetch fires → + +page.ts runs (no layout has mounted yet, no auth gate has run) → + API call / IDB write happens for an unauthenticated user +``` + +**Rule for private modules (IDAA, Journals):** `+page.ts` and `+layout.ts` files must +not call any data load functions that write to IDB. Move all data loading to `$effect` +blocks in the corresponding `+page.svelte`, gated inside the auth-checked layout render. +The comments in every `+page.ts` under `src/routes/idaa/(idaa)/` explain this pattern. + +### The `$effect` auth guards in IDAA `+page.svelte` files + +These ARE still useful — but for a different reason than layout bypass: + +```ts +// In bb/+page.svelte +$effect(() => { + if (!$idaa_loc.novi_verified && !$ae_loc.trusted_access) return; + posts_func.load_ae_obj_li__post(...) +}); +``` + +Because `$ae_loc` is a Svelte 4 coarse-grained store, any unrelated write to it +(iframe height, SWR reload) re-triggers this `$effect`. The guard prevents a spurious +API call if `$idaa_loc.novi_verified` has been cleared between re-runs (e.g. TTL +expiry mid-session). It is a reactivity guard, not a layout-bypass guard. + +--- + ## Page Load Strategies (Avoiding the "Waterfall") When loading data for a primary page view (e.g., viewing a specific Journal, Session, or Person), you must choose a synchronization strategy to ensure the UI renders correctly on the first load. diff --git a/documentation/TODO__Agents.md b/documentation/TODO__Agents.md index 5913274d..3d08526c 100644 --- a/documentation/TODO__Agents.md +++ b/documentation/TODO__Agents.md @@ -118,22 +118,16 @@ suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a pac Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list. Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs. -- [ ] **[IDAA] Do not cache IDAA data in IDB when access is denied (2026-04-19)** - If a user is not authenticated or receives an access-denied response, the frontend must - **not** pre-fetch or cache any IDAA content (Posts, Archives, Events/Recovery Meetings) - into the local browser IndexedDB (Dexie). Storing private IDAA data in IDB on an - unauthenticated device is a privacy violation — the data would persist in the browser - even after the session ends, accessible to the next person who opens DevTools. - - **Fix pattern:** - - All IDAA SWR load functions (`load_ae_obj_li__*` in `ae_idaa/`, `ae_posts/`, and the - IDAA-specific event queries) must gate on a successful auth check before calling - `_refresh_*_background` or writing to IDB. - - If the API returns a 401/403, do not write to Dexie — return/throw early. - - On explicit logout or Novi auth invalidation, purge IDAA tables from IDB - (`db_idaa`, `db_posts`, and any IDAA event records in `db_events`). - - Audit all `+page.ts` / `+layout.ts` files under `src/routes/idaa/` to confirm - no eager prefetch runs before the auth guard resolves. +- [x] **[IDAA] Do not cache IDAA data in IDB when access is denied (2026-04-19, audited 2026-04-28)** + Full audit confirmed all protection layers are in place. No code changes required. + - All `+page.ts` / `+layout.ts` under `src/routes/idaa/` are clean — no SWR loads run before auth resolves. + - All `$effect` SWR calls in IDAA `+page.svelte` files are gated on `$idaa_loc.novi_verified || $ae_loc.trusted_access`. + - `(idaa)/+layout.svelte` purges `db_posts`, `db_archives`, `db_events` on auth failure, no-UUID/no-session, and inconsistent state. + - `sign_out()` calls `indexedDB.deleteDatabase()` on all IDAA databases. + - API 401/403 responses fail-fast in `api_get_object.ts` (throw before any IDB write). + - `idaa_trig` is in-memory `writable()` only — cannot carry stale trigger state across sessions. + - `$effect` auth guards in IDAA page components are reactivity guards (prevent spurious SWR calls on coarse `$ae_loc` writes), NOT auth-bypass guards. SvelteKit layout hierarchy already prevents child components from mounting when `(idaa)/+layout.svelte` blocks rendering. + - Doc: SvelteKit layout hierarchy security model captured in `GUIDE__SvelteKit2_Svelte5_DexieJS.md` and `BOOTSTRAP__AI_Agent_Quickstart.md` (Mistake #7). - [ ] **[IDAA] Make `contact_li_json_ext` searchable — Recovery Meeting contact search (2026-04-08)** Members cannot search for meetings by contact name or email. `contact_li_json` data is not