58 lines
2.2 KiB
JavaScript
58 lines
2.2 KiB
JavaScript
/**
|
|
* PreToolUse(Read) wrapper — path-deny for Read tool.
|
|
* Router-gate v4 emergency fix (Smoke 5 2026-05-30).
|
|
*
|
|
* Spec §3.1 declared transcript JSONL hard-deny but Read tool had NO
|
|
* path-protection — controller could Read ~/.claude/projects/*.jsonl
|
|
* (parent context exfil from other sessions). Same for runtime artifacts,
|
|
* .env, normative files.
|
|
*
|
|
* Reuses DEFAULT_PROTECTED_PATTERNS from shell-content-rules.mjs.
|
|
* Fail-CLOSE on internal error (security default).
|
|
*/
|
|
import { fileURLToPath } from 'url';
|
|
import {
|
|
readStdin,
|
|
parseEventJson,
|
|
exitDecision,
|
|
} from './enforce-hook-helpers.mjs';
|
|
import { defaultPathNormalize, isProtectedPath, READ_DENY_PATTERNS } from './shell-content-rules.mjs';
|
|
|
|
export function decide({ toolName, filePath }) {
|
|
if (toolName !== 'Read') return { block: false, reason: null };
|
|
const fp = String(filePath || '');
|
|
if (!fp) return { block: false, reason: null };
|
|
// Narrow READ_DENY_PATTERNS (not the full DEFAULT_PROTECTED_PATTERNS): Read of
|
|
// CLAUDE.md / normative docs / memory has no exfil value and must stay allowed
|
|
// for the claude-md-management / memory-sync workflow. Only genuine Read-exfil
|
|
// targets — transcripts, runtime, settings, secrets — are blocked. The full
|
|
// protected-list still guards Bash/PowerShell read and Write (over-block fix 2026-05-31).
|
|
if (isProtectedPath(fp, defaultPathNormalize, READ_DENY_PATTERNS)) {
|
|
return {
|
|
block: true,
|
|
reason: `path «${defaultPathNormalize(fp)}» protected against Read (§3.1 transcript/runtime/secrets hard-deny)`,
|
|
};
|
|
}
|
|
return { block: false, reason: null };
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
const raw = await readStdin();
|
|
const event = parseEventJson(raw);
|
|
const r = decide({
|
|
toolName: event.tool_name,
|
|
filePath: event.tool_input?.file_path || event.tool_input?.filePath,
|
|
});
|
|
if (r.block) {
|
|
return exitDecision({ block: true, message: `[read-path-deny] ${r.reason}` });
|
|
}
|
|
return exitDecision({ block: false });
|
|
} catch {
|
|
return exitDecision({ block: true, message: '[read-path-deny] внутренняя ошибка — fail-CLOSE' });
|
|
}
|
|
}
|
|
|
|
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
if (isCli) main();
|