#!/usr/bin/env bash set -uo pipefail # CASAN H4 — Tool-output indirect-injection scanner (Track A, V7). # # A tool (shell command, file read, web fetch, sub-agent) can return content # that is then fed back into a downstream model's context. If that content # carries a prompt injection, the model can be hijacked even though the ORIGINAL # user input was clean. This scans a tool-output file the same way an untrusted # artifact is scanned, BEFORE the output is allowed to re-enter model context. # # It reuses security-check.sh in `input` mode (block-pattern + unicode/encoding # normalization + secret detection) but forces the semantic/strict model path # OFF so the scan is deterministic and needs no model backend — this is a # pattern scan of machine output, not a user-intent classification. # # Usage: # tool-output-scan.sh [context-label] # Exit: # 0 — safe to reuse # 2 — injection / secret pattern detected (caller should reject/quarantine) # 64 — usage error (file missing) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OUTPUT_FILE="${1:-}" LABEL="${2:-unknown-tool}" if [[ -z "$OUTPUT_FILE" || ! -f "$OUTPUT_FILE" ]]; then echo "Usage: tool-output-scan.sh [context-label]" >&2 exit 64 fi WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT SCAN_OUT="$WORK/tool-output-scan.txt" TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" # Deterministic pattern scan: semantic + strict explicitly disabled here so a # tool-output scan never depends on (or is blocked by) model availability. CASAN_SECURITY_STRICT=0 CASAN_SEMANTIC_CLASSIFY=0 \ bash "$SCRIPT_DIR/security-check.sh" "$OUTPUT_FILE" "$SCAN_OUT" input >/dev/null 2>&1 SC_RC=$? if [[ "$SC_RC" -eq 2 ]]; then echo "TOOL_OUTPUT_SCAN_BLOCKED label=$LABEL file=$OUTPUT_FILE reason=injection_or_secret timestamp=$TIMESTAMP" exit 2 elif [[ "$SC_RC" -ne 0 ]]; then echo "TOOL_OUTPUT_SCAN_ERROR label=$LABEL rc=$SC_RC" >&2 exit 2 # fail closed on scan error fi echo "TOOL_OUTPUT_SCAN_CLEAN label=$LABEL file=$OUTPUT_FILE timestamp=$TIMESTAMP" exit 0