diff --git a/GEMINI.md b/GEMINI.md index b6aeba5..e48325c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,12 +1,14 @@ # SeedPGP - Gemini Code Assist Project Brief ## Project Overview + **SeedPGP v1.3.0**: Client-side BIP39 mnemonic encryption webapp **Stack**: Bun + Vite + React + TypeScript + OpenPGP.js + Tailwind CSS **Deploy**: GitHub Pages (public repo: `seedpgp-web-app`, private source: `seedpgp-web`) -**Live URL**: https://kccleoc.github.io/seedpgp-web-app/ +**Live URL**: ## Core Constraints + 1. **Security-first**: Never persist secrets (mnemonic/passphrase/private keys) to localStorage/sessionStorage/IndexedDB 2. **Small PRs**: Max 1-5 files per feature; propose plan before coding 3. **Client-side only**: No backend; all crypto runs in browser (Web Crypto API + OpenPGP.js) @@ -14,6 +16,7 @@ 5. **Honest security claims**: Don't overclaim what client-side JS can guarantee ## Non-Negotiables + - Small diffs only: one feature slice per PR (1-5 files if possible) - No big code dumps; propose plan first, then implement - Never persist secrets to browser storage @@ -25,10 +28,12 @@ ## Architecture Map ### Entry Points + - `src/main.tsx` → `src/App.tsx` (main application) - Build output: `dist/` (separate git repo for GitHub Pages deployment) ### Directory Structure + ``` src/ ├── components/ # React UI components @@ -51,14 +56,18 @@ src/ ### Key Modules #### `src/lib/seedpgp.ts` + Core encryption/decryption: + - `encryptToSeedPgp()` - Encrypts mnemonic with PGP public key + optional password - `decryptFromSeedPgp()` - Decrypts with PGP private key + optional password - Uses OpenPGP.js for PGP operations - Output format: `SEEDPGP1:version:base64data:fingerprint` #### `src/lib/sessionCrypto.ts` (v1.3.0+) + Ephemeral session-key encryption: + - `getSessionKey()` - Generates/returns non-exportable AES-GCM-256 key (idempotent) - `encryptJsonToBlob(obj)` - Encrypts to `{v, alg, iv_b64, ct_b64}` - `decryptBlobToJson(blob)` - Decrypts back to original object @@ -66,7 +75,9 @@ Ephemeral session-key encryption: - Test: `await window.runSessionCryptoTest()` (DEV only) #### `src/lib/types.ts` + Core interfaces: + - `SeedPgpPlaintext` - Decrypted mnemonic data structure - `SeedPgpCiphertext` - Encrypted payload structure - `EncryptedBlob` - Session-key encrypted cache format @@ -76,21 +87,25 @@ Core interfaces: ## Key Features ### v1.0 - Core Functionality + - **Backup**: Encrypt mnemonic with PGP public key + optional password → QR display - **Restore**: Scan/paste QR → decrypt with private key → show mnemonic - **PGP support**: Import public/private keys (.asc files or paste) ### v1.1 - QR Features + - **QR Display**: Generate QR codes from encrypted data - **QR Scanner**: Camera + file upload (uses html5-qrcode library) ### v1.2 - Security Monitoring + - **Storage Indicator**: Real-time display of localStorage/sessionStorage contents - **Security Warnings**: Context-aware alerts about browser memory limitations - **Clipboard Tracker**: Monitor clipboard operations on sensitive fields - **Read-only Mode**: Toggle to clear state + show CSP/build info ### v1.3 - Session-Key Encryption (Current) + - **Ephemeral encryption**: AES-GCM-256 session key (non-exportable) encrypts sensitive state - **Auto-clear**: Plaintext mnemonic cleared from UI immediately after QR generation - **Encrypted cache**: Only ciphertext stored in React state; key lives in memory only @@ -102,6 +117,7 @@ Core interfaces: ## Development Workflow ### Commands + ```bash bun install # Install dependencies bun run dev # Dev server (localhost:5173) @@ -112,11 +128,13 @@ bun run preview # Preview production build ``` ### Deployment Process + 1. **Private repo** (`seedpgp-web`): Source code, development 2. **Public repo** (`seedpgp-web-app`): Built files for GitHub Pages 3. **Deploy script** (`scripts/deploy.sh`): Builds + copies to dist/ + pushes to public repo ### Git Workflow + ```bash # Commit feature git add src/ @@ -135,24 +153,29 @@ git push origin main --tags ## Required Workflow for AI Agents ### 1. Study First + Before implementing any feature: + - Read relevant files - Explain current architecture + entry points - List files that will be touched - Identify potential conflicts or dependencies ### 2. Plan + - Propose smallest vertical slice (1-5 files) - Show API signatures or interface changes first - Get approval before generating full implementation ### 3. Implement + - Generate code with TypeScript strict mode - Include JSDoc comments for public APIs - Show unified diffs, not full file rewrites (when possible) - Keep changes under 50-100 lines per file when feasible ### 4. Verify + - Run `bun run typecheck` - no errors - Run `bun run build` - successful dist/ output - Provide manual test steps for browser verification @@ -163,25 +186,30 @@ Before implementing any feature: ## Common Patterns ### State Management + - React `useState` + `useEffect` (no Redux/Zustand/external store) - Ephemeral state only; avoid persistent storage for secrets ### Styling + - Tailwind utility classes (configured in `tailwind.config.js`) - Responsive design: mobile-first with `md:` breakpoints - Dark theme primary: slate-900 background, blue-400 accents ### Icons + - `lucide-react` library - Common: Shield, QrCode, Lock, Eye, AlertCircle ### Crypto Operations + - **PGP**: OpenPGP.js (`openpgp` package) - **Session keys**: Web Crypto API (`crypto.subtle`) - **Key generation**: `crypto.subtle.generateKey()` with `extractable: false` - **Encryption**: AES-GCM with random 12-byte IV per operation ### Type Safety + - Strict TypeScript (`tsconfig.json`: `strict: true`) - Check `src/lib/types.ts` for core interfaces - Avoid `any`; use `unknown` + type guards when necessary @@ -191,22 +219,27 @@ Before implementing any feature: ## Security Architecture ### Threat Model (Honest) + **What we protect against:** + - Accidental persistence to localStorage/sessionStorage - Plaintext secrets lingering in React state after use - Clipboard history exposure (with warnings) **What we DON'T protect against (and must not claim to):** + - Active XSS or malicious browser extensions - Memory dumps or browser crash reports - JavaScript garbage collection timing (non-deterministic) ### Memory Handling + - **Session keys**: Non-exportable CryptoKey objects (Web Crypto API) - **Plaintext clearing**: Set to empty string + drop references (but GC timing is non-deterministic) - **No guarantees**: Cannot force immediate memory wiping in JavaScript ### Storage Policy + - **NEVER write to**: localStorage, sessionStorage, IndexedDB, cookies - **Exception**: Non-sensitive UI state only (theme preferences, etc.) - NOT IMPLEMENTED YET - **Verification**: StorageIndicator component monitors all storage APIs @@ -216,17 +249,20 @@ Before implementing any feature: ## What NOT to Do ### Code Generation + - Don't generate full file rewrites unless necessary - Don't add dependencies without discussing bundle size impact - Don't use `any` types without explicit justification - Don't skip TypeScript strict mode checks ### Security Claims + - Don't claim "RAM is wiped" (JavaScript can't force GC) - Don't claim "offline mode" without real CSP headers (GitHub Pages can't set custom headers) - Don't promise protection against active browser compromise (XSS/extensions) ### Storage + - Don't write secrets to storage without explicit approval - Don't cache decrypted data beyond immediate use - Don't assume browser storage is secure @@ -236,6 +272,7 @@ Before implementing any feature: ## Testing & Verification ### Manual Test Checklist (Before Marking Feature Complete) + 1. ✅ `bun run typecheck` passes (no TypeScript errors) 2. ✅ `bun run build` succeeds (dist/ generated) 3. ✅ Browser test: Feature works as described @@ -244,6 +281,7 @@ Before implementing any feature: 6. ✅ DevTools Network tab: No unexpected network calls (if Read-only Mode) ### Session-Key Encryption Test (v1.3+) + ```javascript // In browser DevTools console: await window.runSessionCryptoTest() @@ -255,17 +293,20 @@ await window.runSessionCryptoTest() ## Current Version: v1.3.0 ### Recent Changes (2026-01-29) + - ✅ Added `src/lib/sessionCrypto.ts` with ephemeral AES-GCM session keys - ✅ Integrated into Backup flow: plaintext mnemonic auto-cleared after QR generation - ✅ Added Lock/Clear button to destroy session key and clear all state - ✅ Added cleanup on component unmount ### Known Limitations + - GitHub Pages cannot set custom CSP headers (need Cloudflare Pages for enforcement) - Read-only Mode is UI-level only (not browser-enforced) - Session-key encryption doesn't protect against active XSS/extensions ### Next Priorities (Suggested) + 1. Extend session-key encryption to Restore flow 2. Migrate to Cloudflare Pages for real CSP header enforcement 3. Add "Encrypted in memory" badge when encryptedMnemonicCache exists @@ -276,12 +317,15 @@ await window.runSessionCryptoTest() ## Quick Reference ### File a Bug/Feature + 1. Describe expected vs actual behavior 2. Include browser console errors (if any) 3. Specify which flow (Backup/Restore/QR Scanner) ### Roll Over to Next Session + Always provide: + - Current version number - What was implemented this session - Files modified @@ -293,6 +337,7 @@ Always provide: ## Example Prompts for Gemini ### Exploration + ``` Read GEMINI.md, then explain: 1. Where is the mnemonic textarea and how is its value managed? @@ -301,6 +346,7 @@ Read GEMINI.md, then explain: ``` ### Feature Request + ``` Task: [Feature description] @@ -315,6 +361,7 @@ Plan first: show proposed API/changes before generating code. ``` ### Verification + ``` Audit the codebase to verify [feature] is fully implemented. Check: diff --git a/src/App.tsx b/src/App.tsx index e04b39c..2d8295b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,7 +23,7 @@ import { StorageIndicator } from './components/StorageIndicator'; import { SecurityWarnings } from './components/SecurityWarnings'; import { ClipboardTracker } from './components/ClipboardTracker'; import { ReadOnly } from './components/ReadOnly'; -import { encryptJsonToBlob, destroySessionKey, EncryptedBlob } from './lib/sessionCrypto'; +import { getSessionKey, encryptJsonToBlob, destroySessionKey, EncryptedBlob } from './lib/sessionCrypto'; console.log("OpenPGP.js version:", openpgp.config.versionString); @@ -122,6 +122,8 @@ import { encryptJsonToBlob, destroySessionKey, EncryptedBlob } from './lib/sessi setRecipientFpr(result.recipientFingerprint); } + // Initialize session key before encrypting + await getSessionKey(); // Encrypt mnemonic with session key and clear plaintext state const blob = await encryptJsonToBlob({ mnemonic, timestamp: Date.now() }); setEncryptedMnemonicCache(blob);