#!/usr/bin/env node /** * F6 安全启动守卫 — 会话开始时自动检测并修复危险安全配置 * * 检测项: * - skipDangerousModePermissionPrompt === true → 自动修复为 false * * 触发: UserPromptSubmit hook (每次会话首次查询) * 耗时: <5ms (仅读写一个 JSON 文件) * 冷却: 使用 lockfile 防止同会话多次执行 */ const fs = require('fs'); const path = require('path'); const { logSecurityEvent } = require('./lib/security-log.js'); const ROOT = require('./lib/root.js'); const SETTINGS_FILE = path.join(ROOT, 'settings.json'); const LOCK_FILE = path.join(ROOT, 'debug', '.security-guard-lock'); /** * 核心守卫逻辑 (不读 stdin, 不调 process.exit) * 供 prompt-dispatcher 内联调用 * @returns {boolean} true=已执行守卫检查, false=冷却期跳过 */ function runGuard() { // 冷却: 同会话只执行一次 (lockfile 10分钟有效) try { if (fs.existsSync(LOCK_FILE)) { const lockAge = Date.now() - fs.statSync(LOCK_FILE).mtimeMs; if (lockAge < 10 * 60 * 1000) { return false; // 冷却期,跳过 } } } catch {} // 读取 settings.json if (!fs.existsSync(SETTINGS_FILE)) { return false; } // P3-3: 备份/恢复机制 const BACKUP_DIR = path.join(ROOT, 'backups'); const BACKUP_PREFIX = 'settings-guard-'; const MAX_BACKUPS = 5; let settings; try { settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8')); } catch (parseErr) { // settings.json 损坏 — 尝试从备份恢复 logSecurityEvent("critical", "security-startup-guard", "F6-anti-writeback", "settings.json corrupt: " + (parseErr.message || "").slice(0, 80)); try { if (!fs.existsSync(BACKUP_DIR)) return false; var backups = fs.readdirSync(BACKUP_DIR) .filter(function(f) { return f.startsWith(BACKUP_PREFIX) && f.endsWith('.json'); }) .sort().reverse(); if (backups.length === 0) return false; var bkContent = fs.readFileSync(path.join(BACKUP_DIR, backups[0]), "utf8"); JSON.parse(bkContent); // validate var tmpR = SETTINGS_FILE + '.tmp.' + process.pid; fs.writeFileSync(tmpR, bkContent); fs.renameSync(tmpR, SETTINGS_FILE); logSecurityEvent("auto-restore", "security-startup-guard", "F6-anti-writeback", "settings.json restored from " + backups[0]); settings = JSON.parse(bkContent); } catch { return false; } } // P3-3: 创建备份 (每次守卫检查时) try { if (!fs.existsSync(BACKUP_DIR)) fs.mkdirSync(BACKUP_DIR, { recursive: true }); var bkName = BACKUP_PREFIX + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19) + '.json'; fs.writeFileSync(path.join(BACKUP_DIR, bkName), JSON.stringify(settings, null, 2)); var allBk = fs.readdirSync(BACKUP_DIR) .filter(function(f) { return f.startsWith(BACKUP_PREFIX); }).sort(); while (allBk.length > MAX_BACKUPS) { try { fs.unlinkSync(path.join(BACKUP_DIR, allBk.shift())); } catch {} } } catch {} // 检测: skipDangerousModePermissionPrompt === true → 自动修复为 false // 用户确认: skipDangerousModePermissionPrompt=true 是宿主应用需要的,永久豁免 // 不再自动修复此配置项 // 写 lockfile try { const debugDir = path.join(ROOT, 'debug'); if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true }); fs.writeFileSync(LOCK_FILE, new Date().toISOString()); } catch {} return true; } function main() { // 消费 stdin (hook 协议要求) const readStdin = require('./lib/read-stdin.js'); readStdin({ maxSize: 512 * 1024 }).then(() => { runGuard(); process.exit(0); }).catch(() => process.exit(0)); // Fail-open: 守卫异常不阻断用户会话 } // 模块导出 (供 dispatcher 调用) if (typeof module !== 'undefined') { module.exports = { main, runGuard }; } if (require.main === module) { main(); }