#!/usr/bin/env python3
"""
Claude Code PreToolUse hook — blocks Bash commands that READ or TRANSMIT secret
files (.env*, .pgpass, ssh private keys).

Why: `.claude/settings.json` permissions end with a blanket `Bash(*)` allow, which
sidesteps the `Read(./.env*)` deny rule — `cat backend/.env`, `Get-Content .env`,
`curl -F @backend/.env …` all exfiltrate prod DB passwords + FORGEJO/GLITCHTIP tokens
without a single prompt. This hook closes that bypass at the command layer.

Wired in `.claude/settings.json` hooks.PreToolUse with matcher "Bash".

Behavior:
- Reads hook input JSON from stdin; extracts tool_input.command.
- Blocks (exit 2, stderr -> Claude) iff the command references a REAL secret file
  AND uses a read/transmit verb, OR `< file` redirection, OR `@file` upload, OR `open(`.
- Template files (.env.example / .sample / .template / .dist / .md) are allowed.
- `ls`/`stat`/`test -f` on a secret file are allowed (they don't read content).
- Fail-OPEN on parse errors (exit 0): this is a tripwire layered on top of the deny
  list + (future) sandbox, not the sole guard — better to under-block than to wedge
  every Bash call on a malformed payload.
"""
from __future__ import annotations

import json
import re
import sys

# A secret-file token: `.env` with any number of dotted segments, `.pgpass`, or an
# ssh private key. The leading delimiter ([start | space | quote | = : < @ ( / ])
# anchors it as a filename/path component, not a substring of another word.
SECRET_RE = re.compile(
    r"(?:^|[\s'\"=:<@(/])"
    r"(\.env(?:\.[\w-]+)*|\.pgpass|id_rsa[\w.]*|id_ed25519[\w.]*|id_ecdsa[\w.]*)"
    r"(?![\w-])",
    re.IGNORECASE,
)
TEMPLATE_SUFFIXES = (".example", ".sample", ".template", ".dist", ".md")

# Verbs that read a file's content or push it off-box.
READ_VERB_RE = re.compile(
    r"(?i)(?<![\w-])("
    r"cat|tac|nl|head|tail|less|more|od|xxd|hexdump|strings|base64|"
    r"type|gc|get-content|gp|select-string|sls|"
    r"cp|copy|scp|rsync|sftp|"
    r"curl|wget|invoke-webrequest|iwr|"
    r"sed|awk|grep|findstr|sort|paste|cut|tr|dd|source|\."
    r")(?![\w-])"
)


def _is_template(token: str) -> bool:
    low = token.lower()
    return any(low.endswith(s) for s in TEMPLATE_SUFFIXES)


def main() -> int:
    try:
        payload = json.load(sys.stdin)
    except Exception:
        return 0

    tool = payload.get("tool_name") or payload.get("tool") or ""
    if tool != "Bash":
        return 0

    cmd = (payload.get("tool_input") or {}).get("command") or ""
    if not cmd:
        return 0

    hits = [m.group(1) for m in SECRET_RE.finditer(cmd) if not _is_template(m.group(1))]
    if not hits:
        return 0

    danger = (
        READ_VERB_RE.search(cmd)
        or re.search(r"<\s*\S*\.env\b", cmd, re.IGNORECASE)
        or re.search(r"@\S*\.env\b", cmd, re.IGNORECASE)
        or re.search(r"open\(\s*['\"][^'\"]*\.env", cmd, re.IGNORECASE)
    )
    if not danger:
        return 0

    msg = [
        f"Blocked: команда читает/передаёт секрет-файл ({hits[0]}).",
        "Blanket `Bash(*)` обходит `Read(./.env*)` deny — этот PreToolUse-hook закрывает дыру.",
        "Секрет-файлы содержат prod DB-пароли + FORGEJO/GLITCHTIP токены.",
        "Если правда нужно прочитать — сделай это вручную вне Claude, либо используй `.env.example`.",
    ]
    print("\n".join(msg), file=sys.stderr)
    return 2


if __name__ == "__main__":
    sys.exit(main())
