bookworm-smart-assistant/scripts/patches/patch-lstat-guard.js

77 lines
2.4 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* patch-lstat-guard.js
* P1 安全修复: project-context-injector.js 添加 lstat 符号链接防护
* 红队发现: symlink oracle via .bookworm-context.md (85% 概率)
* Idempotent: sentinel 防重复执行
*/
'use strict';
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = path.join(process.env.USERPROFILE || process.env.HOME, '.claude');
const HOOKS_DIR = path.join(CLAUDE_ROOT, 'hooks');
const STATE_DIR = path.join(CLAUDE_ROOT, 'session-state');
const SENTINEL = path.join(STATE_DIR, 'patch-lstat-guard.done');
const TARGET = path.join(HOOKS_DIR, 'project-context-injector.js');
if (fs.existsSync(SENTINEL)) {
console.log('[SKIP] patch-lstat-guard already applied');
process.exit(0);
}
if (!fs.existsSync(TARGET)) {
console.error('[FAIL] Target not found: ' + TARGET);
process.exit(1);
}
const ANCHOR = ` if (!fs.existsSync(ctxPath)) {\n process.exit(0);\n }`;
const PATCH = ` if (!fs.existsSync(ctxPath)) {
process.exit(0);
}
try { if (fs.lstatSync(ctxPath).isSymbolicLink()) process.exit(0); } catch { process.exit(0); }`;
try {
const bak = TARGET + '.bak-lstat-' + Date.now();
fs.copyFileSync(TARGET, bak);
console.log('[OK] Backup: ' + path.basename(bak));
let src = fs.readFileSync(TARGET, 'utf8');
if (src.includes('isSymbolicLink')) {
console.log('[SKIP] lstat guard already present in source');
} else if (!src.includes(ANCHOR)) {
const anchorCRLF = ANCHOR.replace(/\n/g, '\r\n');
if (src.includes(anchorCRLF)) {
src = src.replace(anchorCRLF, PATCH.replace(/\n/g, '\r\n'));
console.log('[OK] Patched (CRLF mode)');
} else {
console.error('[FAIL] Anchor not found in ' + path.basename(TARGET));
process.exit(1);
}
} else {
src = src.replace(ANCHOR, PATCH);
console.log('[OK] Patched (LF mode)');
}
const tmp = TARGET + '.tmp.' + process.pid;
fs.writeFileSync(tmp, src, 'utf8');
fs.renameSync(tmp, TARGET);
console.log('[OK] ' + path.basename(TARGET) + ' updated');
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
fs.writeFileSync(SENTINEL, JSON.stringify({
applied: new Date().toISOString(),
target: path.basename(TARGET),
backup: path.basename(bak),
fix: 'lstat symlink guard after existsSync check'
}, null, 2), 'utf8');
console.log('[DONE] patch-lstat-guard applied');
} catch (err) {
console.error('[FAIL] ' + err.message);
process.exit(1);
}