#!/usr/bin/env bash
# PreToolUse(Bash) — route UNBOUNDED multi-file reads into the quietcontext sandbox.
# Bounded pipelines (wc / head -N / tail -N / grep -c) already keep raw bytes out of
# context and are left alone. Bypass with a trailing "# raw-ok".
set -uo pipefail

INPUT=""; IFS= read -rd '' INPUT || true

# Fast path: the deny below can only ever fire when the command contains one of
# these literals (find / grep / git ls-files / ls with flags / a glob). Checked
# against the raw JSON (a superset of tool_input.command), so this never denies
# on its own — it only ever skips the jq spawn when none can possibly match.
case "$INPUT" in
  *find*|*grep*|*ls-files*|*'ls '*|*'**/*'*|*'*.'*) ;;
  *) exit 0 ;;
esac

CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) || exit 0
[[ -z "$CMD" ]] && exit 0

RAW_OK_RE='#[[:space:]]*raw-ok'
[[ $CMD =~ $RAW_OK_RE ]] && exit 0

# Output-bounded dispatch wrappers: their stdout contract is a single logfile
# path (the raw read/test-run text lives only in a QUOTED PROMPT ARGUMENT
# handed to another agent, never in our own context). `cdx exec ...` is one
# such wrapper — exempt it outright, before any pattern matching, so prose
# inside its prompt payload can never trip the scanner below.
CDX_EXEMPT_RE='^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+)*cdx[[:space:]]+exec([[:space:]]|$)'
[[ $CMD =~ $CDX_EXEMPT_RE ]] && exit 0

# Strip quoted string literals before the TRIGGER check only. Quoted payloads
# (e.g. a prompt string handed to another agent/process) merely TALK about
# reading files or running suites — they are not commands this shell will
# execute — so they must never be able to trip FILESCAN_RE into a deny.
# Escaped quotes inside double-quoted spans are honored well enough for real
# shell lines; bash single-quoted strings never contain an escaped quote, so
# a plain non-greedy match is exact for that case.
STRIPPED=$(printf '%s' "$CMD" | sed -E \
  -e 's/"[^"\\]*(\\.[^"\\]*)*"/ /g' \
  -e "s/'[^']*'/ /g")

# Multi-file read: recursive/glob-wide source. Checked against the
# quote-stripped text — this is the only regex that can cause a deny.
FILESCAN_RE='(\bfind\b|\bgrep\b[^|]*-[a-zA-Z]*[rR]|\bgit ls-files\b|\bls\b[^|]*-[a-zA-Z]*R|\*\*/\*|\*\.[a-z]+)'
[[ $STRIPPED =~ $FILESCAN_RE ]] || exit 0

# Mutation or side-effecting: not our business. Both regexes below are
# exit-0/allow paths only, so they stay on the raw (unstripped) $CMD — the
# bounding/mutating syntax they look for (e.g. `sed -n '1,20p'`, `awk 'NR<=5'`)
# is itself conventionally quoted, and stripping it would only widen the deny
# surface, never narrow it. That would be loosening the wrong direction.
MUTATE_RE='(\brm\b|\bmv\b|\bcp\b|-exec\b|-delete\b|\bsed -i\b|\bxargs\b[^|]*\b(rm|mv|sed)\b)'
[[ $CMD =~ $MUTATE_RE ]] && exit 0

# Bounded output: the raw bytes never reach context. Leave alone.
BOUNDED_RE='(\|[^|]*\bwc\b|\|[^|]*\b(head|tail)\b[^|]*(-n[[:space:]]*)?-?[0-9]{1,2}\b|\|[^|]*\b(head|tail)\b[^|0-9]*$|\|[^|]*\bsed\b[^|]*[0-9]+[[:space:]]*p|\|[^|]*\bawk\b[^|]*NR[[:space:]]*<|\bgrep\b[^|]*(-[a-zA-Z]*c\b|--count)|\bwc -l[[:space:]]*<)'
[[ $CMD =~ $BOUNDED_RE ]] && exit 0

jq -nc '{
  hookSpecificOutput: {
    hookEventName: "PreToolUse",
    permissionDecision: "deny",
    permissionDecisionReason: "quietcontext-gate: this reads many files with no bound — the raw output would land in context. Either bound it (pipe to wc / head -N), or do the work in the sandbox: ToolSearch(query:\"select:mcp__plugin_quietcontext_quietcontext__execute\") then mcp__plugin_quietcontext_quietcontext__execute with an equivalent script that prints only the derived answer. Deliberate raw run? append \" # raw-ok\"."
  }
}'
exit 0
