#!/usr/bin/env bash set -uo pipefail # CASAN Track C-MVP — Data-exfiltration guard (C3, V19). # # Content leaving the trusted boundary must not carry secrets/PII. This guard # is the checkpoint for three egress destinations, each with its own policy: # # cloud — content about to be sent to a CLOUD model (OpenAI/Anthropic). # A secret => BLOCK (fail closed); PII => masked copy emitted. # Critical once Plan-03 wires a real cloud backend. # audit — content about to be written to an audit/log record. # PII/secret => masked copy emitted (BLOCK if masking impossible). # artifact — a generated artifact about to be persisted/shared. # Any secret / env-token => BLOCK. # # It reuses security-check.sh's secret/PII detection + masking (single source of # truth) and applies the destination policy on top. Deterministic; no model call. # # Usage: data-exfil-guard.sh [masked-output-file] # Exit: 0 allowed (possibly masked), 2 blocked (secret at a fail-closed boundary), # 64 usage. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=casan-log.sh source "$SCRIPT_DIR/casan-log.sh" FILE="${1:-}" DEST="${2:-}" MASKED_OUT="${3:-}" if [[ -z "$FILE" || ! -f "$FILE" || -z "$DEST" ]]; then echo "Usage: data-exfil-guard.sh [masked-output-file]" >&2 exit 64 fi case "$DEST" in cloud|audit|artifact) ;; *) echo "unknown destination: $DEST" >&2; exit 64 ;; esac SCAN_OUT="$(mktemp)" trap 'rm -f "$SCAN_OUT"' EXIT # Deterministic detection: security-check input-mode (semantic OFF). rc==2 means # a secret/critical pattern was found; on rc==0 SCAN_OUT holds the PII-masked copy. CASAN_SECURITY_STRICT=0 CASAN_SEMANTIC_CLASSIFY=0 \ bash "$SCRIPT_DIR/security-check.sh" "$FILE" "$SCAN_OUT" input >/dev/null 2>&1 SC_RC=$? if [[ "$SC_RC" -eq 2 ]]; then # A secret / private key / connection string / card was detected. case "$DEST" in cloud) casan_log error data-exfil "DATA_EXFIL_BLOCKED destination=cloud reason=secret_would_leave_org" echo "DATA_EXFIL_BLOCKED destination=cloud reason=secret_in_content" >&2 exit 2 ;; artifact) casan_log error data-exfil "DATA_EXFIL_BLOCKED destination=artifact reason=secret_or_env_token" echo "DATA_EXFIL_BLOCKED destination=artifact reason=secret_or_env_token" >&2 exit 2 ;; audit) # Audit must never store a raw secret and must never lose the record; if we # cannot safely mask a hard secret we fail closed rather than log it raw. casan_log error data-exfil "DATA_EXFIL_BLOCKED destination=audit reason=unmaskable_secret" echo "DATA_EXFIL_BLOCKED destination=audit reason=unmaskable_secret" >&2 exit 2 ;; esac elif [[ "$SC_RC" -ne 0 ]]; then echo "DATA_EXFIL_SCAN_ERROR destination=$DEST rc=$SC_RC" >&2 exit 2 # fail closed on scan error fi # rc==0: content is safe; SCAN_OUT is the PII-masked copy. if [[ -n "$MASKED_OUT" ]]; then cp "$SCAN_OUT" "$MASKED_OUT" fi if ! cmp -s "$FILE" "$SCAN_OUT"; then casan_log info data-exfil "DATA_EXFIL_MASKED destination=$DEST (PII redacted before egress)" echo "DATA_EXFIL_MASKED destination=$DEST" else echo "DATA_EXFIL_CLEAN destination=$DEST" fi exit 0