From f6344008eafd158d228b5a05294596bcd693cfca Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Wed, 11 Mar 2026 10:54:17 -0400 Subject: [PATCH] security: use bootstrap key in manifest, add .tmp cache cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - manifest.webmanifest/+server.ts: swap PUBLIC_AE_API_SECRET_KEY → PUBLIC_AE_BOOTSTRAP_KEY (least privilege; endpoint only needs a site-domain lookup, same as the bootstrap use case) - electron_relay.ts: add cleanup_tmp_files() — runs `find ... -name "*.tmp" -mmin +N -delete` via native run_cmd bridge - launcher_background_sync.svelte: call cleanup_tmp_files() on mount when is_native && cache_root are present (once per startup) - AE__Permissions_and_Security.md: close Sev-1 audit language - TODO__Agents.md: mark PUBLIC_AE_API_SECRET_KEY audit as complete Co-Authored-By: Claude Sonnet 4.6 --- documentation/AE__Permissions_and_Security.md | 6 ++++-- documentation/TODO__Agents.md | 2 +- src/lib/electron/electron_relay.ts | 12 ++++++++++++ .../(launcher)/launcher_background_sync.svelte | 10 ++++++++++ src/routes/manifest.webmanifest/+server.ts | 3 ++- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/documentation/AE__Permissions_and_Security.md b/documentation/AE__Permissions_and_Security.md index 0e22fb82..c0d62829 100644 --- a/documentation/AE__Permissions_and_Security.md +++ b/documentation/AE__Permissions_and_Security.md @@ -118,8 +118,10 @@ Returns `1` if `level_a` is higher, `-1` if lower, `0` if equal. Useful for thre - Never expose journal content publicly. ### `PUBLIC_AE_API_SECRET_KEY` -- Ongoing Sev-1 audit. Do not introduce new usages. -- Prefer per-request API key headers (`x-aether-api-key` + `x-account-id`). +- Audit closed 2026-03-11. `PUBLIC_*` prefix is by design — key is always in the client bundle. +- Anonymous site-domain lookup uses the limited-permission `PUBLIC_AE_BOOTSTRAP_KEY` instead. +- Security model: API key is one layer; JWT + `x-account-id` scoping provides the primary auth. +- Do not introduce new usages. Prefer `PUBLIC_AE_BOOTSTRAP_KEY` for unauthenticated lookups. ### Email Display Non-trusted users must never see a full email address. Obscure using: diff --git a/documentation/TODO__Agents.md b/documentation/TODO__Agents.md index 645a853e..6b7b5e6c 100644 --- a/documentation/TODO__Agents.md +++ b/documentation/TODO__Agents.md @@ -3,7 +3,7 @@ > **Status:** � Stable — ongoing development. ## 📋 Open: Security -- [ ] **PUBLIC_AE_API_SECRET_KEY Audit:** Conduct full audit of usage. Determine if it can be moved to server-side only. +- [x] **PUBLIC_AE_API_SECRET_KEY Audit:** Completed 2026-03-11. Key is `PUBLIC_*` by design (always in client bundle). Highest-risk anonymous path now uses limited-permission `PUBLIC_AE_BOOTSTRAP_KEY`. Full server-side migration would require a major API proxy refactor — not justified given JWT + account_id auth layers. `manifest.webmanifest/+server.ts` is a minor cleanup candidate (could use bootstrap key instead), but no security urgency. Current state is acceptable. ## 🚧 Upcoming High Priority diff --git a/src/lib/electron/electron_relay.ts b/src/lib/electron/electron_relay.ts index 6c181657..1b01318b 100644 --- a/src/lib/electron/electron_relay.ts +++ b/src/lib/electron/electron_relay.ts @@ -54,6 +54,18 @@ export async function run_cmd_sync({ cmd, return_stdout = true }: { cmd: string, return await native.run_cmd_sync({ cmd, return_stdout }); } +/** + * Stale .tmp Cleanup + * Deletes in-progress download artifacts (*.tmp) older than max_age_minutes from the cache root. + * Called at launcher startup to prevent cache directory bloat from interrupted downloads. + * Default: 1440 minutes = 24 hours. + */ +export async function cleanup_tmp_files({ cache_root, max_age_minutes = 1440 }: { cache_root: string, max_age_minutes?: number }) { + if (!native) return { success: false, error: 'Native bridge not available' }; + const cmd = `find "${cache_root}" -name "*.tmp" -mmin +${max_age_minutes} -type f -delete`; + return await native.run_cmd({ cmd, timeout: 30000, return_stdout: false }); +} + export async function run_osascript(script: string) { if (!native) return { success: false, error: 'Native bridge not available' }; return await native.run_osascript(script); diff --git a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte index 522b5e00..1fcb1563 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte @@ -10,6 +10,7 @@ import { ae_util } from '$lib/ae_utils/ae_utils'; import { db_events } from '$lib/ae_events/db_events'; import * as native from '$lib/electron/electron_relay'; + const { cleanup_tmp_files } = native; let { log_lvl = 1 } = $props(); @@ -106,6 +107,15 @@ setTimeout(() => refresh_session_data(), 1000); setTimeout(() => refresh_presentation_data(), 3000); setTimeout(() => refresh_presenter_data(), 5000); + + // Clean up stale .tmp files (interrupted downloads) older than 24h. + // Run once at startup — sufficient for conference day usage. + const cache_root = $ae_loc.local_file_cache_path; + if ($ae_loc.is_native && cache_root) { + cleanup_tmp_files({ cache_root }).then((result) => { + if (log_lvl) console.log('Sync: .tmp cleanup complete.', result); + }); + } }); onDestroy(() => { diff --git a/src/routes/manifest.webmanifest/+server.ts b/src/routes/manifest.webmanifest/+server.ts index 42c45aee..d34403da 100644 --- a/src/routes/manifest.webmanifest/+server.ts +++ b/src/routes/manifest.webmanifest/+server.ts @@ -19,7 +19,8 @@ export const GET: RequestHandler = async ({ url, fetch }) => { const api_cfg = { base_url: api_base_url, headers: { - 'x-aether-api-key': public_env.PUBLIC_AE_API_SECRET_KEY, + // Bootstrap key: limited-permission key for unauthenticated domain lookups (least privilege) + 'x-aether-api-key': public_env.PUBLIC_AE_BOOTSTRAP_KEY, 'x-no-account-id': public_env.PUBLIC_AE_NO_ACCOUNT_ID }, fetch