UNPKG

ailock

Version:

AI-Proof File Guard - Protect sensitive files from accidental AI modifications

202 lines 7.27 kB
import { readFile, writeFile, mkdir } from 'fs/promises'; import { existsSync } from 'fs'; import { join } from 'path'; import { homedir, platform, arch, networkInterfaces } from 'os'; import { createHash, randomUUID } from 'crypto'; /** * Get the ailock configuration directory */ export function getAilockConfigDir() { return join(homedir(), '.ailock'); } /** * Get the machine UUID file path */ export function getMachineUuidPath() { return join(getAilockConfigDir(), 'machine-uuid'); } /** * Generate a stable machine identifier based on system characteristics * Falls back to crypto.randomUUID() if system info unavailable */ export function generateMachineId() { try { // Collect system characteristics for stable ID generation const systemInfo = { platform: platform(), arch: arch(), hostname: process.env.HOSTNAME || process.env.COMPUTERNAME || 'unknown' }; // Get network interface MAC addresses for additional entropy const interfaces = networkInterfaces(); const macAddresses = []; for (const interfaceName in interfaces) { const iface = interfaces[interfaceName]; if (iface) { for (const config of iface) { if (config.mac && config.mac !== '00:00:00:00:00:00') { macAddresses.push(config.mac); } } } } // Create deterministic hash from system info if (macAddresses.length > 0) { const combinedInfo = JSON.stringify({ ...systemInfo, mac: macAddresses.sort().join(':') // Sort for consistency }); const hash = createHash('sha256').update(combinedInfo).digest('hex'); // Take first 32 characters and format as UUID return [ hash.slice(0, 8), hash.slice(8, 12), hash.slice(12, 16), hash.slice(16, 20), hash.slice(20, 32) ].join('-'); } else { // Fallback to random UUID if no MAC addresses available return randomUUID(); } } catch (error) { // Final fallback - always generate a valid UUID return randomUUID(); } } /** * Get or create machine UUID * Returns a persistent machine identifier for analytics and API calls */ export async function getMachineUuid() { const configDir = getAilockConfigDir(); const uuidPath = getMachineUuidPath(); try { // Try to read existing UUID if (existsSync(uuidPath)) { const existingUuid = await readFile(uuidPath, 'utf-8'); const trimmedUuid = existingUuid.trim(); // Validate UUID format (basic check) if (trimmedUuid.length > 0 && /^[0-9a-f-]{36}$/i.test(trimmedUuid)) { return trimmedUuid; } } // Generate new UUID if file doesn't exist or is invalid const newUuid = generateMachineId(); // Ensure config directory exists if (!existsSync(configDir)) { await mkdir(configDir, { recursive: true }); } // Write UUID to file with secure permissions await writeFile(uuidPath, newUuid, { encoding: 'utf-8', mode: 0o600 // Read/write for owner only }); return newUuid; } catch (error) { // If all else fails, return a session UUID (not persisted) console.warn('Warning: Could not persist machine UUID, using session UUID'); return randomUUID(); } } /** * Clear the stored machine UUID (useful for testing) */ export async function clearMachineUuid() { const uuidPath = getMachineUuidPath(); if (existsSync(uuidPath)) { const fs = await import('fs/promises'); await fs.unlink(uuidPath); } } /** * Sanitize string for safe logging/transmission * Removes potential sensitive information */ export function sanitizeForLogging(input) { if (!input) return ''; // Hash any potential UUIDs for privacy const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi; let sanitized = input.replace(uuidRegex, (match) => { const hash = createHash('sha256').update(match).digest('hex'); return `uuid_${hash.slice(0, 8)}`; }); // Hash any potential file paths longer than reasonable length const pathRegex = /(?:\/[^\/\s]+){3,}|(?:[A-Z]:\\[^\\s]+){2,}/g; sanitized = sanitized.replace(pathRegex, (match) => { const hash = createHash('sha256').update(match).digest('hex'); return `path_${hash.slice(0, 8)}`; }); // Remove potential email addresses const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g; sanitized = sanitized.replace(emailRegex, 'email_***'); // Remove potential API keys or tokens const tokenRegex = /(?:api[_-]?key|token|secret|password)[^\s]*[=:]\s*[^\s]+/gi; sanitized = sanitized.replace(tokenRegex, 'credential_***'); return sanitized; } /** * Hash directory path for privacy-preserving analytics */ export function hashPathForAnalytics(path) { if (!path) return ''; // Create a privacy-preserving hash that still allows for some analytics // We hash the path but preserve some structural information const normalizedPath = path.replace(/\\/g, '/').toLowerCase(); const hash = createHash('sha256').update(normalizedPath).digest('hex'); // Return first 16 characters for compact representation return hash.slice(0, 16); } /** * Validate that a string doesn't contain sensitive information * Returns true if safe to log/transmit */ export function isSafeForLogging(input) { if (!input) return true; // Check for potential sensitive patterns const sensitivePatterns = [ /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, // Email /(?:password|secret|key|token)[^\s]*[=:]/i, // Credentials /(?:sk_|pk_|rk_)[a-zA-Z0-9]+/, // API keys /Bearer\s+[a-zA-Z0-9_.-]+/i, // Bearer tokens /(?:\/home\/|\/Users\/|C:\\Users\\)[^\/\s]+/, // Home directories ]; return !sensitivePatterns.some(pattern => pattern.test(input)); } /** * Get machine fingerprint for security validation * Used to detect if configuration has been moved between machines */ export function getMachineFingerprint() { try { const systemInfo = { platform: platform(), arch: arch(), // Don't include hostname as it's too identifying }; const hash = createHash('sha256') .update(JSON.stringify(systemInfo)) .digest('hex'); return hash.slice(0, 16); // Short fingerprint } catch { return 'unknown'; } } /** * Validate machine UUID format */ export function isValidMachineUuid(uuid) { if (!uuid || typeof uuid !== 'string') return false; // Standard UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid.trim()); } //# sourceMappingURL=machine-id.js.map