77 lines
2.4 KiB
JavaScript
77 lines
2.4 KiB
JavaScript
|
|
#!/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);
|
||
|
|
}
|