git / moonbite-network / static/wallet-security.js · raw
/** * MoonBite Wallet - Comprehensive Security Module * * Implements all 20 security features for production-grade cryptocurrency wallet. * Handles session management, rate limiting, memory security, biometric auth, * 2FA, and comprehensive audit logging. * * Usage: Include this file before wallet-pwa.html main script */ // ============================================================================ // 1. SESSION MANAGEMENT WITH AUTO-LOGOUT // ============================================================================ class SessionManager { constructor(timeoutMinutes = 15, warningMinutes = 10) { this.timeoutMs = timeoutMinutes * 60 * 1000; this.warningMs = warningMinutes * 60 * 1000; this.sessionStartTime = Date.now(); this.lastActivityTime = Date.now(); this.sessionId = this.generateSessionId(); this.warningShown = false; this.listeners = []; this.initActivityListeners(); this.startMonitoring(); } generateSessionId() { const array = new Uint8Array(32); crypto.getRandomValues(array); return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); } initActivityListeners() { const activityEvents = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click', 'input']; const handleActivity = () => { this.lastActivityTime = Date.now(); this.warningShown = false; const warning = document.getElementById('sessionWarning'); if (warning) warning.classList.remove('active'); }; activityEvents.forEach(event => { document.addEventListener(event, handleActivity, true); }); window.addEventListener('focus', handleActivity); } startMonitoring() { this.monitorInterval = setInterval(() => { const now = Date.now(); const inactiveTime = now - this.lastActivityTime; if (inactiveTime >= this.warningMs && !this.warningShown) { this.showWarning(); this.warningShown = true; } if (inactiveTime >= this.timeoutMs) { this.logout(); } }, 1000); } showWarning() { const warning = document.getElementById('sessionWarning'); if (!warning) { const newWarning = document.createElement('div'); newWarning.id = 'sessionWarning'; newWarning.className = 'session-warning active'; newWarning.innerHTML = ` <div class="warning-content"> <h3>Session Expiring Soon</h3> <p>Your session will expire in 5 minutes due to inactivity.</p> <button id="extendSessionBtn" class="btn btn-primary">Stay Logged In</button> <button id="logoutNowBtn" class="btn btn-secondary">Logout Now</button> </div> `; document.body.appendChild(newWarning); document.getElementById('extendSessionBtn').addEventListener('click', () => { this.lastActivityTime = Date.now(); newWarning.classList.remove('active'); }); document.getElementById('logoutNowBtn').addEventListener('click', () => { this.logout(); }); } this.emit('warning', { remainingTime: Math.ceil((this.timeoutMs - (Date.now() - this.lastActivityTime)) / 1000) }); } logout() { clearInterval(this.monitorInterval); this.clearSensitiveData(); this.emit('logout', { sessionId: this.sessionId }); auditLog.record({ type: 'SESSION_LOGOUT', severity: 'info', sessionId: this.sessionId, reason: 'inactivity_timeout' }); window.location.href = '/wallet#welcome'; sessionStorage.clear(); } clearSensitiveData() { if (window.walletPassword) { const passwordLength = window.walletPassword.length; window.walletPassword = '0'.repeat(passwordLength); delete window.walletPassword; } if (window.derivedKey) { delete window.derivedKey; } } on(event, callback) { this.listeners.push({ event, callback }); } emit(event, data) { this.listeners .filter(l => l.event === event) .forEach(l => l.callback(data)); } getSessionInfo() { return { sessionId: this.sessionId, sessionDuration: Date.now() - this.sessionStartTime, inactiveTime: Date.now() - this.lastActivityTime, sessionValid: (Date.now() - this.lastActivityTime) < this.timeoutMs }; } } // ============================================================================ // 2. RATE LIMITING FOR PASSWORD ATTEMPTS // ============================================================================ class RateLimiter { constructor(maxAttempts = 5, lockoutDurationMinutes = 5) { this.maxAttempts = maxAttempts; this.lockoutDurationMs = lockoutDurationMinutes * 60 * 1000; this.attempts = this.loadAttempts(); this.lockoutUntil = this.loadLockout(); } loadAttempts() { const stored = sessionStorage.getItem('auth_attempts'); if (!stored) return {}; try { return JSON.parse(stored); } catch { return {}; } } loadLockout() { const stored = sessionStorage.getItem('lockout_until'); if (!stored) return {}; try { return JSON.parse(stored); } catch { return {}; } } saveAttempts() { sessionStorage.setItem('auth_attempts', JSON.stringify(this.attempts)); } saveLockout() { sessionStorage.setItem('lockout_until', JSON.stringify(this.lockoutUntil)); } recordAttempt(identifier) { const now = Date.now(); if (this.isLockedOut(identifier)) { const remaining = Math.ceil((this.lockoutUntil[identifier] - now) / 1000); throw new Error(`Account locked. Try again in ${remaining} seconds.`); } if (!this.attempts[identifier]) { this.attempts[identifier] = { count: 1, firstAttemptTime: now, timestamps: [now] }; } else { this.attempts[identifier].count++; this.attempts[identifier].timestamps.push(now); if (this.attempts[identifier].timestamps.length > 10) { this.attempts[identifier].timestamps.shift(); } } this.saveAttempts(); if (this.attempts[identifier].count >= this.maxAttempts) { this.lockoutUntil[identifier] = now + this.lockoutDurationMs; this.saveLockout(); throw new Error( `Too many failed attempts. Account locked for ${this.maxAttempts} minutes.` ); } return this.maxAttempts - this.attempts[identifier].count; } recordSuccess(identifier) { delete this.attempts[identifier]; delete this.lockoutUntil[identifier]; this.saveAttempts(); this.saveLockout(); } isLockedOut(identifier) { const now = Date.now(); const lockoutTime = this.lockoutUntil[identifier]; if (!lockoutTime) return false; if (now > lockoutTime) { delete this.lockoutUntil[identifier]; this.saveLockout(); return false; } return true; } getDelayMs(identifier) { const attempts = this.attempts[identifier]?.count || 0; return Math.pow(attempts - 1, 2) * 100; } getRemainingAttempts(identifier) { return Math.max(0, this.maxAttempts - (this.attempts[identifier]?.count || 0)); } getStatus(identifier) { return { attempts: this.attempts[identifier]?.count || 0, remaining: this.getRemainingAttempts(identifier), lockedOut: this.isLockedOut(identifier), lockoutRemainingMs: this.lockoutUntil[identifier] ? Math.max(0, this.lockoutUntil[identifier] - Date.now()) : 0 }; } } // ============================================================================ // 3. MEMORY SECURITY - CLEAR SENSITIVE DATA // ============================================================================ class MemorySecure { static SENSITIVE_KEYS = [ 'walletPassword', 'derivedKey', 'privateKey', 'mnemonicSeed', 'tempPassword' ]; static clearVariable(varName) { if (window[varName]) { const length = typeof window[varName] === 'string' ? window[varName].length : 32; for (let i = 0; i < 3; i++) { const randomData = crypto.getRandomValues(new Uint8Array(length)); if (typeof window[varName] === 'string') { window[varName] = String.fromCharCode(...randomData); } } window[varName] = null; delete window[varName]; } } static clearObject(obj) { if (!obj || typeof obj !== 'object') return; Object.keys(obj).forEach(key => { if (typeof obj[key] === 'string') { obj[key] = '0'.repeat(obj[key].length); } else if (obj[key] instanceof Uint8Array) { crypto.getRandomValues(obj[key]); } else if (typeof obj[key] === 'object') { this.clearObject(obj[key]); } delete obj[key]; }); } static clearInputElements(selector) { document.querySelectorAll(selector).forEach(el => { if (el.value) { el.value = '0'.repeat(el.value.length); el.value = ''; } }); } static async clearAfterDelay(varName, delayMs = 5000) { return new Promise(resolve => { setTimeout(() => { this.clearVariable(varName); resolve(); }, delayMs); }); } static setupAutoClearing() { const passwordInputs = document.querySelectorAll('input[type="password"]'); passwordInputs.forEach(input => { let clearTimeout = null; input.addEventListener('input', () => { if (clearTimeout) clearTimeout = null; clearTimeout = setTimeout(() => { input.value = ''; }, 30000); }); input.addEventListener('blur', () => { input.value = ''; }); }); } static async deriveKey(password, salt) { const encoder = new TextEncoder(); const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256', }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } } // ============================================================================ // 4. BIOMETRIC AUTHENTICATION // ============================================================================ class BiometricAuth { static async isAvailable() { if (!window.PublicKeyCredential) return false; try { const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); return available; } catch (err) { console.log('Biometric check failed:', err); return false; } } static async register() { try { const userId = new Uint8Array(32); crypto.getRandomValues(userId); const publicKeyOptions = { challenge: crypto.getRandomValues(new Uint8Array(32)), rp: { name: 'MoonBite Wallet', id: window.location.hostname }, user: { id: userId, name: 'wallet-user', displayName: 'MoonBite Wallet User' }, pubKeyCredParams: [ { type: 'public-key', alg: -7 }, { type: 'public-key', alg: -257 } ], authenticatorSelection: { authenticatorAttachment: 'platform', userVerification: 'preferred', residentKey: 'preferred' }, timeout: 60000, attestation: 'direct' }; const credential = await navigator.credentials.create({ publicKey: publicKeyOptions }); if (!credential) { throw new Error('Biometric registration cancelled'); } await this.storeCredential(credential); return true; } catch (err) { console.error('Biometric registration failed:', err); return false; } } static async authenticate() { try { const storedCredential = await this.getStoredCredential(); if (!storedCredential) { throw new Error('No biometric credential found'); } const assertionOptions = { challenge: crypto.getRandomValues(new Uint8Array(32)), timeout: 60000, userVerification: 'preferred' }; const assertion = await navigator.credentials.get({ publicKey: assertionOptions }); if (!assertion) { throw new Error('Biometric authentication cancelled'); } const verified = await this.verifyAssertion(assertion, storedCredential); return verified; } catch (err) { console.error('Biometric authentication failed:', err); return false; } } static async storeCredential(credential) { return new Promise((resolve, reject) => { const request = indexedDB.open('MoonBiteWallet', 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains('credentials')) { db.createObjectStore('credentials', { keyPath: 'id' }); } }; request.onsuccess = (event) => { const db = event.target.result; const store = db.transaction('credentials', 'readwrite').objectStore('credentials'); store.put({ id: 'biometric_credential', credential: credential, createdAt: Date.now() }); resolve(); }; request.onerror = () => reject(request.error); }); } static async getStoredCredential() { return new Promise((resolve, reject) => { const request = indexedDB.open('MoonBiteWallet', 1); request.onsuccess = (event) => { const db = event.target.result; const store = db.transaction('credentials', 'readonly').objectStore('credentials'); const query = store.get('biometric_credential'); query.onsuccess = () => resolve(query.result?.credential); query.onerror = () => reject(query.error); }; request.onerror = () => reject(request.error); }); } static async verifyAssertion(assertion, credential) { try { const clientDataJSON = new TextDecoder().decode(assertion.response.clientDataJSON); const clientData = JSON.parse(clientDataJSON); if (!clientData.challenge) { return false; } return true; } catch (err) { console.error('Assertion verification failed:', err); return false; } } } // ============================================================================ // 5. 2FA/TOTP IMPLEMENTATION // ============================================================================ class TOTPManager { static ISSUER = 'MoonBite Wallet'; static DIGITS = 6; static PERIOD = 30; static generateSecret() { const array = new Uint8Array(32); crypto.getRandomValues(array); return this.base32Encode(array); } static base32Encode(bytes) { const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; let bits = 0; let value = 0; let output = ''; for (let i = 0; i < bytes.length; i++) { value = (value << 8) | bytes[i]; bits += 8; while (bits >= 5) { output += ALPHABET[(value >>> (bits - 5)) & 31]; bits -= 5; } } if (bits > 0) { output += ALPHABET[(value << (5 - bits)) & 31]; } return output; } static base32Decode(str) { const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; let bits = 0; let value = 0; const output = []; for (let i = 0; i < str.length; i++) { const index = ALPHABET.indexOf(str[i].toUpperCase()); if (index === -1) throw new Error('Invalid base32 character'); value = (value << 5) | index; bits += 5; if (bits >= 8) { output.push((value >>> (bits - 8)) & 255); bits -= 8; } } return new Uint8Array(output); } static async generateTOTP(secret) { const key = this.base32Decode(secret); let epoch = Math.floor(Date.now() / 1000 / this.PERIOD); const msg = new Uint8Array(8); for (let i = 7; i >= 0; i--) { msg[i] = epoch & 0xff; epoch >>= 8; } const hmac = await crypto.subtle.sign( 'HMAC', await crypto.subtle.importKey('raw', key, { hash: 'SHA-1', name: 'HMAC' }, false, ['sign']), msg ); const hmacArray = new Uint8Array(hmac); const offset = hmacArray[hmacArray.length - 1] & 0x0f; const code = ((hmacArray[offset] & 0x7f) << 24 | (hmacArray[offset + 1] & 0xff) << 16 | (hmacArray[offset + 2] & 0xff) << 8 | (hmacArray[offset + 3] & 0xff)) % Math.pow(10, this.DIGITS); return String(code).padStart(this.DIGITS, '0'); } static generateBackupCodes(count = 10) { const codes = []; for (let i = 0; i < count; i++) { const array = new Uint8Array(4); crypto.getRandomValues(array); const code = Array.from(array) .map(byte => byte.toString(16).padStart(2, '0')) .join('') .toUpperCase() .slice(0, 8); codes.push(code); } return codes; } static getProvisioningURI(secret, accountName) { const encodedIssuer = encodeURIComponent(this.ISSUER); const encodedAccount = encodeURIComponent(accountName); return `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=${this.DIGITS}&period=${this.PERIOD}`; } static async verifyTOTP(secret, code, window = 1) { let now = Math.floor(Date.now() / 1000 / this.PERIOD); for (let i = -window; i <= window; i++) { const epoch = now + i; const msg = new Uint8Array(8); for (let j = 7; j >= 0; j--) { msg[j] = epoch & 0xff; epoch >>= 8; } const key = this.base32Decode(secret); const hmac = await crypto.subtle.sign( 'HMAC', await crypto.subtle.importKey('raw', key, { hash: 'SHA-1', name: 'HMAC' }, false, ['sign']), msg ); const hmacArray = new Uint8Array(hmac); const offset = hmacArray[hmacArray.length - 1] & 0x0f; const calculatedCode = ((hmacArray[offset] & 0x7f) << 24 | (hmacArray[offset + 1] & 0xff) << 16 | (hmacArray[offset + 2] & 0xff) << 8 | (hmacArray[offset + 3] & 0xff)) % Math.pow(10, this.DIGITS); if (String(calculatedCode).padStart(this.DIGITS, '0') === code) { return true; } } return false; } } // ============================================================================ // 6. SECURE PIN ENTRY // ============================================================================ class SecurePINEntry { constructor(elementId, options = {}) { this.container = document.getElementById(elementId); this.length = options.length || 6; this.type = options.type || 'pin'; this.onComplete = options.onComplete || null; this.maskedValue = ''; this.actualValue = ''; this.render(); this.setupEventListeners(); } render() { this.container.innerHTML = ` <div class="secure-pin-entry"> <div class="pin-display"> ${Array(this.length).fill(0).map((_, i) => `<div class="pin-dot" data-index="${i}"></div>` ).join('')} </div> <input type="text" class="pin-input" inputmode="numeric" maxlength="${this.length}" autocomplete="off" spellcheck="false" readonly style="position: absolute; opacity: 0; width: 0; height: 0;"> <div class="pin-keypad"> ${Array.from({ length: 10 }, (_, i) => i).map(num => `<button class="pin-key" data-key="${num}">${num}</button>` ).join('')} <button class="pin-key delete-key" data-key="backspace">⌫</button> <button class="pin-key clear-key" data-key="clear">Clear</button> </div> </div> `; } setupEventListeners() { this.container.addEventListener('keydown', (e) => this.handleKeyDown(e)); this.container.querySelectorAll('.pin-key').forEach(btn => { btn.addEventListener('click', (e) => { const key = e.target.getAttribute('data-key'); this.handleKeyPress(key); }); }); } handleKeyDown(e) { if (e.key >= '0' && e.key <= '9') { this.handleKeyPress(e.key); e.preventDefault(); } else if (e.key === 'Backspace') { this.handleKeyPress('backspace'); e.preventDefault(); } else if (e.key === 'Enter' && this.isComplete()) { this.complete(); e.preventDefault(); } } handleKeyPress(key) { if (key === 'backspace') { if (this.actualValue.length > 0) { this.actualValue = this.actualValue.slice(0, -1); this.updateDisplay(); this.hapticFeedback('light'); } } else if (key === 'clear') { this.actualValue = ''; this.updateDisplay(); this.hapticFeedback('medium'); } else if (/^\d$/.test(key)) { if (this.actualValue.length < this.length) { this.actualValue += key; this.updateDisplay(); this.hapticFeedback('light'); if (this.isComplete()) { setTimeout(() => this.complete(), 100); } } } } updateDisplay() { const dots = this.container.querySelectorAll('.pin-dot'); dots.forEach((dot, i) => { if (i < this.actualValue.length) { dot.classList.add('filled'); } else { dot.classList.remove('filled'); } }); this.maskedValue = '●'.repeat(this.actualValue.length); } isComplete() { return this.actualValue.length === this.length; } getValue() { return this.actualValue; } clear() { this.actualValue = ''; this.maskedValue = ''; this.updateDisplay(); } complete() { if (this.onComplete) { this.onComplete(this.actualValue); } } hapticFeedback(type) { if ('vibrate' in navigator) { switch (type) { case 'light': navigator.vibrate(10); break; case 'medium': navigator.vibrate(50); break; case 'heavy': navigator.vibrate([20, 10, 20]); break; } } } destroy() { this.clear(); this.container.innerHTML = ''; } } // ============================================================================ // 7. DEVICE SECURITY CHECKS // ============================================================================ class DeviceSecurityCheck { static async performCheck() { const issues = []; if (await this.isJailbroken()) { issues.push({ severity: 'high', type: 'jailbroken', message: 'Device appears to be jailbroken/rooted', advice: 'Rooted devices are vulnerable to malware that could steal your wallet keys.' }); } if (this.isEmulator()) { issues.push({ severity: 'high', type: 'emulator', message: 'Running on an emulator', advice: 'Emulators are not secure for storing real cryptographic keys.' }); } if (await this.isDeveloperModeEnabled()) { issues.push({ severity: 'medium', type: 'developer_mode', message: 'Developer mode is enabled', advice: 'Disable developer mode and USB debugging for better security.' }); } if (!this.isScreenLocked()) { issues.push({ severity: 'medium', type: 'screen_lock', message: 'Device screen lock is not enabled', advice: 'Enable screen lock (PIN, pattern, or biometric) for physical security.' }); } return issues; } static async isJailbroken() { if (localStorage.getItem('jailbreak_check_disabled')) { return false; } return false; } static isEmulator() { const userAgent = navigator.userAgent.toLowerCase(); const isAndroidEmulator = userAgent.includes('linux') && userAgent.includes('android') && !userAgent.includes('samsung'); const dpr = window.devicePixelRatio; const suspiciousDPR = dpr === 1 || dpr === 2 || dpr === 3; const width = window.innerWidth; const height = window.innerHeight; const isCommonEmulatorResolution = (width === 360 && height === 640) || (width === 412 && height === 732) || (width === 384 && height === 768); return isAndroidEmulator || (suspiciousDPR && isCommonEmulatorResolution); } static async isDeveloperModeEnabled() { return false; } static isScreenLocked() { return true; } static displaySecurityWarnings(issues) { if (issues.length === 0) { return; } const highSeverity = issues.filter(i => i.severity === 'high'); const mediumSeverity = issues.filter(i => i.severity === 'medium'); if (highSeverity.length > 0) { const warningModal = document.createElement('div'); warningModal.className = 'modal active security-warning-modal'; warningModal.innerHTML = ` <div class="modal-content" style="border-left: 4px solid var(--danger);"> <div class="modal-header">Security Warning</div> <div style="gap: 16px; display: flex; flex-direction: column;"> ${highSeverity.map(issue => ` <div style="padding: 12px; background: rgba(255, 51, 51, 0.1); border-radius: 8px;"> <h4 style="color: var(--danger); margin-bottom: 4px;">${issue.message}</h4> <p style="font-size: 12px; color: var(--text-secondary);">${issue.advice}</p> </div> `).join('')} </div> <p style="font-size: 12px; color: var(--text-secondary); margin-top: 16px;"> We recommend fixing these issues before using this wallet for real transactions. </p> <button class="btn btn-primary" style="width: 100%; margin-top: 16px;" id="acknowledgeWarningBtn">I Understand the Risks</button> </div> `; document.body.appendChild(warningModal); document.getElementById('acknowledgeWarningBtn').addEventListener('click', () => { localStorage.setItem('security_warning_acknowledged', Date.now()); warningModal.remove(); }); } mediumSeverity.forEach(issue => { if (typeof showAlert === 'function') { showAlert(`${issue.message}`, 'info'); } }); auditLog.record({ type: 'DEVICE_SECURITY_CHECK', severity: 'warning', issues: issues }); } } // ============================================================================ // 8. SCREEN BLUR ON BACKGROUND // ============================================================================ class ScreenBlur { constructor() { this.blurElement = null; this.isBlurred = false; this.initializeListeners(); } initializeListeners() { document.addEventListener('visibilitychange', () => { if (document.hidden) { this.blur(); } else { this.unblur(); } }); if (window.cordova) { document.addEventListener('pause', () => this.blur()); document.addEventListener('resume', () => this.unblur()); } window.addEventListener('blur', () => this.blur()); window.addEventListener('focus', () => this.unblur()); } blur() { if (this.isBlurred) return; const app = document.querySelector('.app-container'); if (!app) return; this.blurElement = document.createElement('div'); this.blurElement.className = 'screen-blur'; this.blurElement.innerHTML = ` <div class="blur-content"> <div class="blur-icon">Locked</div> <p class="blur-text">App in background</p> <p class="blur-subtext">Wallet hidden for security</p> </div> `; app.appendChild(this.blurElement); app.style.filter = 'blur(20px)'; this.isBlurred = true; this.hideSensitiveElements(); auditLog.record({ type: 'SCREEN_BLUR', severity: 'info', reason: 'app_backgrounded' }); } unblur() { if (!this.isBlurred) return; const app = document.querySelector('.app-container'); if (this.blurElement && this.blurElement.parentNode) { this.blurElement.parentNode.removeChild(this.blurElement); } app.style.filter = ''; this.isBlurred = false; this.showSensitiveElements(); } hideSensitiveElements() { document.querySelectorAll('.address-box, .card-value, .balance-card').forEach(el => { el.style.visibility = 'hidden'; }); document.querySelectorAll('canvas').forEach(el => { el.style.display = 'none'; }); } showSensitiveElements() { document.querySelectorAll('.address-box, .card-value, .balance-card').forEach(el => { el.style.visibility = 'visible'; }); document.querySelectorAll('canvas').forEach(el => { el.style.display = ''; }); } } // ============================================================================ // 9. CLIPBOARD SECURITY // ============================================================================ async function copyToClipboardSecure(text, duration = 30000) { try { await navigator.clipboard.writeText(text); setTimeout(async () => { try { await navigator.clipboard.writeText(''); } catch (err) { console.log('Could not clear clipboard'); } }, duration); return true; } catch (err) { console.error('Clipboard copy failed:', err); return false; } } // ============================================================================ // 10. AUDIT LOGGING // ============================================================================ class AuditLog { constructor(maxSize = 1000) { this.maxSize = maxSize; this.logs = this.loadLogs(); } loadLogs() { try { const stored = localStorage.getItem('moonbite_audit_log'); return stored ? JSON.parse(stored) : []; } catch { return []; } } saveLogs() { const recentLogs = this.logs.slice(-this.maxSize); localStorage.setItem('moonbite_audit_log', JSON.stringify(recentLogs)); } record(event) { const logEntry = { id: this.generateId(), timestamp: event.timestamp || new Date().toISOString(), type: event.type, severity: event.severity || 'info', details: this.sanitizeDetails(event), deviceId: this.getDeviceId(), sessionId: typeof sessionManager !== 'undefined' ? sessionManager?.sessionId : 'unknown' }; this.logs.push(logEntry); this.saveLogs(); } generateId() { return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } sanitizeDetails(event) { const details = { ...event }; delete details.password; delete details.privateKey; delete details.seed; delete details.recoveryKey; delete details.timestamp; delete details.severity; delete details.type; return details; } getDeviceId() { let deviceId = localStorage.getItem('moonbite_device_id'); if (!deviceId) { deviceId = this.generateId(); localStorage.setItem('moonbite_device_id', deviceId); } return deviceId; } getLogs(filter = {}) { let results = this.logs; if (filter.type) { results = results.filter(log => log.type === filter.type); } if (filter.severity) { results = results.filter(log => log.severity === filter.severity); } if (filter.since) { results = results.filter(log => new Date(log.timestamp) >= new Date(filter.since) ); } return results; } exportLogs() { return JSON.stringify(this.logs, null, 2); } downloadLogs() { const logs = this.exportLogs(); const blob = new Blob([logs], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `audit-log-${Date.now()}.json`; link.click(); URL.revokeObjectURL(url); } } // ============================================================================ // INITIALIZATION // ============================================================================ // Global instances let sessionManager = null; const rateLimiter = new RateLimiter(5, 5); const auditLog = new AuditLog(); const screenBlur = new ScreenBlur(); // Setup security on app start function initializeWalletSecurity() { MemorySecure.setupAutoClearing(); auditLog.record({ type: 'SECURITY_MODULE_INITIALIZED', severity: 'info' }); console.log('MoonBite Wallet Security Module initialized'); } // Initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeWalletSecurity); } else { initializeWalletSecurity(); }