#!/bin/sh # Tests for hooks/secret-guard.sh — the PreToolUse gate that blocks command # shapes which render secret values into the transcript. # # Contract: reads PreToolUse hook JSON on stdin, ALWAYS exits 0, and answers # in the JSON body as hookSpecificOutput.permissionDecision = allow | deny. # Asserting on exit status would pass vacuously — assert on the decision. # # Every deny case below is a shape that caused a real leak; the comment names # the incident so nobody "simplifies" the pattern away later. set -u ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) GUARD="$ROOT/hooks/secret-guard.sh" pass=0 fail=0 # decision_for [tool_name] # Emits the guard's permissionDecision for a synthetic Bash tool call. decision_for() { _cmd=$1 _tool=${2:-Bash} jq -n --arg t "$_tool" --arg c "$_cmd" \ '{tool_name: $t, tool_input: {command: $c}}' \ | sh "$GUARD" \ | jq -r '.hookSpecificOutput.permissionDecision // "MISSING"' } expect() { _want=$1 _cmd=$2 _label=$3 _got=$(decision_for "$_cmd") if [ "$_got" = "$_want" ]; then pass=$((pass + 1)) printf ' ok %s\n' "$_label" else fail=$((fail + 1)) printf ' FAIL %s\n want=%s got=%s\n cmd=%s\n' "$_label" "$_want" "$_got" "$_cmd" fi } if [ ! -f "$GUARD" ]; then printf 'FAIL: %s does not exist\n' "$GUARD" exit 1 fi command -v jq >/dev/null 2>&1 || { printf 'SKIP: jq not installed\n'; exit 0; } # --- deny: decrypt-and-print in one invocation (2026-08-05 ntfy-token leak) --- expect deny 'sops -d secrets.enc.yaml | tail -20' 'sops decrypt piped to tail' expect deny 'sops -d ingress.enc.yaml > /tmp/x; cat /tmp/x' 'sops decrypt then cat' # --- deny: 1Password raw reads (2026-07-30) --- expect deny 'op item get "gitea mcp" --format json' 'op item get' expect deny 'op read "op://vault/item/field"' 'op read' # --- deny: env dumps (infra#228, 2026-07-27) --- expect deny 'env | grep -i gitea' 'env piped to grep' expect deny 'env' 'bare env' expect deny 'printenv' 'printenv' # --- deny: kubernetes secret material --- expect deny 'kubectl get secret brain-app -o yaml' 'kubectl get secret -o yaml' # --- deny: base64 as a masking bypass (2026-06-11) --- expect deny 'echo "$GITEA_MCP_TOKEN" | base64' 'token piped through base64' # --- allow: ordinary commands must not be blocked --- expect allow 'ls -la /tmp' 'plain ls' expect allow 'git status --porcelain' 'git status' expect allow 'kubectl describe secret brain-app' 'kubectl describe secret (values redacted by k8s)' expect allow '[ -n "$GITEA_MCP_TOKEN" ] && echo set' 'existence-only check' # --- allow: the guard only judges Bash --- _got=$(decision_for 'env' 'Read') if [ "$_got" = "allow" ]; then pass=$((pass + 1)); printf ' ok non-Bash tool passes through\n' else fail=$((fail + 1)); printf ' FAIL non-Bash tool passes through\n want=allow got=%s\n' "$_got" fi printf '\nsecret_guard: %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ]