#!/usr/bin/env node const fs = require("fs"); const path = require("path"); const readStdin = require('./lib/read-stdin.js'); const SUFFIX_RULES = [ { suffix: ".credentials.json", reason: "Claude OAuth 凭证文件" }, { suffix: ".env", reason: ".env 环境变量文件" }, { suffix: ".pem", reason: "PEM 证书/密钥文件" }, { suffix: ".key", reason: "私钥文件" }, { suffix: ".p12", reason: "PKCS12 证书文件" }, { suffix: ".pfx", reason: "PFX 证书文件" }, { suffix: ".netrc", reason: ".netrc 网络凭证" }, { suffix: ".git-credentials", reason: "Git 凭证" }, { suffix: ".htpasswd", reason: "HTTP Auth 密码" }, { suffix: ".npmrc", reason: "npm token" }, { suffix: ".pypirc", reason: "PyPI token" }, ]; const CONTAINS_RULES = [ { pattern: "id_rsa", reason: "SSH RSA 私钥" }, { pattern: "id_ed25519", reason: "SSH ED25519 私钥" }, { pattern: "service_account", reason: "GCP 服务账号" }, { pattern: "service-account", reason: "GCP 服务账号" }, { pattern: "firebase-adminsdk", reason: "Firebase SDK" }, { pattern: "firebase_adminsdk", reason: "Firebase SDK" }, ]; function isEnvVariant(fp) { var b = path.basename(fp).toLowerCase(); return b.startsWith(".env.") && b.length > 5; } function isSecretsFile(fp) { var b = path.basename(fp).toLowerCase(); var X = [".json",".yaml",".yml",".toml",".xml"]; return b.startsWith("secret") && X.some(function(e){return b.endsWith(e);}); } function isCredFile(fp) { var b = path.basename(fp).toLowerCase(); var X = [".json",".yaml",".yml",".toml",".xml"]; return b.startsWith("credential") && X.some(function(e){return b.endsWith(e);}); } function norm(fp) { if (!fp) return ""; fp = fp.trim(); // P1-8: 剥离尾部空格 (Windows 路径绕过) var resolved = path.resolve(fp); // P1-5: 解析符号链接,防止 symlink 绕过 try { resolved = fs.realpathSync(resolved); } catch(e) {} return resolved.split(path.sep).join("/").toLowerCase() .replace(/\.+$/, '') // P1-8: 剥离尾部点 (Windows 会忽略) .replace(/::?\$DATA$/i, ''); // P1-8: 剥离 NTFS ADS 后缀 } function logEvt(decision,reason,detail) { try { let root=require('./lib/root.js'),dd=path.join(root,"debug"); if(!fs.existsSync(dd))fs.mkdirSync(dd,{recursive:true}); let lf=path.join(dd,"security-"+new Date().toISOString().slice(0,10)+".jsonl"); fs.appendFileSync(lf,JSON.stringify({ts:new Date().toISOString(),decision:decision, hook:"block-sensitive-reads",reason:reason,detail:(detail||"").slice(0,200) })+"\n"); }catch(e){} } function check(raw) { let fp=norm(raw); if(!fp) return null; for(let i=0;i { var ti=inp.tool_input||{}; var rp=ti.file_path||ti.filePath||ti.path||""; var reason=check(rp); if(reason){logEvt("deny",reason,rp); process.stderr.write(JSON.stringify({hookSpecificOutput:{permissionDecision:"deny"}, systemMessage:"[安全防护] 阻止读取: "+rp+" | "+reason})); process.exit(2);return;} process.exit(0); }).catch((e) => { process.stderr.write(JSON.stringify({hookSpecificOutput:{permissionDecision:"ask"}, systemMessage:"[安全防护] 异常("+(e.message||"")+")"})); process.exit(2); }); } if(typeof module!=="undefined"){module.exports={check:check};} if(require.main===module){main();}