#!/usr/bin/env node // PreToolUse 가드 — 비밀값 파일 접근을 하드 차단한다. // // 차단 대상: .project-env, .env, .env.*, ~/.env (LITELLM_KEY 등). // - 허용: .project-env.example (참조용 예시는 OK) // 적용 도구: Read/Edit/Write/NotebookEdit 의 경로, 그리고 Bash 명령 문자열(cat/less/grep 등 우회 포함). // // settings.json 의 permissions.deny 는 Read 도구만 막지만, 이 훅은 Bash 우회까지 막는다. // 반환: permissionDecision=deny 면 도구 호출이 실행 전에 거부된다. import { readFileSync } from "node:fs"; let input = {}; try { input = JSON.parse(readFileSync(0, "utf8") || "{}"); } catch { process.exit(0); // 입력 파싱 실패 시 통과(다른 가드에 위임). } const tool = input.tool_name || ""; const ti = input.tool_input || {}; // .project-env(단, .example 제외) 또는 .env / .env.* / ~/.env 를 가리키는 토큰. const SECRET_RE = /(?:^|[\s'"=(/])\.project-env(?!\.example)|(?:^|[\s'"=(/])\.env(?:\.[\w.-]+)?(?![\w])/; let target = ""; switch (tool) { case "Read": case "Edit": case "Write": target = ti.file_path || ""; break; case "NotebookEdit": target = ti.notebook_path || ""; break; case "Bash": target = ti.command || ""; break; default: process.exit(0); } if (SECRET_RE.test(target)) { process.stdout.write( JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "비밀값 파일(.project-env / .env / ~/.env)은 읽거나 수정할 수 없습니다. " + "DB·MinIO·LiteLLM 자격증명은 런타임에 환경변수로만 주입됩니다($DATABASE_URL 등). " + "키 구조가 궁금하면 .project-env.example 을 보세요.", }, }) ); process.exit(0); } process.exit(0);