#!/usr/bin/env node /** * PreToolUse Hook: NDA 读取防护 * 匹配器: Read|Glob|Grep * 阻止 AI 读取 Bookworm 系统配置文件,防止通过工具调用泄露架构信息。 * * 退出码: 0=放行, 2=阻断(deny) * 激活条件: 仅在 Portable 发行版的 settings.json 中注册 */ 'use strict'; const fs = require('fs'); const path = require('path'); const readStdin = require('./lib/read-stdin.js'); const CLAUDE_ROOT = require('./lib/root.js'); // 安全日志 (fail-open) let logSecurityEvent = () => {}; try { logSecurityEvent = require('./lib/security-log.js').logSecurityEvent; } catch {} // --- 路径规范化 (对齐 block-sensitive-reads 的逻辑) --- function normalizePath(fp) { if (!fp) return ''; fp = fp.trim(); let resolved = path.resolve(fp); try { resolved = fs.realpathSync(resolved); } catch {} return resolved.replace(/\\/g, '/') .replace(/\.+$/, '') .replace(/::?\$DATA$/i, ''); } // --- 白名单: 用户自己的数据,AI 需要访问 --- const WHITELIST = [ /[\/]\.claude[\/]projects([\/]|$)/i, // 项目级 CLAUDE.md (用户项目配置) /[\/]\.claude[\/]memory([\/]|$)/i, // 用户记忆文件 ]; // --- 黑名单: 系统核心文件/目录 --- const BLACKLIST_PATHS = [ // 精确文件 { pattern: /[\/]\.claude[\/]CLAUDE\.md$/i, reason: '系统配置' }, { pattern: /[\/]\.claude[\/]settings\.json$/i, reason: '系统配置' }, { pattern: /[\/]\.claude[\/]settings\.local\.json$/i, reason: '系统配置' }, { pattern: /[\/]\.claude[\/]stats-compiled\.json$/i, reason: '系统数据' }, { pattern: /[\/]\.claude[\/]MEMORY\.md$/i, reason: '系统索引' }, // 目录级 { pattern: /[\/]\.claude[\/]skills[\/]/i, reason: '系统模块' }, { pattern: /[\/]\.claude[\/]agents[\/]/i, reason: '系统模块' }, { pattern: /[\/]\.claude[\/]hooks[\/]/i, reason: '系统模块' }, { pattern: /[\/]\.claude[\/]scripts[\/]/i, reason: '系统脚本' }, { pattern: /[\/]\.claude[\/]rules[\/]/i, reason: '系统规则' }, { pattern: /[\/]\.claude[\/]constitution[\/]/i, reason: '系统规则' }, { pattern: /[\/]\.claude[\/]docs[\/]/i, reason: '系统文档' }, { pattern: /[\/]\.claude[\/]debug[\/]/i, reason: '系统日志' }, { pattern: /[\/]\.claude[\/]templates[\/]/i, reason: '系统模板' }, ]; // --- Glob 模式黑名单 (匹配 Glob 工具的 pattern 参数) --- const GLOB_BLACKLIST = [ /\.claude/i, /skills[\/\\]/i, /agents[\/\\]/i, /hooks[\/\\]/i, /CLAUDE\.md/i, /settings\.json/i, /SKILL\.md/i, ]; // 拒绝消息 (不暴露防护机制) const DENY_MSG = '[系统] 该路径属于系统内部区域,无法访问。请直接描述您需要完成的任务,我来帮您。'; /** * 检查路径是否命中黑名单 * @returns {string|null} 命中原因,null 表示放行 */ function checkPath(normalizedPath) { if (!normalizedPath) return null; // 白名单优先 for (const wp of WHITELIST) { if (wp.test(normalizedPath)) return null; } // 黑名单 for (const { pattern, reason } of BLACKLIST_PATHS) { if (pattern.test(normalizedPath)) return reason; } // 兜底: 任何 .claude/ 下未白名单的路径 if (/[\/]\.claude[\/]/.test(normalizedPath)) { // 排除 .claude/projects/ 和 .claude/memory/ (已在白名单) return '系统目录'; } return null; } /** * 检查 Glob 的 pattern 参数 */ function checkGlobPattern(pattern) { if (!pattern) return null; for (const bp of GLOB_BLACKLIST) { if (bp.test(pattern)) return '系统模式搜索'; } return null; } /** * 可导出的统一检查入口 (供 dispatcher 或测试调用) */ function checkInput(input) { const toolName = (input.tool_name || '').toLowerCase(); const ti = input.tool_input || {}; // --- Read --- if (toolName === 'read') { const fp = normalizePath(ti.file_path || ti.filePath || ''); const reason = checkPath(fp); if (reason) return { decision: 'deny', reason, path: ti.file_path }; } // --- Glob --- if (toolName === 'glob') { // 检查搜索目录 const searchPath = normalizePath(ti.path || ''); const pathReason = checkPath(searchPath); if (pathReason) return { decision: 'deny', reason: pathReason, path: ti.path }; // 检查 glob pattern const patternReason = checkGlobPattern(ti.pattern || ''); if (patternReason) return { decision: 'deny', reason: patternReason, path: ti.pattern }; } // --- Grep --- if (toolName === 'grep') { const searchPath = normalizePath(ti.path || ''); const reason = checkPath(searchPath); if (reason) return { decision: 'deny', reason, path: ti.path }; } return null; // 放行 } // --- 主流程 --- function main() { readStdin({ maxSize: 512 * 1024 }).then(input => { const result = checkInput(input); if (result) { logSecurityEvent('nda-deny', 'nda-read-guard', result.reason, result.path || ''); process.stderr.write(JSON.stringify({ hookSpecificOutput: { permissionDecision: 'deny' }, systemMessage: DENY_MSG, })); process.exit(2); return; } process.exit(0); }).catch(() => { // fail-open: 解析异常时放行 (不阻断用户正常工作) process.exit(0); }); } if (typeof module !== 'undefined') { module.exports = { checkInput, checkPath, checkGlobPattern, normalizePath }; } if (require.main === module) { main(); }