diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0be554c --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 19da9dc..b22f222 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,49 @@ -# SeedPGP v1.4.5 +# SeedPGP v1.4.7 **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. +**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:** --- +## 🚦 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 **Before you start**: How much are you backing up? This determines your setup. @@ -263,7 +299,7 @@ You now have: ## πŸ›‘οΈ 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:** @@ -341,9 +377,9 @@ bun test:integration ## πŸ“– Technical Documentation -- [MEMORY_STRATEGY.md](MEMORY_STRATEGY.md) - Why JS can't zero memory and how SeedPGP defends -- [RECOVERY_PLAYBOOK.md](RECOVERY_PLAYBOOK.md) - Offline recovery instructions -- [SECURITY_AUDIT_REPORT.md](SECURITY_AUDIT_REPORT.md) - Full audit findings +- [MEMORY_STRATEGY.md](doc/MEMORY_STRATEGY.md) - Why JS can't zero memory and how SeedPGP defends +- [RECOVERY_PLAYBOOK.md](doc/RECOVERY_PLAYBOOK.md) - Offline recovery instructions +- [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) - **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 **Security Audited:** v1.4.4 (no exploits found) diff --git a/Screenshot 2026-02-08 at 21.14.14.png b/Screenshot 2026-02-08 at 21.14.14.png deleted file mode 100644 index ded40a3..0000000 Binary files a/Screenshot 2026-02-08 at 21.14.14.png and /dev/null differ diff --git a/GEMINI.md b/doc/GEMINI.md similarity index 99% rename from GEMINI.md rename to doc/GEMINI.md index 357c5bb..5b11c15 100644 --- a/GEMINI.md +++ b/doc/GEMINI.md @@ -2,7 +2,7 @@ ## 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 **Deploy**: Cloudflare Pages (private repo: `seedpgp-web`) **Live URL**: @@ -300,7 +300,7 @@ await window.runSessionCryptoTest() --- -## Current Version: v1.4.5 +## Current Version: v1.4.7 **Recent Changes (v1.4.5):** - Fixed QR Scanner bugs related to camera initialization and race conditions. diff --git a/IMPLEMENTATION_SUMMARY.md b/doc/IMPLEMENTATION_SUMMARY.md similarity index 100% rename from IMPLEMENTATION_SUMMARY.md rename to doc/IMPLEMENTATION_SUMMARY.md diff --git a/doc/LOCAL_TESTING_GUIDE.md b/doc/LOCAL_TESTING_GUIDE.md new file mode 100644 index 0000000..b12aeb5 --- /dev/null +++ b/doc/LOCAL_TESTING_GUIDE.md @@ -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 +- `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! diff --git a/MEMORY_STRATEGY.md b/doc/MEMORY_STRATEGY.md similarity index 100% rename from MEMORY_STRATEGY.md rename to doc/MEMORY_STRATEGY.md diff --git a/RECOVERY_PLAYBOOK.md b/doc/RECOVERY_PLAYBOOK.md similarity index 99% rename from RECOVERY_PLAYBOOK.md rename to doc/RECOVERY_PLAYBOOK.md index beac245..54309df 100644 --- a/RECOVERY_PLAYBOOK.md +++ b/doc/RECOVERY_PLAYBOOK.md @@ -1,6 +1,6 @@ ## 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.** πŸ”’ **Last Updated:** February 3, 2026 -**SeedPGP Version:** 1.4.4 +**SeedPGP Version:** 1.4.7 **Frame Example CRC:** 58B5 βœ“ **Test Recovery:** [ ] Completed [ ] Not Tested diff --git a/SECURITY_AUDIT_REPORT.md b/doc/SECURITY_AUDIT_REPORT.md similarity index 99% rename from SECURITY_AUDIT_REPORT.md rename to doc/SECURITY_AUDIT_REPORT.md index fe954d3..fd6b5a8 100644 --- a/SECURITY_AUDIT_REPORT.md +++ b/doc/SECURITY_AUDIT_REPORT.md @@ -1,7 +1,7 @@ # SeedPGP Web Application - Comprehensive Forensic Security Audit Report **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 **Severity Levels:** CRITICAL | HIGH | MEDIUM | LOW diff --git a/SECURITY_PATCHES.md b/doc/SECURITY_PATCHES.md similarity index 100% rename from SECURITY_PATCHES.md rename to doc/SECURITY_PATCHES.md diff --git a/doc/SERVE.md b/doc/SERVE.md new file mode 100644 index 0000000..0fa8620 --- /dev/null +++ b/doc/SERVE.md @@ -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. diff --git a/doc/TAILS_OFFLINE_PLAYBOOK.md b/doc/TAILS_OFFLINE_PLAYBOOK.md new file mode 100644 index 0000000..aa3059f --- /dev/null +++ b/doc/TAILS_OFFLINE_PLAYBOOK.md @@ -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 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//` + +### 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 # Replace 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 + +**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 (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 , assets load correctly. On Tails via , 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**: +- **seedpgp-web Security Audit**: See SECURITY_AUDIT_REPORT.md +- **BIP39 Standard**: +- **Air-gap Best Practices**: +- **Bun Documentation**: +- **Python HTTP Server**: + +--- + +## 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 diff --git a/package.json b/package.json index a2bd05f..0ec9422 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "preview": "vite preview", "typecheck": "tsc --noEmit", "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": { "@types/bip32": "^2.0.4", diff --git a/serve.ts b/serve.ts new file mode 100644 index 0000000..2551696 --- /dev/null +++ b/serve.ts @@ -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') diff --git a/vite.config.ts b/vite.config.ts index 266a627..95cdeb7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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) build: { outDir: 'dist',