#!/usr/bin/env node /** L4: 状态文件 HMAC 完整性模块 (v1.2 — 随机密钥 + Markdown + sanitization) */ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const os = require('os'); const SALT = 'bookworm-state-integrity-v1'; // R5: HMAC 密钥改为随机生成,首次运行时持久化到 .hmac-key (权限 600) // 回退: 密钥文件不可用时仍使用 hostname+username 派生 (向后兼容) var _keyFile = path.join(os.homedir(), '.claude', '.hmac-key'); function deriveKey() { // 优先使用随机密钥文件 try { if (fs.existsSync(_keyFile)) { return Buffer.from(fs.readFileSync(_keyFile, 'utf8').trim(), 'hex'); } // 首次运行: 生成 32 字节随机密钥 var key = crypto.randomBytes(32); var dir = path.dirname(_keyFile); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(_keyFile, key.toString('hex') + '\n', { mode: 0o600 }); return key; } catch { // 回退: 可预测密钥 (向后兼容) return crypto.createHash('sha256') .update(os.hostname() + ':' + os.userInfo().username + ':' + SALT) .digest(); } } function computeHMAC(content) { return crypto.createHmac('sha256', deriveKey()).update(content).digest('hex'); } function sigPath(fp) { return fp + '.sig'; } // ─── JSON 文件签名读写 ────────────────────────────────── function writeWithSignature(filePath, data) { var json = JSON.stringify(data, null, 2) + '\n'; var tmp = filePath + '.tmp.' + process.pid; fs.writeFileSync(tmp, json); fs.renameSync(tmp, filePath); fs.writeFileSync(sigPath(filePath), computeHMAC(json) + '\n'); } function readWithVerification(filePath, opts) { opts = opts || {}; try { var raw = fs.readFileSync(filePath, 'utf8'); var sp = sigPath(filePath); if (!fs.existsSync(sp)) { return { data: JSON.parse(raw), verified: false, error: 'sig-missing' }; } var expected = fs.readFileSync(sp, 'utf8').trim(); var actual = computeHMAC(raw); if (expected !== actual) { if (opts.strict) return { data: null, verified: false, error: 'sig-mismatch' }; return { data: JSON.parse(raw), verified: false, error: 'sig-mismatch' }; } return { data: JSON.parse(raw), verified: true, error: null }; } catch (e) { return { data: null, verified: false, error: e.message }; } } // ─── 通用文件原子写入 + 签名 (支持任意文本文件) ────────── /** * 原子写入文本文件 + HMAC 签名 * @param {string} filePath - 目标文件路径 * @param {string} content - 文本内容 */ function writeTextWithSignature(filePath, content) { var dir = path.dirname(filePath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); var tmp = filePath + '.tmp.' + process.pid; fs.writeFileSync(tmp, content); fs.renameSync(tmp, filePath); fs.writeFileSync(sigPath(filePath), computeHMAC(content) + '\n'); } /** * 读取文本文件 + 验证 HMAC * @param {string} filePath * @param {object} [opts] - { strict: boolean } * @returns {{ content: string|null, verified: boolean, error: string|null }} */ function readTextWithVerification(filePath, opts) { opts = opts || {}; try { var raw = fs.readFileSync(filePath, 'utf8'); var sp = sigPath(filePath); if (!fs.existsSync(sp)) { return { content: raw, verified: false, error: 'sig-missing' }; } var expected = fs.readFileSync(sp, 'utf8').trim(); var actual = computeHMAC(raw); if (expected !== actual) { if (opts.strict) return { content: null, verified: false, error: 'sig-mismatch' }; return { content: raw, verified: false, error: 'sig-mismatch' }; } return { content: raw, verified: true, error: null }; } catch (e) { return { content: null, verified: false, error: e.message }; } } // ─── Prompt Injection Sanitizer ───────────────────────── /** * 检测 resume-prompt.md 中的疑似注入标记 * 返回清理后的内容 + 检测到的可疑模式列表 */ const INJECTION_PATTERNS = [ /