#!/usr/bin/env bash set -uo pipefail # CASAN H6/H7 — Kill-switch (Incident response · C7 / V23). # # A scoped emergency stop: engage a switch for a project / model / provider and # any gate that honors it refuses to run further work in that scope. Engaging is # recorded; clearing requires an explicit reason (production: reviewer approval). # # Usage: # kill-switch.sh engage [reason] # turn the switch ON # kill-switch.sh clear [reason] # turn it OFF (audited) # kill-switch.sh check # exit 2 if engaged, 0 if clear # kill-switch.sh status # list engaged switches # scope ∈ {project, model, provider, tenant, global}. A `global` switch stops # everything; a `tenant` switch (SEC-23 MT-03) stops only that tenant. # Env: CASAN_KILLSWITCH_DIR (default .specify/logs/level5/kill-switch) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/casan-paths.sh" PROJECT_ROOT="$CASAN_APP_ROOT" KS_DIR="${CASAN_KILLSWITCH_DIR:-$CASAN_STATE_ROOT/logs/level5/kill-switch}" mkdir -p "$KS_DIR" CMD="${1:-}"; SCOPE="${2:-}"; ID="${3:-}"; REASON="${4:-unspecified}" ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; } safe() { printf '%s' "$1" | tr '/ :' '___'; } case "$CMD" in engage) [[ -n "$SCOPE" && -n "$ID" ]] || { echo "usage: kill-switch.sh engage [reason]" >&2; exit 64; } f="$KS_DIR/$(safe "$SCOPE")-$(safe "$ID").on" printf '{"scope":"%s","id":"%s","reason":"%s","engaged_at":"%s","actor":"%s"}\n' \ "$SCOPE" "$ID" "$REASON" "$(ts)" "${CASAN_ACTOR:-system}" > "$f" echo "KILL_SWITCH_ENGAGED scope=$SCOPE id=$ID reason=$REASON" ;; clear) [[ -n "$SCOPE" && -n "$ID" ]] || { echo "usage: kill-switch.sh clear [reason]" >&2; exit 64; } # SEC-07 (M-08): clearing an emergency stop is a high-trust action. In enforced # mode it needs a REGISTERED reviewer's verified approval (approval-verify.sh), # not just "anyone who can run the script". Dev (default) stays unchanged. if [[ "${CASAN_PROFILE:-}" == "prod" || "${CASAN_APPROVAL_STRICT:-}" == "1" ]]; then APPROVER="${CASAN_APPROVER:-}" KS_INP="$(mktemp)"; printf '%s/%s' "$SCOPE" "$ID" > "$KS_INP" if [[ -z "$APPROVER" ]] || \ ! bash "$SCRIPT_DIR/approval-verify.sh" kill_switch "${CASAN_ACTOR:-system}" \ "$KS_INP" "$APPROVER" "${CASAN_APPROVAL_SIG:--}" >/dev/null 2>&1; then rm -f "$KS_INP" echo "KILL_SWITCH_CLEAR_DENIED scope=$SCOPE id=$ID reason=approval_required" >&2 exit 3 fi rm -f "$KS_INP" fi f="$KS_DIR/$(safe "$SCOPE")-$(safe "$ID").on" if [[ -f "$f" ]]; then printf '%s cleared_by=%s reason=%s at=%s\n' "$(cat "$f")" "${CASAN_ACTOR:-system}" "$REASON" "$(ts)" \ >> "$KS_DIR/kill-switch-history.log" rm -f "$f" echo "KILL_SWITCH_CLEARED scope=$SCOPE id=$ID" else echo "KILL_SWITCH_NOT_ENGAGED scope=$SCOPE id=$ID" fi ;; check) [[ -n "$SCOPE" && -n "$ID" ]] || { echo "usage: kill-switch.sh check " >&2; exit 64; } # A global switch, or a switch for this exact scope/id, blocks. if [[ -f "$KS_DIR/global-all.on" ]]; then echo "KILL_SWITCH_ACTIVE scope=global" >&2; exit 2 fi if [[ -f "$KS_DIR/$(safe "$SCOPE")-$(safe "$ID").on" ]]; then echo "KILL_SWITCH_ACTIVE scope=$SCOPE id=$ID" >&2; exit 2 fi echo "KILL_SWITCH_CLEAR scope=$SCOPE id=$ID"; exit 0 ;; status) n=0 for f in "$KS_DIR"/*.on; do [[ -e "$f" ]] || continue; cat "$f"; n=$((n+1)); done echo "KILL_SWITCH_STATUS engaged=$n" ;; *) echo "Usage: kill-switch.sh {engage|clear|check|status} [reason]" >&2 exit 64 ;; esac