#!/usr/bin/env node /** * patch-p1-settings-hmac-sign.js * * P1.4: 给 settings.json 加 HMAC 签名 (settings.json.sig) * * 镜像 feature-flags.json.sig 模式 (64 字节 HMAC-SHA256)。 * 用 .hmac-key 作为密钥(已在 P0.1 收紧 ACL)。 * * 设计: * - 不修改 settings.json 本身(避免破坏现有结构) * - 仅在并行写出 settings.json.sig * - 提供 verify-settings-sig.js 验证工具 * * 后续: 启动期 hook 应读 .sig 比对,发现篡改告警/拒载 */ 'use strict'; const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const ROOT = path.join(__dirname, '..', '..'); const SETTINGS = path.join(ROOT, 'settings.json'); const SIG_FILE = path.join(ROOT, 'settings.json.sig'); const HMAC_KEY = path.join(ROOT, '.hmac-key'); function main() { if (!fs.existsSync(SETTINGS)) { process.stderr.write('[ERROR] settings.json not found: ' + SETTINGS + '\n'); process.exit(1); } if (!fs.existsSync(HMAC_KEY)) { process.stderr.write('[ERROR] .hmac-key not found. Run hmac-init first.\n'); process.exit(1); } const key = fs.readFileSync(HMAC_KEY, 'utf8').trim(); if (key.length < 32) { process.stderr.write('[ERROR] .hmac-key too short (' + key.length + ' chars). Need >=32.\n'); process.exit(1); } const content = fs.readFileSync(SETTINGS); const sig = crypto.createHmac('sha256', key).update(content).digest('hex'); // 幂等 if (fs.existsSync(SIG_FILE)) { const old = fs.readFileSync(SIG_FILE, 'utf8').trim(); if (old === sig) { process.stdout.write('[SKIP] signature unchanged\n'); process.exit(0); } const ts = new Date().toISOString().replace(/[:.]/g, '-'); fs.copyFileSync(SIG_FILE, SIG_FILE + '.bak.' + ts); process.stdout.write('[BACKUP] ' + SIG_FILE + '.bak.' + ts + '\n'); } // 原子写 const tmpPath = SIG_FILE + '.tmp.' + process.pid; fs.writeFileSync(tmpPath, sig); fs.renameSync(tmpPath, SIG_FILE); // ACL 收紧(仅当前用户) try { const { execSync } = require('child_process'); if (process.platform === 'win32') { const user = process.env.USERNAME; execSync('icacls "' + SIG_FILE + '" /inheritance:r /grant:r "' + user + ':F"', { stdio: 'ignore' }); } else { fs.chmodSync(SIG_FILE, 0o600); } } catch (_) { /* best-effort */ } process.stdout.write('[OK] settings.json.sig written (sha256-hmac, ' + sig.length + ' chars)\n'); process.stdout.write(' ' + sig.slice(0, 16) + '...' + sig.slice(-16) + '\n'); process.stdout.write(' verify with: node scripts/patches/verify-settings-sig.js\n'); } if (require.main === module) main();