From 09e6a8b9dfadcb00ce5f53f6a77c389b3efc5600 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 22 Apr 2026 18:13:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(crypto):=20Phase=202e=20=E2=80=94=20flip?= =?UTF-8?q?=20encryption=20on=20for=20tags/scenes/missions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns on at-rest encryption for the four tables staged in Phase 2a. New writes now encrypt the user-typed fields; future code paths read via decryptRecords as normal (the modules already call decrypt on read, no changes needed). Flipped: - globalTags.name — tag names can leak categorization intent - tagGroups.name — same - workbenchScenes.name/description — scene labels often encode Space-specific context - aiMissions.title/conceptMarkdown/objective — mission configuration is user-authored Deliberately unchanged: - color / icon / groupId / sortOrder / openApps / wallpaper / scopeTagIds / cadence / state / agentId — all structural, indexed, or FK data needed for query paths - agents.name stays plaintext per the prior design note (Actor displayName cache key) Migration approach — pre-live lenient: decryptRecords skips values that aren't encrypted (isEncrypted gate in record-helpers.ts:256), so existing plaintext rows stay readable after the flip. New writes encrypt; existing rows get encrypted organically as the user edits them. No Dexie migration needed. A post-login "encrypt-at-rest sweep" over pre-existing rows is a follow-up if hard at-rest coverage is required before launch. Crypto audit: 196 Dexie tables (95 encrypted, +4 vs 91 before), 101 allowlisted plaintext. Type-check clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apps/web/src/lib/data/crypto/registry.ts | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/apps/mana/apps/web/src/lib/data/crypto/registry.ts b/apps/mana/apps/web/src/lib/data/crypto/registry.ts index 8f8fe0217..f9ad6078d 100644 --- a/apps/mana/apps/web/src/lib/data/crypto/registry.ts +++ b/apps/mana/apps/web/src/lib/data/crypto/registry.ts @@ -574,17 +574,14 @@ export const ENCRYPTION_REGISTRY: Record = { agents: { enabled: true, fields: ['systemPrompt', 'memory'] }, // ─── AI Missions ───────────────────────────────────────── - // docs/plans/space-scoped-data-model.md §2a — declared with - // enabled:false during prep so the audit script is happy; flips to - // true in 2c alongside the Dexie v35 encryption migration. - // + // docs/plans/space-scoped-data-model.md §2e — encryption enabled. // User-typed content on missions: `title` (display label the user // types at create time), `conceptMarkdown` (free-form context the // planner reads), `objective` (the actionable goal string). State, // cadence, inputs (FK-only), nextRunAt, iterations, agentId all // stay plaintext — needed for the Runner's "due now" index walk // and mission-detail filters. - aiMissions: { enabled: false, fields: ['title', 'conceptMarkdown', 'objective'] }, + aiMissions: { enabled: true, fields: ['title', 'conceptMarkdown', 'objective'] }, // ─── User-level Tag Presets ────────────────────────────── // Named templates the user applies when creating a new Space. The @@ -598,27 +595,32 @@ export const ENCRYPTION_REGISTRY: Record = { userTagPresets: { enabled: true, fields: ['name', 'tags'] }, // ─── Tags (shared-stores) ──────────────────────────────── - // docs/plans/space-scoped-data-model.md §2a — declared with - // enabled:false during prep; flips to true in 2c. Tag names like - // "Therapie" or "Finanzen-privat" can leak personal categorization, - // so they belong under encryption. `color` + `icon` + `groupId` + - // `sortOrder` stay plaintext: they're visual metadata + the group - // FK, none of which leak sensitive taxonomy. `name` is NOT indexed - // for .where() lookups today, so encrypting it is safe — dedupe- - // within-space lookups go through the new [spaceId+name] index after - // Phase 2b and run over already-decrypted rows in the scoped store. - globalTags: { enabled: false, fields: ['name'] }, - tagGroups: { enabled: false, fields: ['name'] }, + // docs/plans/space-scoped-data-model.md §2e — encryption enabled. + // Tag names like "Therapie" or "Finanzen-privat" can leak personal + // categorization. `color` + `icon` + `groupId` + `sortOrder` stay + // plaintext: they're visual metadata + the group FK, none of which + // leak sensitive taxonomy. `name` is NOT indexed for .where() + // lookups today, so encrypting it is safe — dedupe-within-space + // lookups go through the [spaceId+name] index from 2b and run over + // already-decrypted rows in the scoped store. + // + // Pre-live migration note: decryptRecords is lenient (isEncrypted() + // gate skips plaintext values), so existing rows from before the + // flip stay readable. New writes encrypt; existing rows get + // encrypted the next time they're edited. A post-login + // "encrypt-at-rest sweep" over the pre-existing rows is a Phase 2e + // follow-up if we want hard at-rest coverage before launch. + globalTags: { enabled: true, fields: ['name'] }, + tagGroups: { enabled: true, fields: ['name'] }, // ─── Workbench Scenes ──────────────────────────────────── - // docs/plans/space-scoped-data-model.md §2a — declared with - // enabled:false during prep; flips to true in 2c. `name` is the - // user-visible scene label ("Heute", "Q2-Launch") and `description` - // is the short subtitle — both are user-typed free text that can - // leak Space-specific context. openApps / order / wallpaper / - // viewingAsAgentId / scopeTagIds stay plaintext (structural / - // indexed / foreign-key data). - workbenchScenes: { enabled: false, fields: ['name', 'description'] }, + // docs/plans/space-scoped-data-model.md §2e — encryption enabled. + // `name` is the user-visible scene label ("Heute", "Q2-Launch") + // and `description` is the short subtitle — both are user-typed + // free text that can leak Space-specific context. openApps / + // order / wallpaper / viewingAsAgentId / scopeTagIds stay + // plaintext (structural / indexed / foreign-key data). + workbenchScenes: { enabled: true, fields: ['name', 'description'] }, // ─── Articles (Pocket-style read-it-later) ────────────── // Reading-behaviour data — same sensitivity class as newsArticles.