mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeb5184b8a | ||
|
|
422fe04a12 | ||
|
|
ebeea79a33 | ||
|
|
faf58dc49d | ||
|
|
46982794cc | ||
|
|
9ffdbbd50f | ||
|
|
b024856c08 | ||
|
|
a919e8bf09 |
34
GEMINI.md
34
GEMINI.md
@@ -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
|
||||||
@@ -129,9 +130,18 @@ bun run preview # Preview production build
|
|||||||
|
|
||||||
### Deployment Process
|
### Deployment Process
|
||||||
|
|
||||||
|
This project is now deployed to Cloudflare Pages for enhanced security.
|
||||||
|
|
||||||
1. **Private repo** (`seedpgp-web`): Source code, development
|
1. **Private repo** (`seedpgp-web`): Source code, development
|
||||||
2. **Public repo** (`seedpgp-web-app`): Built files for GitHub Pages
|
2. **Cloudflare Pages**: Deploys from `seedpgp-web` repo directly.
|
||||||
3. **Deploy script** (`scripts/deploy.sh`): Builds + copies to dist/ + pushes to public repo
|
3. **GitHub Pages (Legacy)**: `seedpgp-web-app` public repo is retained for historical purposes, but no longer actively deployed to.
|
||||||
|
|
||||||
|
### Cloudflare Pages Deployment
|
||||||
|
|
||||||
|
1. Connect GitHub repo (`seedpgp-web`) to Cloudflare Pages.
|
||||||
|
2. Build settings: `bun run build`, output directory: `dist/`.
|
||||||
|
3. `public/_headers` file enforces Content Security Policy (CSP) and other security headers automatically.
|
||||||
|
4. Benefits: Real CSP enforcement, not just a UI toggle.
|
||||||
|
|
||||||
### Git Workflow
|
### Git Workflow
|
||||||
|
|
||||||
@@ -290,14 +300,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 +379,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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "seedpgp-web",
|
"name": "seedpgp-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.3.0",
|
"version": "1.4.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
6
public/_headers
Normal file
6
public/_headers
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'none'; form-action 'none'; base-uri 'self';
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
X-XSS-Protection: 1; mode=block
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
@@ -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={() => {
|
||||||
|
|||||||
@@ -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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ const gitHash = execSync('git rev-parse --short HEAD').toString().trim()
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: '/seedpgp-web-app/',
|
base: process.env.CF_PAGES ? '/' : '/seedpgp-web-app/',
|
||||||
|
publicDir: 'public', // ← Explicitly set (should be default)
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
emptyOutDir: false,
|
emptyOutDir: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user