3 Commits

Author SHA1 Message Date
LC mac
9ffdbbd50f feat(v1.4): Add 'Encrypted in memory' badge 2026-01-30 01:21:28 +08:00
LC mac
b024856c08 docs: update GEMINI.md for v1.4.0 + remove debug logs 2026-01-30 00:44:46 +08:00
LC mac
a919e8bf09 chore: small fix in 1.4.0 2026-01-30 00:36:09 +08:00
5 changed files with 19 additions and 18 deletions

View File

@@ -34,7 +34,7 @@
### Directory Structure ### Directory Structure
``` ```BASH
src/ src/
├── components/ # React UI components ├── components/ # React UI components
│ ├── PgpKeyInput.tsx │ ├── PgpKeyInput.tsx
@@ -104,10 +104,11 @@ Core interfaces:
- **Clipboard Tracker**: Monitor clipboard operations on sensitive fields - **Clipboard Tracker**: Monitor clipboard operations on sensitive fields
- **Read-only Mode**: Toggle to clear state + show CSP/build info - **Read-only Mode**: Toggle to clear state + show CSP/build info
### v1.3 - Session-Key Encryption (Current) ### v1.3-v1.4 - Session-Key Encryption
- **Ephemeral encryption**: AES-GCM-256 session key (non-exportable) encrypts sensitive state - **Ephemeral encryption**: AES-GCM-256 session key (non-exportable) encrypts sensitive state
- **Auto-clear**: Plaintext mnemonic cleared from UI immediately after QR generation - **Backup flow (v1.3)**: Mnemonic auto-clears immediately after QR generation
- **Restore flow (v1.4)**: Decrypted mnemonic auto-clears after 10 seconds + manual Hide button
- **Encrypted cache**: Only ciphertext stored in React state; key lives in memory only - **Encrypted cache**: Only ciphertext stored in React state; key lives in memory only
- **Lock/Clear**: Manual cleanup destroys session key + clears all state - **Lock/Clear**: Manual cleanup destroys session key + clears all state
- **Lifecycle**: Session key auto-destroyed on page close/refresh - **Lifecycle**: Session key auto-destroyed on page close/refresh
@@ -290,14 +291,13 @@ await window.runSessionCryptoTest()
--- ---
## Current Version: v1.3.0 ## Current Version: v1.4.0
### Recent Changes (2026-01-29) ### Recent Changes (2026-01-30)
- ✅ Extended session-key encryption to Restore flow
- ✅ Added `src/lib/sessionCrypto.ts` with ephemeral AES-GCM session keys - ✅ Added 10-second auto-clear timer for restored mnemonic
-Integrated into Backup flow: plaintext mnemonic auto-cleared after QR generation -Added Hide button for manual clear
-Added Lock/Clear button to destroy session key and clear all state -Removed debug console logs from sessionCrypto.ts
- ✅ Added cleanup on component unmount
### Known Limitations ### Known Limitations
@@ -370,6 +370,7 @@ Check:
Output: ✅ or ❌ for each item + suggest fixes for failures. Output: ✅ or ❌ for each item + suggest fixes for failures.
``` ```
--- ---
**Last Updated**: 2026-01-29 **Last Updated**: 2026-01-29

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SeedPGP v1.1</title> <title>SeedPGP v1.4</title>
</head> </head>
<body> <body>

View File

@@ -1,7 +1,7 @@
{ {
"name": "seedpgp-web", "name": "seedpgp-web",
"private": true, "private": true,
"version": "1.3.0", "version": "1.4.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -216,6 +216,12 @@ import { getSessionKey, encryptJsonToBlob, destroySessionKey, EncryptedBlob } fr
<span>Read-only</span> <span>Read-only</span>
</div> </div>
)} )}
{encryptedMnemonicCache && (
<div className="flex items-center gap-2 text-sm text-green-400 bg-slate-800/50 px-3 py-1.5 rounded-lg">
<Shield size={16} />
<span>Encrypted in memory</span>
</div>
)}
<div className="flex bg-slate-800/50 rounded-lg p-1 backdrop-blur"> <div className="flex bg-slate-800/50 rounded-lg p-1 backdrop-blur">
<button <button
onClick={() => { onClick={() => {

View File

@@ -56,13 +56,10 @@ export interface EncryptedBlob {
* @returns A promise that resolves to the generated or existing CryptoKey. * @returns A promise that resolves to the generated or existing CryptoKey.
*/ */
export async function getSessionKey(): Promise<CryptoKey> { export async function getSessionKey(): Promise<CryptoKey> {
console.log('getSessionKey called.');
if (sessionKey) { if (sessionKey) {
console.log('Session key already exists.');
return sessionKey; return sessionKey;
} }
console.log('Generating new session key...');
const key = await window.crypto.subtle.generateKey( const key = await window.crypto.subtle.generateKey(
{ {
name: KEY_ALGORITHM, name: KEY_ALGORITHM,
@@ -72,7 +69,6 @@ export async function getSessionKey(): Promise<CryptoKey> {
['encrypt', 'decrypt'], ['encrypt', 'decrypt'],
); );
sessionKey = key; sessionKey = key;
console.log('New session key generated and stored.');
return key; return key;
} }
@@ -82,9 +78,7 @@ export async function getSessionKey(): Promise<CryptoKey> {
* @returns A promise that resolves to an EncryptedBlob. * @returns A promise that resolves to an EncryptedBlob.
*/ */
export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> { export async function encryptJsonToBlob<T>(data: T): Promise<EncryptedBlob> {
console.log('encryptJsonToBlob called.');
if (!sessionKey) { if (!sessionKey) {
console.error('ERROR: Session key not initialized when encryptJsonToBlob was called.');
throw new Error('Session key not initialized. Call getSessionKey() first.'); throw new Error('Session key not initialized. Call getSessionKey() first.');
} }