mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 01:47:52 +08:00
docs: update version to v1.4.7 and organize documentation
- Update package.json version to v1.4.7 - Update README.md header to v1.4.7 - Update GEMINI.md version references to v1.4.7 - Update RECOVERY_PLAYBOOK.md version to v1.4.7 - Update SECURITY_AUDIT_REPORT.md version to v1.4.7 - Move documentation files to doc/ directory for better organization - Add new documentation files: LOCAL_TESTING_GUIDE.md, SERVE.md, TAILS_OFFLINE_PLAYBOOK.md - Add Makefile and serve.ts for improved development workflow
This commit is contained in:
100
Makefile
Normal file
100
Makefile
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
.PHONY: help install build build-offline serve-local audit clean verify-offline
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "seedpgp-web Makefile - Bun-based build system"
|
||||||
|
@echo ""
|
||||||
|
@echo "Usage:"
|
||||||
|
@echo " make install - Install dependencies with Bun"
|
||||||
|
@echo " make build - Build production bundle (for Cloudflare)"
|
||||||
|
@echo " make build-offline - Build with relative paths (for offline/Tails use)"
|
||||||
|
@echo " make serve-local - Serve dist/ locally for testing (http://localhost:8000)"
|
||||||
|
@echo " make audit - Run security audit"
|
||||||
|
@echo " make verify-offline - Verify offline compatibility"
|
||||||
|
@echo " make clean - Clean build artifacts"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
install:
|
||||||
|
@echo "📦 Installing dependencies with Bun..."
|
||||||
|
bun install
|
||||||
|
|
||||||
|
# Build for Cloudflare (absolute paths)
|
||||||
|
build:
|
||||||
|
@echo "🔨 Building for Cloudflare Pages (absolute paths)..."
|
||||||
|
VITE_BASE_PATH="/" bun run vite build
|
||||||
|
@echo "✅ Build complete: dist/"
|
||||||
|
|
||||||
|
# Build for offline/Tails (relative paths)
|
||||||
|
build-offline:
|
||||||
|
@echo "🔨 Building for offline/Tails (relative paths)..."
|
||||||
|
VITE_BASE_PATH="./" bun run vite build
|
||||||
|
@echo "✅ Build complete: dist/ (with relative asset paths)"
|
||||||
|
|
||||||
|
# Development server (for testing locally)
|
||||||
|
serve-local:
|
||||||
|
@echo "🚀 Starting local server at http://localhost:8000"
|
||||||
|
@echo " Press Ctrl+C to stop"
|
||||||
|
# Use Python builtin http.server for a simple, dependency-free static server
|
||||||
|
cd dist && python3 -m http.server 8000
|
||||||
|
|
||||||
|
serve-bun:
|
||||||
|
@echo "🚀 Starting Bun static server at http://127.0.0.1:8000"
|
||||||
|
@echo " Press Ctrl+C to stop"
|
||||||
|
bun ./serve.ts
|
||||||
|
|
||||||
|
# Security audit - check for network calls and suspicious patterns
|
||||||
|
audit:
|
||||||
|
@echo "🔍 Running security audit..."
|
||||||
|
@echo ""
|
||||||
|
@echo "Checking for fetch/XHR calls..."
|
||||||
|
@grep -r "fetch\|XMLHttpRequest\|axios\|http\|https" src/ --include="*.ts" --include="*.tsx" --include="*.js" || echo "✅ No network calls found"
|
||||||
|
@echo ""
|
||||||
|
@echo "Checking dist/ for external resources..."
|
||||||
|
@grep -r "cloudflare\|googleapis\|cdn\|http:" dist/ || echo "✅ No external URLs in build"
|
||||||
|
@echo ""
|
||||||
|
@echo "Checking for localStorage/sessionStorage usage..."
|
||||||
|
@grep -r "localStorage\|sessionStorage" src/ --include="*.ts" --include="*.tsx" || echo "✅ No persistent storage calls"
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Security audit complete"
|
||||||
|
|
||||||
|
# Verify offline compatibility
|
||||||
|
verify-offline:
|
||||||
|
@echo "🧪 Verifying offline compatibility..."
|
||||||
|
@echo ""
|
||||||
|
@echo "Checking dist/ file structure..."
|
||||||
|
@find dist -type f | wc -l | xargs echo "Total files:"
|
||||||
|
@echo ""
|
||||||
|
@echo "Verifying index.html exists and is readable..."
|
||||||
|
@[ -f dist/index.html ] && echo "✅ index.html found" || echo "❌ index.html NOT found"
|
||||||
|
@echo ""
|
||||||
|
@echo "Checking for asset references in index.html..."
|
||||||
|
@head -20 dist/index.html | grep -q "assets" && echo "✅ Assets referenced" || echo "⚠️ No assets referenced"
|
||||||
|
@echo ""
|
||||||
|
@echo "Checking for relative path usage..."
|
||||||
|
@grep -q 'src="./' dist/index.html && echo "✅ Relative paths detected" || echo "⚠️ Check asset paths"
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Offline compatibility check complete"
|
||||||
|
|
||||||
|
# Clean build artifacts
|
||||||
|
clean:
|
||||||
|
@echo "🗑️ Cleaning build artifacts..."
|
||||||
|
rm -rf dist/
|
||||||
|
rm -rf .dist/
|
||||||
|
@echo "✅ Clean complete"
|
||||||
|
|
||||||
|
# Full pipeline: clean, build for offline, verify
|
||||||
|
full-build-offline: clean build-offline verify-offline audit
|
||||||
|
@echo ""
|
||||||
|
@echo "✅ Full offline build pipeline complete!"
|
||||||
|
@echo " Ready to copy to USB for Tails"
|
||||||
|
@echo ""
|
||||||
|
@echo "Next steps:"
|
||||||
|
@echo " 1. Format USB: diskutil secureErase freespace 0 /dev/diskX"
|
||||||
|
@echo " 2. Copy: cp -R dist/* /Volumes/SEEDPGP/"
|
||||||
|
@echo " 3. Eject: diskutil eject /Volumes/SEEDPGP"
|
||||||
|
@echo " 4. Boot Tails and insert Application USB"
|
||||||
|
|
||||||
|
# Quick development setup
|
||||||
|
dev:
|
||||||
|
@echo "🚀 Starting Bun dev server..."
|
||||||
|
bun run dev
|
||||||
48
README.md
48
README.md
@@ -1,13 +1,49 @@
|
|||||||
# SeedPGP v1.4.5
|
# SeedPGP v1.4.7
|
||||||
|
|
||||||
**Secure BIP39 mnemonic backup using PGP encryption and QR codes**
|
**Secure BIP39 mnemonic backup using PGP encryption and QR codes**
|
||||||
|
|
||||||
A client-side web app for encrypting cryptocurrency seed phrases with OpenPGP and encoding them as QR-friendly Base45 frames with CRC16 integrity checking.
|
A client-side web app for encrypting cryptocurrency seed phrases with OpenPGP and encoding them as QR-friendly Base45 frames with CRC16 integrity checking.
|
||||||
|
|
||||||
|
**Quick note for Bitcoin users (beginner-friendly):**
|
||||||
|
|
||||||
|
- This tool helps you securely back up your Bitcoin seed phrase (BIP39) by encrypting it with OpenPGP and giving you a compact QR-friendly export. You don't need to understand the internals to use it — follow the Quick Start below and test recovery immediately.
|
||||||
|
- If you are new to Bitcoin: write your seed phrase on paper, keep copies in separate secure locations, and consider using Tails for larger amounts.
|
||||||
|
|
||||||
**Live App:** <https://seedpgp-web.pages.dev>
|
**Live App:** <https://seedpgp-web.pages.dev>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🚦 Quick Start — Bitcoin Beginners
|
||||||
|
|
||||||
|
If you're new to Bitcoin, this short guide gets you from zero to a tested backup in a few minutes.
|
||||||
|
|
||||||
|
1. Clone the repo and install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/kccleoc/seedpgp-web.git
|
||||||
|
cd seedpgp-web
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Build the offline bundle and serve it locally (recommended):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make full-build-offline # builds and verifies dist/
|
||||||
|
make serve-local # start local HTTP server on http://localhost:8000
|
||||||
|
# or: bun run serve # uses Bun server
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Open your browser at `http://localhost:8000`, generate a seed, write it on paper, then encrypt/export using the app.
|
||||||
|
|
||||||
|
2. IMPORTANT: Test recovery immediately — import the backup into the app and confirm the seed matches.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Always store the written seed (paper) securely; treat it like cash.
|
||||||
|
- For larger amounts, follow the Tails air-gapped instructions in the `doc/TAILS_OFFLINE_PLAYBOOK.md` file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 💡 Safe Usage Guide: Choose Your Path
|
## 💡 Safe Usage Guide: Choose Your Path
|
||||||
|
|
||||||
**Before you start**: How much are you backing up? This determines your setup.
|
**Before you start**: How much are you backing up? This determines your setup.
|
||||||
@@ -263,7 +299,7 @@ You now have:
|
|||||||
|
|
||||||
## 🛡️ Threat Model & Limitations
|
## 🛡️ Threat Model & Limitations
|
||||||
|
|
||||||
See [MEMORY_STRATEGY.md](MEMORY_STRATEGY.md) for comprehensive explanation of what SeedPGP protects against and what it can't.
|
See [MEMORY_STRATEGY.md](doc/MEMORY_STRATEGY.md) for comprehensive explanation of what SeedPGP protects against and what it can't.
|
||||||
|
|
||||||
**TL;DR - Real risks are:**
|
**TL;DR - Real risks are:**
|
||||||
|
|
||||||
@@ -341,9 +377,9 @@ bun test:integration
|
|||||||
|
|
||||||
## 📖 Technical Documentation
|
## 📖 Technical Documentation
|
||||||
|
|
||||||
- [MEMORY_STRATEGY.md](MEMORY_STRATEGY.md) - Why JS can't zero memory and how SeedPGP defends
|
- [MEMORY_STRATEGY.md](doc/MEMORY_STRATEGY.md) - Why JS can't zero memory and how SeedPGP defends
|
||||||
- [RECOVERY_PLAYBOOK.md](RECOVERY_PLAYBOOK.md) - Offline recovery instructions
|
- [RECOVERY_PLAYBOOK.md](doc/RECOVERY_PLAYBOOK.md) - Offline recovery instructions
|
||||||
- [SECURITY_AUDIT_REPORT.md](SECURITY_AUDIT_REPORT.md) - Full audit findings
|
- [SECURITY_AUDIT_REPORT.md](doc/SECURITY_AUDIT_REPORT.md) - Full audit findings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -377,7 +413,7 @@ Guard it with your life.
|
|||||||
|
|
||||||
- **Issues:** [GitHub Issues](https://github.com/kccleoc/seedpgp-web/issues)
|
- **Issues:** [GitHub Issues](https://github.com/kccleoc/seedpgp-web/issues)
|
||||||
- **Security:** Private disclosure via GitHub security advisory
|
- **Security:** Private disclosure via GitHub security advisory
|
||||||
- **Recovery Help:** See [RECOVERY_PLAYBOOK.md](RECOVERY_PLAYBOOK.md)
|
- **Recovery Help:** See [RECOVERY_PLAYBOOK.md](doc/RECOVERY_PLAYBOOK.md)
|
||||||
|
|
||||||
**Author:** kccleoc
|
**Author:** kccleoc
|
||||||
**Security Audited:** v1.4.4 (no exploits found)
|
**Security Audited:** v1.4.4 (no exploits found)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**SeedPGP v1.4.5**: Client-side BIP39 mnemonic encryption webapp
|
**SeedPGP v1.4.7**: Client-side BIP39 mnemonic encryption webapp
|
||||||
**Stack**: Bun + Vite + React + TypeScript + OpenPGP.js + Tailwind CSS
|
**Stack**: Bun + Vite + React + TypeScript + OpenPGP.js + Tailwind CSS
|
||||||
**Deploy**: Cloudflare Pages (private repo: `seedpgp-web`)
|
**Deploy**: Cloudflare Pages (private repo: `seedpgp-web`)
|
||||||
**Live URL**: <https://seedpgp-web.pages.dev/>
|
**Live URL**: <https://seedpgp-web.pages.dev/>
|
||||||
@@ -300,7 +300,7 @@ await window.runSessionCryptoTest()
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current Version: v1.4.5
|
## Current Version: v1.4.7
|
||||||
|
|
||||||
**Recent Changes (v1.4.5):**
|
**Recent Changes (v1.4.5):**
|
||||||
- Fixed QR Scanner bugs related to camera initialization and race conditions.
|
- Fixed QR Scanner bugs related to camera initialization and race conditions.
|
||||||
244
doc/LOCAL_TESTING_GUIDE.md
Normal file
244
doc/LOCAL_TESTING_GUIDE.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# Local Testing & Offline Build Guide
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
✅ **Updated vite.config.ts**
|
||||||
|
|
||||||
|
- Changed `base: '/'` → `base: process.env.VITE_BASE_PATH || './'`
|
||||||
|
- Assets now load with relative paths: `./assets/` instead of `/assets/`
|
||||||
|
- This fixes Safari's file:// protocol restrictions for offline use
|
||||||
|
|
||||||
|
✅ **Created Makefile** (Bun-based build system)
|
||||||
|
|
||||||
|
- `make build-offline` - Build with relative paths for Tails/USB
|
||||||
|
- `make serve-local` - Test locally on <http://localhost:8000>
|
||||||
|
- `make audit` - Security scan for network calls
|
||||||
|
- `make full-build-offline` - Complete pipeline (build + verify + audit)
|
||||||
|
|
||||||
|
✅ **Updated TAILS_OFFLINE_PLAYBOOK.md**
|
||||||
|
|
||||||
|
- All references changed from `npm` → `bun`
|
||||||
|
- Added Makefile integration
|
||||||
|
- Added local testing instructions
|
||||||
|
- Added Appendix sections for quick reference
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why file:// Protocol Failed in Safari
|
||||||
|
|
||||||
|
```
|
||||||
|
[Error] Not allowed to load local resource: file:///assets/index-DRV-ClkL.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause:** Assets referenced as `/assets/` (absolute paths) don't work with `file://` protocol in browsers for security reasons.
|
||||||
|
|
||||||
|
**Solution:** Use relative paths `./assets/` which:
|
||||||
|
|
||||||
|
- Work with both `file://` on Tails
|
||||||
|
- Work with `http://` on macOS for testing
|
||||||
|
- Are included in the vite.config.ts change above
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Locally on macOS (Before Tails)
|
||||||
|
|
||||||
|
### Step 1: Build with offline configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd seedpgp-web
|
||||||
|
make install # Install dependencies if not done
|
||||||
|
make build-offline # Build with relative paths
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Serve locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make serve-local
|
||||||
|
# Output: 🚀 Starting local server at http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Test in Safari
|
||||||
|
|
||||||
|
- Open Safari
|
||||||
|
- Go to: `http://localhost:8000`
|
||||||
|
- Verify:
|
||||||
|
- All assets load (no errors in console)
|
||||||
|
- UI displays correctly
|
||||||
|
- Functionality works
|
||||||
|
|
||||||
|
### Step 4: Clean up
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop server: Ctrl+C
|
||||||
|
# Clean build: make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Building for Cloudflare vs Offline
|
||||||
|
|
||||||
|
### For Tails/Offline (use this for your air-gapped workflow)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build-offline
|
||||||
|
# Builds with: base: './'
|
||||||
|
# Assets use relative paths
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Cloudflare Pages (production deployment)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
# Builds with: base: '/'
|
||||||
|
# Assets use absolute paths (correct for web servers)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check assets are using relative paths
|
||||||
|
head -20 dist/index.html | grep "src=\|href="
|
||||||
|
|
||||||
|
# Should show: src="./assets/..." href="./assets/..."
|
||||||
|
|
||||||
|
# Run full security pipeline
|
||||||
|
make full-build-offline
|
||||||
|
|
||||||
|
# Just audit for network calls
|
||||||
|
make audit
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## USB Transfer Workflow
|
||||||
|
|
||||||
|
Once local testing passes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Build with offline paths
|
||||||
|
make build-offline
|
||||||
|
|
||||||
|
# 2. Format USB (replace diskX with your USB)
|
||||||
|
diskutil secureErase freespace 0 /dev/diskX
|
||||||
|
|
||||||
|
# 3. Create partition
|
||||||
|
diskutil partitionDisk /dev/diskX 1 MBR FAT32 SEEDPGP 0b
|
||||||
|
|
||||||
|
# 4. Copy all files
|
||||||
|
cp -R dist/* /Volumes/SEEDPGP/
|
||||||
|
|
||||||
|
# 5. Eject
|
||||||
|
diskutil eject /Volumes/SEEDPGP
|
||||||
|
|
||||||
|
# 6. Boot Tails, insert USB, open file:///media/amnesia/SEEDPGP/index.html in Firefox
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure After Build
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/
|
||||||
|
├── index.html (references ./assets/...)
|
||||||
|
├── assets/
|
||||||
|
│ ├── index-xxx.js (minified app code)
|
||||||
|
│ ├── index-xxx.css (styles)
|
||||||
|
│ └── secp256k1-xxx.wasm (crypto library)
|
||||||
|
└── vite.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
All assets have relative paths in index.html ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why This Works for Offline
|
||||||
|
|
||||||
|
| Scenario | Base Path | Works? |
|
||||||
|
|----------|-----------|--------|
|
||||||
|
| `file:///media/amnesia/SEEDPGP/index.html` on Tails | `./` | ✅ Yes |
|
||||||
|
| `http://localhost:8000` on macOS | `./` | ✅ Yes |
|
||||||
|
| `https://example.com` on Cloudflare | `./` | ✅ Yes (still works) |
|
||||||
|
| `file://` with absolute paths `/assets/` | `/` | ❌ No (security blocked) |
|
||||||
|
|
||||||
|
The relative path solution works everywhere! ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Test locally first**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build-offline && make serve-local
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Verify no network calls**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make audit
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Prepare USB for Tails**
|
||||||
|
- Follow the USB Transfer Workflow section above
|
||||||
|
|
||||||
|
4. **Boot Tails and test**
|
||||||
|
- Follow Phase 5-7 in TAILS_OFFLINE_PLAYBOOK.md
|
||||||
|
|
||||||
|
5. **Generate seed phrase**
|
||||||
|
- All offline, no network exposure ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**"Cannot find module 'bun'"**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install bun
|
||||||
|
```
|
||||||
|
|
||||||
|
**"make: command not found"**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS should have make pre-installed
|
||||||
|
# Verify: which make
|
||||||
|
```
|
||||||
|
|
||||||
|
**"Port 8000 already in use"**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# The serve script will automatically find another port
|
||||||
|
# Or kill existing process: lsof -ti:8000 | xargs kill -9
|
||||||
|
```
|
||||||
|
|
||||||
|
**Assets still not loading in Safari**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clear Safari cache
|
||||||
|
# Safari → Settings → Privacy → Remove All Website Data
|
||||||
|
# Then test again
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Differences from Original Setup
|
||||||
|
|
||||||
|
| Aspect | Before | After |
|
||||||
|
|--------|--------|-------|
|
||||||
|
| Package Manager | npm | Bun |
|
||||||
|
| Base Path | `/` (absolute) | `./` (relative) |
|
||||||
|
| Build Command | `npm run build` | `make build-offline` |
|
||||||
|
| Local Testing | Couldn't test locally | `make serve-local` |
|
||||||
|
| File Protocol Support | ❌ Broken in Safari | ✅ Works everywhere |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Modified/Created
|
||||||
|
|
||||||
|
- ✏️ `vite.config.ts` - Changed base path to relative
|
||||||
|
- ✨ `Makefile` - New build automation (8 commands)
|
||||||
|
- 📝 `TAILS_OFFLINE_PLAYBOOK.md` - Updated for Bun + local testing
|
||||||
|
|
||||||
|
All three files are ready to use now!
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
## SeedPGP Recovery Playbook - Offline Recovery Guide
|
## SeedPGP Recovery Playbook - Offline Recovery Guide
|
||||||
|
|
||||||
**Generated:** Feb 3, 2026 | **SeedPGP v1.4.4** | **Frame Format:** `SEEDPGP1:0:CRC16:BASE45_PAYLOAD`
|
**Generated:** Feb 3, 2026 | **SeedPGP v1.4.7** | **Frame Format:** `SEEDPGP1:0:CRC16:BASE45_PAYLOAD`
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -415,7 +415,7 @@ print(f"BIP39 Passphrase used: {'YES' if data['pp'] == 1 else 'NO'}")
|
|||||||
**Print this playbook on archival paper or metal. Store separately from encrypted backups and private keys.** 🔒
|
**Print this playbook on archival paper or metal. Store separately from encrypted backups and private keys.** 🔒
|
||||||
|
|
||||||
**Last Updated:** February 3, 2026
|
**Last Updated:** February 3, 2026
|
||||||
**SeedPGP Version:** 1.4.4
|
**SeedPGP Version:** 1.4.7
|
||||||
**Frame Example CRC:** 58B5 ✓
|
**Frame Example CRC:** 58B5 ✓
|
||||||
**Test Recovery:** [ ] Completed [ ] Not Tested
|
**Test Recovery:** [ ] Completed [ ] Not Tested
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# SeedPGP Web Application - Comprehensive Forensic Security Audit Report
|
# SeedPGP Web Application - Comprehensive Forensic Security Audit Report
|
||||||
|
|
||||||
**Audit Date:** February 12, 2026
|
**Audit Date:** February 12, 2026
|
||||||
**Application:** seedpgp-web v1.4.6
|
**Application:** seedpgp-web v1.4.7
|
||||||
**Scope:** Full encryption, key management, and seed handling application
|
**Scope:** Full encryption, key management, and seed handling application
|
||||||
**Severity Levels:** CRITICAL | HIGH | MEDIUM | LOW
|
**Severity Levels:** CRITICAL | HIGH | MEDIUM | LOW
|
||||||
|
|
||||||
44
doc/SERVE.md
Normal file
44
doc/SERVE.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Serving the built `dist/` folder
|
||||||
|
|
||||||
|
This project provides two lightweight ways to serve the static `dist/` folder locally (both are offline):
|
||||||
|
|
||||||
|
1) Bun static server (uses `serve.ts`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From project root
|
||||||
|
bun run serve
|
||||||
|
# or
|
||||||
|
bun ./serve.ts
|
||||||
|
# Open: http://127.0.0.1:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
1) Python HTTP server (zero-deps, portable)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the `dist` folder
|
||||||
|
cd dist
|
||||||
|
python3 -m http.server 8000
|
||||||
|
# Open: http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
Convenience via `package.json` scripts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run Bun server
|
||||||
|
bun run serve
|
||||||
|
|
||||||
|
# Run Python server (from project root)
|
||||||
|
bun run serve:py
|
||||||
|
```
|
||||||
|
|
||||||
|
Makefile shortcuts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make serve-bun # runs Bun server
|
||||||
|
make serve-local # runs Python http.server
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- The Bun server sets correct `Content-Type` for `.wasm` and other assets.
|
||||||
|
- Always use HTTP (localhost) rather than opening `file://` to avoid CORS/file restrictions.
|
||||||
618
doc/TAILS_OFFLINE_PLAYBOOK.md
Normal file
618
doc/TAILS_OFFLINE_PLAYBOOK.md
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
# Tails Offline Air-Gapped Workflow Playbook
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This playbook provides step-by-step instructions for using seedpgp-web in a secure, air-gapped environment on Tails, eliminating network exposure entirely.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Prerequisites & Preparation
|
||||||
|
|
||||||
|
### 1.1 Requirements
|
||||||
|
|
||||||
|
- **Machine A (Build Machine)**: macOS with Bun, TypeScript, and Git installed
|
||||||
|
- **Tails USB**: 8GB+ USB drive with Tails installed (from tails.boum.org)
|
||||||
|
- **Application USB**: Separate 2GB+ USB drive for seedpgp-web
|
||||||
|
- **Network**: Initial internet access on Machine A only
|
||||||
|
|
||||||
|
### 1.2 Verify Prerequisites on Machine A (macOS with Bun)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify Bun is installed
|
||||||
|
bun --version # Should be v1.0+
|
||||||
|
|
||||||
|
# Verify TypeScript tools
|
||||||
|
which tsc
|
||||||
|
|
||||||
|
# Verify git
|
||||||
|
git --version
|
||||||
|
|
||||||
|
# Clone repository
|
||||||
|
cd ~/workspace
|
||||||
|
git clone <repository-url> seedpgp-web
|
||||||
|
cd seedpgp-web
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Security Checklist Before Starting
|
||||||
|
|
||||||
|
- [ ] Machine A (macOS) is trusted and malware-free (or at minimum risk)
|
||||||
|
- [ ] Bun is installed and up-to-date
|
||||||
|
- [ ] Tails USB is downloaded from official tails.boum.org
|
||||||
|
- [ ] You have physical access to verify USB connections
|
||||||
|
- [ ] You understand this is offline-only after transfer to Application USB
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Build Application Locally (Machine A)
|
||||||
|
|
||||||
|
### 2.1 Clone and Verify Code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/workspace
|
||||||
|
git clone https://github.com/seedpgp/seedpgp-web.git
|
||||||
|
cd seedpgp-web
|
||||||
|
git log --oneline -5 # Document the commit hash for reference
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Install Dependencies with Bun
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use Bun for faster installation
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Code Audit (CRITICAL)
|
||||||
|
|
||||||
|
Before building, audit the source for security issues:
|
||||||
|
|
||||||
|
- [ ] Review `src/lib/seedpgp.ts` - main crypto logic
|
||||||
|
- [ ] Review `src/lib/seedblend.ts` - seed blending algorithm
|
||||||
|
- [ ] Check `src/lib/bip39.ts` - BIP39 implementation
|
||||||
|
- [ ] Verify no external API calls in code
|
||||||
|
- [ ] Run `grep -r "fetch\|axios\|http\|api" src/` to find network calls
|
||||||
|
- [ ] Confirm all dependencies in `bunfig.toml` and `package.json` are necessary
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Perform initial audit with Bun
|
||||||
|
bun run audit # If audit script exists
|
||||||
|
grep -r "fetch\|axios\|XMLHttpRequest" src/
|
||||||
|
grep -r "localStorage\|sessionStorage" src/ # Check what data persists
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Build Production Bundle Using Makefile
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using Makefile (recommended)
|
||||||
|
make build-offline
|
||||||
|
|
||||||
|
# Or directly with Bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates:
|
||||||
|
|
||||||
|
- `dist/index.html` - Main HTML file
|
||||||
|
- `dist/assets/` - Bundled JavaScript, CSS (using relative paths)
|
||||||
|
- All static assets
|
||||||
|
|
||||||
|
### 2.5 Verify Build Output & Test Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all generated files
|
||||||
|
find dist -type f
|
||||||
|
|
||||||
|
# Verify no external resource links
|
||||||
|
grep -r "cloudflare\|googleapis\|cdn\|http:" dist/ || echo "✓ No external URLs found"
|
||||||
|
|
||||||
|
# Test locally with Bun's simple HTTP server
|
||||||
|
bun ./dist/index.html
|
||||||
|
|
||||||
|
# Or serve on port 8000
|
||||||
|
bun --serve --port 8000 ./dist # deprecated: Bun does not provide a built-in static server
|
||||||
|
# Use the Makefile: `make serve-local` (runs a Python http.server) or run directly:
|
||||||
|
# cd dist && python3 -m http.server 8000
|
||||||
|
# Then open http://localhost:8000 in Safari
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why not file://?**: Safari and Firefox restrict loading local assets via `file://` protocol for security. Using a local HTTP server bypasses this while keeping everything offline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Prepare Application USB (Machine A - macOS with Bun)
|
||||||
|
|
||||||
|
### 3.1 Format USB Drive
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List USB drives
|
||||||
|
diskutil list
|
||||||
|
|
||||||
|
# Replace diskX with your Application USB (e.g., disk2)
|
||||||
|
diskutil secureErase freespace 0 /dev/diskX
|
||||||
|
|
||||||
|
# Create new partition
|
||||||
|
diskutil partitionDisk /dev/diskX 1 MBR FAT32 SEEDPGP 0b
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Copy Built Files to USB
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mount should happen automatically, verify:
|
||||||
|
ls /Volumes/SEEDPGP
|
||||||
|
|
||||||
|
# Copy entire dist folder built with make build-offline
|
||||||
|
cp -R dist/* /Volumes/SEEDPGP/
|
||||||
|
|
||||||
|
# Verify copy completed
|
||||||
|
ls /Volumes/SEEDPGP/
|
||||||
|
ls /Volumes/SEEDPGP/assets/
|
||||||
|
|
||||||
|
# (Optional) Generate integrity hash for verification on Tails
|
||||||
|
sha256sum dist/* > /Volumes/SEEDPGP/INTEGRITY.sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Verify USB Contents
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure index.html exists and is readable
|
||||||
|
cat /Volumes/SEEDPGP/index.html | head -20
|
||||||
|
|
||||||
|
# Check file count matches
|
||||||
|
echo "Source files:" && find dist -type f | wc -l
|
||||||
|
echo "USB files:" && find /Volumes/SEEDPGP -type f | wc -l
|
||||||
|
|
||||||
|
# Check assets are properly included
|
||||||
|
find /Volumes/SEEDPGP -type d -name "assets" && echo "✅ Assets folder present"
|
||||||
|
|
||||||
|
# Verify no external URLs in assets
|
||||||
|
grep -r "http:" /Volumes/SEEDPGP/ && echo "⚠️ Warning: HTTP URLs found" || echo "✅ No external URLs"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Eject USB Safely
|
||||||
|
|
||||||
|
```bash
|
||||||
|
diskutil eject /Volumes/SEEDPGP
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Boot Tails & Prepare Environment
|
||||||
|
|
||||||
|
### 4.1 Boot Tails from Tails USB
|
||||||
|
|
||||||
|
- Power off machine
|
||||||
|
- Insert Tails USB
|
||||||
|
- Power on and boot from USB (Cmd+Option during boot on Mac)
|
||||||
|
- Select "Start Tails"
|
||||||
|
- **DO NOT connect to network** (decline "Connect to Tor" if prompted)
|
||||||
|
|
||||||
|
### 4.2 Insert Application USB
|
||||||
|
|
||||||
|
- Once Tails is running, insert Application USB
|
||||||
|
- Tails should auto-mount it to `/media/amnesia/<random-name>/`
|
||||||
|
|
||||||
|
### 4.3 Verify Files Accessible & Start HTTP Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Open terminal in Tails
|
||||||
|
ls /media/amnesia/
|
||||||
|
# Should see your Application USB mount
|
||||||
|
|
||||||
|
# Navigate to application
|
||||||
|
cd /media/amnesia/SEEDPGP/
|
||||||
|
|
||||||
|
# Verify files are present
|
||||||
|
ls -la
|
||||||
|
cat index.html | head -5
|
||||||
|
|
||||||
|
# Start local HTTP server (runs completely offline)
|
||||||
|
python3 -m http.server 8080 &
|
||||||
|
# Output: Serving HTTP on 0.0.0.0 port 8080
|
||||||
|
|
||||||
|
# Verify server is running
|
||||||
|
curl http://localhost:8080/index.html | head -5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Python3 is pre-installed on Tails. The http.server runs completely offline—no internet access required, just localhost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Run Application on Tails
|
||||||
|
|
||||||
|
### 5.1 Open Application in Browser via Local HTTP Server
|
||||||
|
|
||||||
|
**Why HTTP instead of file://?**
|
||||||
|
|
||||||
|
- HTTP is more reliable than `file://` protocol
|
||||||
|
- Eliminates browser security restrictions
|
||||||
|
- Identical to local testing on macOS
|
||||||
|
- Still completely offline (no network exposure)
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
1. **In Terminal (where you started the server from Phase 4.3):**
|
||||||
|
- Verify server is still running: `ps aux | grep http.server`
|
||||||
|
- Should show: `python3 -m http.server 8080`
|
||||||
|
- If stopped, restart: `cd /media/amnesia/SEEDPGP && python3 -m http.server 8080 &`
|
||||||
|
|
||||||
|
2. **Open Firefox:**
|
||||||
|
- Click Firefox icon on desktop
|
||||||
|
- In address bar, type: `http://localhost:8080`
|
||||||
|
- Press Enter
|
||||||
|
|
||||||
|
3. **Verify application loaded:**
|
||||||
|
- Page should load completely
|
||||||
|
- All UI elements visible
|
||||||
|
- No errors in browser console (F12 → Console tab)
|
||||||
|
|
||||||
|
### 5.2 Verify Offline Functionality
|
||||||
|
|
||||||
|
- [ ] Page loads completely
|
||||||
|
- [ ] All UI elements are visible
|
||||||
|
- [ ] No error messages in browser console (F12)
|
||||||
|
- [ ] Images/assets display correctly
|
||||||
|
- [ ] No network requests are visible in Network tab (F12)
|
||||||
|
|
||||||
|
### 5.3 Test Application Features
|
||||||
|
|
||||||
|
**Basic Functionality:**
|
||||||
|
|
||||||
|
```
|
||||||
|
- [ ] Can generate new seed phrase
|
||||||
|
- [ ] Can input existing seed phrase
|
||||||
|
- [ ] Can encrypt seed phrase
|
||||||
|
- [ ] Can generate PGP key
|
||||||
|
- [ ] QR codes generate correctly
|
||||||
|
```
|
||||||
|
|
||||||
|
**Entropy Sources (all should work offline):**
|
||||||
|
|
||||||
|
- [ ] Dice entropy input works
|
||||||
|
- [ ] User mouse/keyboard entropy captures
|
||||||
|
- [ ] Random.org is NOT accessible (verify UI indicates offline mode)
|
||||||
|
- [ ] Audio entropy can be recorded
|
||||||
|
|
||||||
|
### 5.4 Generate Your Seed Phrase
|
||||||
|
|
||||||
|
1. Navigate to main application
|
||||||
|
2. Choose entropy source (Dice, Audio, or Interaction)
|
||||||
|
3. Follow prompts to generate entropy
|
||||||
|
4. Review generated 12/24-word seed phrase
|
||||||
|
5. **Write down on paper** (do NOT screenshot, use only pen & paper)
|
||||||
|
6. Verify BIP39 validation passes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Secure Storage & Export
|
||||||
|
|
||||||
|
### 6.1 Export Encrypted Backup (Optional)
|
||||||
|
|
||||||
|
If you want to save encrypted backup to USB:
|
||||||
|
|
||||||
|
1. Use application's export feature
|
||||||
|
2. Encrypt with strong passphrase
|
||||||
|
3. Save to Application USB
|
||||||
|
4. **Do NOT save to host machine**
|
||||||
|
|
||||||
|
### 6.2 Generate PGP Key (Optional)
|
||||||
|
|
||||||
|
1. Use seedpgp-web to generate PGP key
|
||||||
|
2. Export private key (encrypted)
|
||||||
|
3. Save encrypted to USB if desired
|
||||||
|
4. **Passphrase should be memorable but not written**
|
||||||
|
|
||||||
|
### 6.3 Verify No Leaks
|
||||||
|
|
||||||
|
In Firefox Developer Tools (F12):
|
||||||
|
|
||||||
|
- **Network tab**: Should show only `localhost:8080` requests (all local)
|
||||||
|
- **Application/Storage**: Check nothing persistent was written
|
||||||
|
- **Console**: No fetch/XHR errors to external sites
|
||||||
|
|
||||||
|
**To verify server is local-only:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In terminal, check network connections
|
||||||
|
sudo netstat -tulpn | grep 8080
|
||||||
|
# Should show: tcp 0 0 127.0.0.1:8080 (LISTEN only on localhost)
|
||||||
|
# NOT on 0.0.0.0 or external interface
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Shutdown & Cleanup
|
||||||
|
|
||||||
|
### 7.1 Stop HTTP Server & Secure Shutdown
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop the http.server gracefully
|
||||||
|
killall python3
|
||||||
|
# Or find the PID and kill it
|
||||||
|
ps aux | grep http.server
|
||||||
|
kill -9 <PID> # Replace <PID> with actual process ID
|
||||||
|
|
||||||
|
# Verify it stopped
|
||||||
|
ps aux | grep http.server # Should show nothing
|
||||||
|
|
||||||
|
# Then power off Tails completely
|
||||||
|
sudo poweroff
|
||||||
|
|
||||||
|
# You can also:
|
||||||
|
# - Select "Power Off" from Tails menu
|
||||||
|
# - Or simply close/restart the laptop
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Killing the server ensures no background processes remain before shutdown.
|
||||||
|
|
||||||
|
### 7.2 Physical USB Handling
|
||||||
|
|
||||||
|
- [ ] Eject Application USB physically
|
||||||
|
- [ ] Eject Tails USB physically
|
||||||
|
- [ ] Store both in secure location
|
||||||
|
- **Tails memory is volatile** - all session data gone after power-off
|
||||||
|
|
||||||
|
### 7.3 Host Machine Cleanup (Machine A)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove build artifacts if desired (optional)
|
||||||
|
rm -rf dist/
|
||||||
|
|
||||||
|
# Clear sensitive files from shell history
|
||||||
|
history -c
|
||||||
|
|
||||||
|
# Optionally wipe Machine A's work directory
|
||||||
|
rm -rf ~/workspace/seedpgp-web/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Verification & Best Practices
|
||||||
|
|
||||||
|
### 8.1 Before Production Use - Full Test Run
|
||||||
|
|
||||||
|
**Test on macOS with Bun first:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd seedpgp-web
|
||||||
|
bun install
|
||||||
|
make build-offline # Build with relative paths
|
||||||
|
make serve-local # Serve on http://localhost:8000
|
||||||
|
# Open Safari: http://localhost:8000
|
||||||
|
# Verify: all assets load, no console errors, no network requests
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Complete Phases 1-7 with test run on Tails
|
||||||
|
2. Verify seed phrase generation works reliably
|
||||||
|
3. Test entropy sources work offline
|
||||||
|
4. Confirm PGP key generation (if using)
|
||||||
|
5. Verify export/backup functionality
|
||||||
|
|
||||||
|
### 8.2 Security Best Practices
|
||||||
|
|
||||||
|
- [ ] **Air-gap is primary defense**: No network = no exfiltration
|
||||||
|
- [ ] **Tails is ephemeral**: Always boot fresh, always clean shutdown
|
||||||
|
- [ ] **Paper backups**: Write seed phrase with pen/paper only
|
||||||
|
- [ ] **Multiple USBs**: Keep Tails and Application USB separate
|
||||||
|
- [ ] **Verify hash**: Optional - generate hash of `dist/` folder to verify integrity on future builds
|
||||||
|
|
||||||
|
### 8.3 Future Seed Generation
|
||||||
|
|
||||||
|
Repeat these steps for each new seed phrase:
|
||||||
|
|
||||||
|
1. **Boot Tails** from Tails USB (network disconnected)
|
||||||
|
2. **Insert Application USB** when Tails is running
|
||||||
|
3. **Start HTTP server:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /media/amnesia/SEEDPGP
|
||||||
|
python3 -m http.server 8080 &
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Open Firefox** → `http://localhost:8080`
|
||||||
|
5. **Generate seed phrase** (choose entropy source)
|
||||||
|
6. **Write on paper** (pen & paper only, no screenshots)
|
||||||
|
7. **Stop server and shutdown:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
killall python3
|
||||||
|
sudo poweroff
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Application USB Not Mounting on Tails
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if recognized
|
||||||
|
sudo lsblk
|
||||||
|
|
||||||
|
# Manual mount
|
||||||
|
sudo mkdir -p /media/usb
|
||||||
|
sudo mount /dev/sdX1 /media/usb
|
||||||
|
ls /media/usb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Black Screen / Firefox Won't Start
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
|
||||||
|
- Let Tails fully boot (may take 2-3 minutes)
|
||||||
|
- Try manually starting Firefox from Applications menu
|
||||||
|
- Check memory requirements (Tails recommends 2GB+ RAM)
|
||||||
|
|
||||||
|
### Issue: Assets Not Loading (Broken Images/Styling)
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify file structure on USB
|
||||||
|
ls -la /media/amnesia/SEEDPGP/assets/
|
||||||
|
|
||||||
|
# Check permissions
|
||||||
|
chmod -R 755 /media/amnesia/SEEDPGP/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Browser Console Shows Errors
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
|
||||||
|
- Check if `index.html` references external URLs
|
||||||
|
- Verify `vite.config.ts` doesn't have external dependencies
|
||||||
|
- Review network tab - should show only `localhost:8080` requests
|
||||||
|
|
||||||
|
### Issue: Can't Access <http://localhost:8080>
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify http.server is running
|
||||||
|
ps aux | grep http.server
|
||||||
|
|
||||||
|
# If not running, restart it
|
||||||
|
cd /media/amnesia/SEEDPGP
|
||||||
|
python3 -m http.server 8080 &
|
||||||
|
|
||||||
|
# If port 8080 is in use, try another port
|
||||||
|
python3 -m http.server 8081 &
|
||||||
|
# Then access http://localhost:8081
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: "Connection refused" in Firefox
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if port is listening
|
||||||
|
sudo netstat -tulpn | grep 8080
|
||||||
|
|
||||||
|
# If not, the server stopped. Restart it:
|
||||||
|
cd /media/amnesia/SEEDPGP
|
||||||
|
python3 -m http.server 8080 &
|
||||||
|
|
||||||
|
# Wait a few seconds and refresh Firefox (Cmd+R or Ctrl+R)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Checklist Summary
|
||||||
|
|
||||||
|
Before each use:
|
||||||
|
|
||||||
|
- [ ] Using Tails booted from USB (never host OS)
|
||||||
|
- [ ] Application USB inserted (separate from Tails USB)
|
||||||
|
- [ ] Network disconnected or Tor disabled
|
||||||
|
- [ ] HTTP server started: `python3 -m http.server 8080` from USB
|
||||||
|
- [ ] Accessing <http://localhost:8080> (not file://)
|
||||||
|
- [ ] Firefox console shows no external requests
|
||||||
|
- [ ] All entropy sources working offline
|
||||||
|
- [ ] Seed phrase written on paper only
|
||||||
|
- [ ] HTTP server stopped before shutdown
|
||||||
|
- [ ] USB ejected after use
|
||||||
|
- [ ] Tails powered off completely
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix A: Makefile Commands Quick Reference
|
||||||
|
|
||||||
|
All build commands are available via Makefile on Machine A:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make help # Show all available commands
|
||||||
|
make install # Install Bun dependencies
|
||||||
|
make build-offline # Build with relative paths (for Tails/offline)
|
||||||
|
make serve-local # Test locally on http://localhost:8000
|
||||||
|
make audit # Security audit for network calls
|
||||||
|
make verify-offline # Verify offline compatibility
|
||||||
|
make full-build-offline # Complete pipeline: clean → build → verify → audit
|
||||||
|
make clean # Remove dist/ folder
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example workflow:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd seedpgp-web
|
||||||
|
make install
|
||||||
|
make audit # Security check
|
||||||
|
make full-build-offline # Build and verify
|
||||||
|
# Copy to USB when ready
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix B: Local Testing on macOS Before Tails
|
||||||
|
|
||||||
|
**Why test locally first?**
|
||||||
|
|
||||||
|
- Catch build issues early
|
||||||
|
- Verify all assets load correctly
|
||||||
|
- Confirm no network requests
|
||||||
|
- Validation before USB transfer
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd seedpgp-web
|
||||||
|
make build-offline # Build with relative paths
|
||||||
|
make serve-local # Start local server
|
||||||
|
# Open Safari: http://localhost:8000
|
||||||
|
# Test functionality, then Ctrl+C to stop
|
||||||
|
```
|
||||||
|
|
||||||
|
When served locally on <http://localhost:8000>, assets load correctly. On Tails via <http://localhost:8080>, the same relative paths work seamlessly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix C: Why HTTP Server Instead of file:// Protocol?
|
||||||
|
|
||||||
|
### The Problem with file:// Protocol
|
||||||
|
|
||||||
|
Opening `file:///path/to/index.html` directly has limitations:
|
||||||
|
|
||||||
|
- **Browser security restrictions** - Some features may be blocked or behave unexpectedly
|
||||||
|
- **Asset loading issues** - Sporadic failures with relative/absolute paths
|
||||||
|
- **localStorage limitations** - Storage APIs may not work reliably
|
||||||
|
- **CORS restrictions** - Even local files face CORS-like restrictions on some browsers
|
||||||
|
- **Debugging difficulty** - Hard to distinguish app issues from browser security issues
|
||||||
|
|
||||||
|
### Why http.server Solves This
|
||||||
|
|
||||||
|
Python's `http.server` module:
|
||||||
|
|
||||||
|
1. **Mimics production environment** - Behaves like a real web server
|
||||||
|
2. **Completely offline** - Server runs only on localhost (127.0.0.1:8080)
|
||||||
|
3. **No internet required** - No connection to external servers
|
||||||
|
4. **Browser compatible** - Works reliably across Firefox, Safari, Chrome
|
||||||
|
5. **Identical to macOS testing** - Same mechanism for both platforms
|
||||||
|
6. **Simple & portable** - Python3 comes pre-installed on Tails
|
||||||
|
|
||||||
|
**Verify the server is local-only:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo netstat -tulpn | grep 8080
|
||||||
|
# Output should show: 127.0.0.1:8080 LISTEN (localhost only)
|
||||||
|
# NOT 0.0.0.0:8080 (would indicate public access)
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures your seedpgp-web app runs in a standard HTTP environment without any network exposure. ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- **Tails Documentation**: <https://tails.boum.org/doc/>
|
||||||
|
- **seedpgp-web Security Audit**: See SECURITY_AUDIT_REPORT.md
|
||||||
|
- **BIP39 Standard**: <https://github.com/trezor/python-mnemonic>
|
||||||
|
- **Air-gap Best Practices**: <https://en.wikipedia.org/wiki/Air_gap_(networking)>
|
||||||
|
- **Bun Documentation**: <https://bun.sh/docs>
|
||||||
|
- **Python HTTP Server**: <https://docs.python.org/3/library/http.server.html>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
- **v2.1** - February 13, 2026 - Updated to use Python http.server instead of file:// for reliability
|
||||||
|
- **v2.0** - February 13, 2026 - Updated for Bun, Makefile, and offline compatibility
|
||||||
|
- **v1.0** - February 13, 2026 - Initial playbook creation
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"test:integration": "bun test src/integration.test.ts"
|
"test:integration": "bun test src/integration.test.ts",
|
||||||
|
"serve": "bun ./serve.ts",
|
||||||
|
"serve:py": "cd dist && python3 -m http.server 8000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/bip32": "^2.0.4",
|
"@types/bip32": "^2.0.4",
|
||||||
|
|||||||
57
serve.ts
Normal file
57
serve.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Lightweight static file server using Bun
|
||||||
|
// Run with: bun ./serve.ts
|
||||||
|
|
||||||
|
import { extname } from 'path'
|
||||||
|
|
||||||
|
const DIST = new URL('./dist/', import.meta.url).pathname
|
||||||
|
|
||||||
|
function contentType(path: string) {
|
||||||
|
const ext = extname(path).toLowerCase()
|
||||||
|
switch (ext) {
|
||||||
|
case '.html': return 'text/html; charset=utf-8'
|
||||||
|
case '.js': return 'application/javascript; charset=utf-8'
|
||||||
|
case '.css': return 'text/css; charset=utf-8'
|
||||||
|
case '.wasm': return 'application/wasm'
|
||||||
|
case '.svg': return 'image/svg+xml'
|
||||||
|
case '.json': return 'application/json'
|
||||||
|
case '.png': return 'image/png'
|
||||||
|
case '.jpg': case '.jpeg': return 'image/jpeg'
|
||||||
|
case '.txt': return 'text/plain; charset=utf-8'
|
||||||
|
default: return 'application/octet-stream'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bun.serve({
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port: 8000,
|
||||||
|
fetch(request) {
|
||||||
|
try {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
let pathname = decodeURIComponent(url.pathname)
|
||||||
|
if (pathname === '/' || pathname === '') pathname = '/index.html'
|
||||||
|
|
||||||
|
// prevent path traversal
|
||||||
|
const safePath = new URL('.' + pathname, 'file:' + DIST).pathname
|
||||||
|
|
||||||
|
// Ensure file is inside dist
|
||||||
|
if (!safePath.startsWith(DIST)) {
|
||||||
|
return new Response('Not Found', { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = Bun.file(safePath)
|
||||||
|
const headers = new Headers()
|
||||||
|
headers.set('Content-Type', contentType(safePath))
|
||||||
|
// Localhost only; still set a permissive origin for local dev
|
||||||
|
headers.set('Access-Control-Allow-Origin', 'http://localhost')
|
||||||
|
return new Response(file.stream(), { status: 200, headers })
|
||||||
|
} catch (e) {
|
||||||
|
return new Response('Not Found', { status: 404 })
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return new Response('Internal Server Error', { status: 500 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Bun static server running at http://127.0.0.1:8000 serving ./dist')
|
||||||
@@ -44,7 +44,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
base: '/', // Always use root, since we're Cloudflare Pages only
|
base: process.env.VITE_BASE_PATH || './', // Use relative paths for offline compatibility
|
||||||
publicDir: 'public', // ← Explicitly set (should be default)
|
publicDir: 'public', // ← Explicitly set (should be default)
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
|
|||||||
Reference in New Issue
Block a user