Implement security patches: CSP headers, console disabling, key rotation, clipboard security, network blocking, log cleanup, and PGP validation

This commit is contained in:
LC mac
2026-02-12 02:24:06 +08:00
parent 20cf558e83
commit 6c6379fcd4
11 changed files with 3365 additions and 135 deletions

View File

@@ -1,26 +1,11 @@
import { useState, useEffect, useCallback } from 'react';
import {
QrCode,
RefreshCw,
CheckCircle2,
Lock,
AlertCircle,
Camera,
Dices,
Mic,
Unlock,
EyeOff,
FileKey,
Info,
} from 'lucide-react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { QrCode, RefreshCw, CheckCircle2, Lock, AlertCircle, Camera, Dices, Mic, Unlock, EyeOff, FileKey, Info } from 'lucide-react';
import { PgpKeyInput } from './components/PgpKeyInput';
import { useRef } from 'react';
import { QrDisplay } from './components/QrDisplay';
import QRScanner from './components/QRScanner';
import { validateBip39Mnemonic } from './lib/bip39';
import { buildPlaintext, encryptToSeed, decryptFromSeed, detectEncryptionMode } from './lib/seedpgp';
import { buildPlaintext, encryptToSeed, decryptFromSeed, detectEncryptionMode, validatePGPKey } from './lib/seedpgp';
import { encodeStandardSeedQR, encodeCompactSeedQREntropy } from './lib/seedqr';
import * as openpgp from 'openpgp';
import { SecurityWarnings } from './components/SecurityWarnings';
import { getSessionKey, encryptJsonToBlob, destroySessionKey, EncryptedBlob } from './lib/sessionCrypto';
import { EncryptionMode, EncryptionResult } from './lib/types'; // Import EncryptionMode and EncryptionResult
@@ -34,7 +19,6 @@ import DiceEntropy from './components/DiceEntropy';
import { InteractionEntropy } from './lib/interactionEntropy';
import AudioEntropy from './AudioEntropy';
console.log("OpenPGP.js version:", openpgp.config.versionString);
interface StorageItem {
key: string;
@@ -241,7 +225,7 @@ function App() {
};
const copyToClipboard = async (text: string | Uint8Array) => {
const copyToClipboard = async (text: string | Uint8Array, fieldName = 'Data') => {
if (isReadOnly) {
setError("Copy to clipboard is disabled in Read-only mode.");
return;
@@ -252,6 +236,36 @@ function App() {
try {
await navigator.clipboard.writeText(textToCopy);
setCopied(true);
// Add warning for sensitive data
const isSensitive = fieldName.toLowerCase().includes('mnemonic') ||
fieldName.toLowerCase().includes('seed') ||
fieldName.toLowerCase().includes('password') ||
fieldName.toLowerCase().includes('key');
if (isSensitive) {
setClipboardEvents(prev => [
{
timestamp: new Date(),
field: `${fieldName} (will clear in 10s)`,
length: textToCopy.length
},
...prev.slice(0, 9)
]);
// Auto-clear clipboard after 10 seconds by writing random data
setTimeout(async () => {
try {
const garbage = crypto.getRandomValues(new Uint8Array(Math.max(textToCopy.length, 64)))
.reduce((s, b) => s + String.fromCharCode(32 + (b % 95)), '');
await navigator.clipboard.writeText(garbage);
} catch { }
}, 10000);
// Show warning
alert(`⚠️ ${fieldName} copied to clipboard!\n\n✅ Will auto-clear in 10 seconds.\n\n🔒 Warning: Clipboard is accessible to other apps and browser extensions.`);
}
window.setTimeout(() => setCopied(false), 1500);
} catch {
const ta = document.createElement("textarea");
@@ -297,7 +311,7 @@ function App() {
setRecipientFpr('');
try {
const validation = validateBip39Mnemonic(mnemonic);
const validation = await validateBip39Mnemonic(mnemonic);
if (!validation.valid) {
throw new Error(validation.error);
}
@@ -308,21 +322,21 @@ function App() {
if (encryptionMode === 'seedqr') {
if (seedQrFormat === 'standard') {
const qrString = await encodeStandardSeedQR(mnemonic);
console.log('📋 Standard SeedQR generated:', qrString.slice(0, 50) + '...');
result = { framed: qrString };
} else { // compact
const qrEntropy = await encodeCompactSeedQREntropy(mnemonic);
console.log('🔐 Compact SeedQR generated:');
console.log(' - Type:', qrEntropy instanceof Uint8Array ? 'Uint8Array' : typeof qrEntropy);
console.log(' - Length:', qrEntropy.length);
console.log(' - Hex:', Array.from(qrEntropy).map(b => b.toString(16).padStart(2, '0')).join(''));
console.log(' - First 16 bytes:', Array.from(qrEntropy.slice(0, 16)));
result = { framed: qrEntropy }; // framed will hold the Uint8Array
}
} else {
// Existing PGP and Krux encryption
// Validate PGP public key before encryption
if (publicKeyInput) {
const validation = await validatePGPKey(publicKeyInput);
if (!validation.valid) {
throw new Error(`PGP Key Validation Failed: ${validation.error}`);
}
}
// Encrypt with PGP or Krux
result = await encryptToSeed({
plaintext,
publicKeyArmored: publicKeyInput || undefined,
@@ -467,25 +481,88 @@ function App() {
}
};
const blockAllNetworks = () => {
// Store originals
(window as any).__originalFetch = window.fetch;
(window as any).__originalXHR = window.XMLHttpRequest;
(window as any).__originalWS = window.WebSocket;
(window as any).__originalImage = window.Image;
if ((navigator as any).sendBeacon) {
(window as any).__originalBeacon = navigator.sendBeacon;
}
// 1. Block fetch
window.fetch = (async () =>
Promise.reject(new Error('Network blocked by user'))
) as any;
// 2. Block XMLHttpRequest
window.XMLHttpRequest = new Proxy(XMLHttpRequest, {
construct() {
throw new Error('Network blocked: XMLHttpRequest not allowed');
}
}) as any;
// 3. Block WebSocket
window.WebSocket = new Proxy(WebSocket, {
construct() {
throw new Error('Network blocked: WebSocket not allowed');
}
}) as any;
// 4. Block BeaconAPI
(navigator as any).sendBeacon = () => {
return false;
};
// 5. Block Image src for external resources
const OriginalImage = window.Image;
window.Image = new Proxy(OriginalImage, {
construct(target) {
const img = Reflect.construct(target, []);
const originalSrcSetter = Object.getOwnPropertyDescriptor(
HTMLImageElement.prototype, 'src'
)?.set;
Object.defineProperty(img, 'src', {
configurable: true,
set(value) {
if (value && !value.startsWith('data:') && !value.startsWith('blob:')) {
throw new Error(`Network blocked: cannot load external resource`);
}
originalSrcSetter?.call(this, value);
},
get: Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src')?.get
});
return img;
}
}) as any;
// 6. Block Service Workers
if (navigator.serviceWorker) {
(navigator.serviceWorker as any).register = async () => {
throw new Error('Network blocked: Service Workers disabled');
};
}
};
const unblockAllNetworks = () => {
// Restore everything
if ((window as any).__originalFetch) window.fetch = (window as any).__originalFetch;
if ((window as any).__originalXHR) window.XMLHttpRequest = (window as any).__originalXHR;
if ((window as any).__originalWS) window.WebSocket = (window as any).__originalWS;
if ((window as any).__originalImage) window.Image = (window as any).__originalImage;
if ((window as any).__originalBeacon) navigator.sendBeacon = (window as any).__originalBeacon;
};
const handleToggleNetwork = () => {
setIsNetworkBlocked(!isNetworkBlocked);
if (!isNetworkBlocked) {
// Block network
console.log('🚫 Network BLOCKED - No external requests allowed');
// Optional: Override fetch/XMLHttpRequest
if (typeof window !== 'undefined') {
(window as any).__originalFetch = window.fetch;
// Create a mock fetch function with proper type assertion
const mockFetch = (async () => Promise.reject(new Error('Network blocked by user'))) as unknown as typeof window.fetch;
window.fetch = mockFetch;
}
blockAllNetworks();
} else {
// Unblock network
console.log('🌐 Network ACTIVE');
if ((window as any).__originalFetch) {
window.fetch = (window as any).__originalFetch;
}
unblockAllNetworks();
}
};
@@ -526,7 +603,6 @@ function App() {
// Go to Create tab (fresh start)
setActiveTab('create');
console.log('✅ All data reset');
}
};