#!/usr/bin/env bash
#
# guard-env-files.sh — Songwhip PreToolUse guardrail
#
# Blocks (permissionDecision: "deny") any single tool call that could read or
# modify a .env* secrets file:
#   - Read / Edit / Write whose file_path is a .env* file
#   - Grep / Glob whose path or glob targets .env* files
#   - a Bash command that references a .env* file (cat/source/grep/less/…)
#
# A "deny" blocks only that one tool call and feeds the reason back to Claude as
# context — it does NOT interrupt the session or prompt you. Claude keeps running
# and simply continues without the .env* access. Anything that is NOT a .env*
# access is passed straight through (no output, exit 0), so normal permission
# handling is unchanged.
#
# IMPORTANT: a settings.json `deny` rule on .env* would short-circuit BEFORE this
# hook runs, so do not also deny .env* in settings — let this hook ask instead.
#
# Receives the PreToolUse payload as JSON on stdin. See:
# https://code.claude.com/docs/en/hooks
#
set -euo pipefail

input="$(cat)"

# Fail safe: if jq is unavailable we cannot parse the payload, so ask rather
# than silently allowing a possible secrets read.
if ! command -v jq >/dev/null 2>&1; then
  printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"env-file guard could not run (jq missing) — confirm this is not a .env* access."}}'
  exit 0
fi

tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"

# A path is a .env* file when its basename is .env, .env.<something>, .envrc, or
# .envrc.<something> (e.g. .envrc.shadow).
# (Matches the .env* convention; intentionally does NOT match foo.env or .environment.)
is_env_path() {
  local p="$1" base
  [[ -z "$p" ]] && return 1
  base="${p##*/}"
  [[ "$base" == .env || "$base" == .env.* || "$base" == .envrc || "$base" == .envrc.* ]]
}

# True when a search path or a glob pattern targets a .env*/.envrc* file. The
# glob test requires a .env / .envrc segment (at start or after '/') with an
# optional trailing glob char — i.e. the documented .env* convention. It does
# NOT match foo.env, *.env, or .environment.
is_env_glob() {
  local path="$1" glob="$2"
  is_env_path "$path" && return 0
  [[ -n "$glob" && "$glob" =~ (^|/)\.env(rc)?([./*]|$) ]]
}

deny() {
  jq -n --arg r "$1" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: $r
    }
  }'
  exit 0
}

case "$tool_name" in
  Read|Edit|Write)
    fp="$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')"
    if is_env_path "$fp"; then
      deny "$tool_name targets a .env* secrets file ($fp). Access to .env* secrets is blocked — skip it and continue."
    fi
    ;;
  Grep)
    path="$(printf '%s' "$input" | jq -r '.tool_input.path // empty')"
    # Grep's file filter is `glob`; its `pattern` is a regex over file *contents*,
    # NOT a path — so we deliberately ignore `pattern` here. Treating it as a glob
    # caused needless prompts when a search term merely contained ".env" (e.g.
    # grepping process.env).
    glob="$(printf '%s' "$input" | jq -r '.tool_input.glob // empty')"
    if is_env_glob "$path" "$glob"; then
      deny "Grep targets .env* secrets files. Access to .env* secrets is blocked — skip it and continue."
    fi
    ;;
  Glob)
    path="$(printf '%s' "$input" | jq -r '.tool_input.path // empty')"
    # For the Glob tool, `pattern` IS the file glob.
    glob="$(printf '%s' "$input" | jq -r '.tool_input.pattern // empty')"
    if is_env_glob "$path" "$glob"; then
      deny "Glob targets .env* secrets files. Access to .env* secrets is blocked — skip it and continue."
    fi
    ;;
  Bash)
    cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty')"
    # Match .env / .env.<suffix> / .envrc as a filename token within the command.
    if printf '%s' "$cmd" | grep -Eq '(^|[^[:alnum:]_])\.env(rc)?([^[:alnum:]_]|$)'; then
      deny "This Bash command references a .env* secrets file. Access to .env* secrets is blocked — skip it and continue."
    fi
    ;;
esac

# Not a .env* access — let normal permission handling proceed.
exit 0
