Skip to content

Commit 9196040

Browse files
SecAI-Hubclaude
andcommitted
Add emergency wipe with 3-level panic system and securectl CLI (M23)
Three severity levels: Level 1 locks vault and stops services (reversible), Level 2 shreds all signing/encryption keys, Level 3 re-encrypts vault with random key making data unrecoverable. Levels 2+ require passphrase confirmation. Includes 5-second countdown, audit logging, and panic state tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 31b6536 commit 9196040

3 files changed

Lines changed: 685 additions & 2 deletions

File tree

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
[Unit]
2-
Description=Secure AI Panic Switch (emergency network kill + worker stop)
2+
Description=Secure AI Emergency Panic (securectl)
3+
After=secure-ai-firstboot.service
34

45
[Service]
56
Type=oneshot
6-
ExecStart=/usr/libexec/secure-ai/panic.sh
7+
ExecStart=/usr/libexec/secure-ai/securectl panic 1 --no-countdown
78
RemainAfterExit=yes
9+
10+
# Needs root for vault lock, process kill, key shred
11+
ProtectHome=yes
12+
PrivateTmp=yes
13+
ProtectKernelModules=yes
14+
ProtectControlGroups=yes
15+
LimitCORE=0
16+
17+
ReadWritePaths=/var/lib/secure-ai /run/secure-ai /etc/secure-ai
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Secure AI Appliance — securectl (M23)
4+
#
5+
# Emergency data protection tool with three severity levels:
6+
#
7+
# Level 1 — Lock:
8+
# Lock vault, kill workers, require re-auth. Fully reversible.
9+
#
10+
# Level 2 — Wipe Keys:
11+
# Delete LUKS header backup, delete cosign keys, lock vault.
12+
# Data recoverable only with passphrase.
13+
#
14+
# Level 3 — Full Wipe:
15+
# Re-encrypt vault with random key (data unrecoverable),
16+
# zero inference memory, delete all logs, delete registry.
17+
#
18+
# Usage:
19+
# securectl panic <level> [--confirm <passphrase>] [--no-countdown]
20+
# securectl status
21+
#
22+
set -euo pipefail
23+
24+
SECURE_AI_ROOT="/var/lib/secure-ai"
25+
AUTH_DIR="${SECURE_AI_ROOT}/auth"
26+
AUDIT_LOG="${SECURE_AI_ROOT}/logs/panic-audit.jsonl"
27+
PANIC_STATE="/run/secure-ai/panic-state.json"
28+
MAPPER_NAME="secure-ai-vault"
29+
MOUNT_POINT="${SECURE_AI_ROOT}"
30+
31+
log() {
32+
echo "[securectl] $*"
33+
logger -t securectl "$*" 2>/dev/null || true
34+
}
35+
36+
# --- Audit logging ---
37+
audit_panic() {
38+
local level="$1"
39+
local action="$2"
40+
local timestamp
41+
timestamp=$(date -Iseconds)
42+
43+
mkdir -p "$(dirname "$AUDIT_LOG")" 2>/dev/null || true
44+
45+
local entry
46+
entry=$(python3 -c "
47+
import json, hashlib
48+
entry = {
49+
'timestamp': '${timestamp}',
50+
'event': 'emergency_panic',
51+
'severity': 'CRITICAL',
52+
'level': ${level},
53+
'action': '${action}'
54+
}
55+
entry['hash'] = hashlib.sha256(json.dumps(entry, sort_keys=True).encode()).hexdigest()
56+
print(json.dumps(entry))
57+
" 2>/dev/null || echo '{"event":"emergency_panic","level":'${level}'}')
58+
echo "$entry" >> "$AUDIT_LOG" 2>/dev/null || true
59+
}
60+
61+
# --- Passphrase verification ---
62+
verify_passphrase() {
63+
local passphrase="$1"
64+
65+
if [ ! -f "${AUTH_DIR}/passphrase.json" ]; then
66+
log "WARNING: no passphrase configured, skipping verification"
67+
return 0
68+
fi
69+
70+
# Verify against stored scrypt hash
71+
python3 -c "
72+
import json, hashlib, base64, sys
73+
try:
74+
with open('${AUTH_DIR}/passphrase.json') as f:
75+
data = json.load(f)
76+
stored_hash = data['hash']
77+
salt = base64.b64decode(data['salt'])
78+
n = data.get('n', 32768)
79+
r = data.get('r', 8)
80+
p = data.get('p', 1)
81+
dk = hashlib.scrypt(sys.argv[1].encode(), salt=salt, n=n, r=r, p=p, dklen=64)
82+
if dk.hex() == stored_hash:
83+
sys.exit(0)
84+
else:
85+
sys.exit(1)
86+
except Exception as e:
87+
print(f'Verification error: {e}', file=sys.stderr)
88+
sys.exit(1)
89+
" "$passphrase" 2>/dev/null
90+
}
91+
92+
# --- Countdown ---
93+
countdown() {
94+
local seconds="${1:-5}"
95+
echo ""
96+
echo " *** EMERGENCY ACTION IN ${seconds} SECONDS ***"
97+
echo " Press Ctrl+C to cancel."
98+
echo ""
99+
for i in $(seq "$seconds" -1 1); do
100+
echo " ${i}..."
101+
sleep 1
102+
done
103+
echo ""
104+
}
105+
106+
# --- Write panic state ---
107+
write_panic_state() {
108+
local level="$1"
109+
local status="$2"
110+
mkdir -p "$(dirname "$PANIC_STATE")" 2>/dev/null || true
111+
cat > "$PANIC_STATE" <<EOF
112+
{
113+
"panic_active": true,
114+
"level": ${level},
115+
"status": "${status}",
116+
"timestamp": "$(date -Iseconds)"
117+
}
118+
EOF
119+
chmod 644 "$PANIC_STATE"
120+
}
121+
122+
# --- Level 1: Lock ---
123+
panic_level_1() {
124+
log "PANIC LEVEL 1: LOCK"
125+
audit_panic 1 "lock"
126+
write_panic_state 1 "locking"
127+
128+
# Stop all AI services
129+
log "Stopping all AI services..."
130+
systemctl stop secure-ai-inference.service 2>/dev/null || true
131+
systemctl stop secure-ai-diffusion.service 2>/dev/null || true
132+
systemctl stop secure-ai-registry.service 2>/dev/null || true
133+
systemctl stop secure-ai-tool-firewall.service 2>/dev/null || true
134+
systemctl stop secure-ai-quarantine-watcher.service 2>/dev/null || true
135+
systemctl stop secure-ai-search-mediator.service 2>/dev/null || true
136+
137+
# Kill any remaining workers
138+
pkill -f "llama-server" 2>/dev/null || true
139+
pkill -f "diffusion-worker" 2>/dev/null || true
140+
141+
# Lock vault
142+
log "Locking vault..."
143+
sync
144+
umount "${MOUNT_POINT}" 2>/dev/null || true
145+
cryptsetup close "${MAPPER_NAME}" 2>/dev/null || true
146+
147+
# Invalidate all sessions
148+
if [ -d "${AUTH_DIR}/sessions" ]; then
149+
rm -rf "${AUTH_DIR}/sessions"/* 2>/dev/null || true
150+
fi
151+
152+
write_panic_state 1 "locked"
153+
log "LEVEL 1 COMPLETE: vault locked, services stopped, sessions invalidated"
154+
}
155+
156+
# --- Level 2: Wipe Keys ---
157+
panic_level_2() {
158+
log "PANIC LEVEL 2: WIPE KEYS"
159+
audit_panic 2 "wipe_keys"
160+
write_panic_state 2 "wiping_keys"
161+
162+
# First do everything Level 1 does
163+
panic_level_1
164+
165+
# Delete LUKS header backup
166+
log "Deleting LUKS header backup..."
167+
if [ -f "${SECURE_AI_ROOT}/keys/luks-header-backup" ]; then
168+
shred -vfz -n 3 "${SECURE_AI_ROOT}/keys/luks-header-backup" 2>/dev/null || true
169+
rm -f "${SECURE_AI_ROOT}/keys/luks-header-backup" 2>/dev/null || true
170+
fi
171+
172+
# Delete cosign keys
173+
log "Deleting cosign signing keys..."
174+
find "${SECURE_AI_ROOT}/keys" -name "*.key" -o -name "*.pem" -o -name "*.pub" 2>/dev/null | while read -r keyfile; do
175+
shred -vfz -n 3 "$keyfile" 2>/dev/null || true
176+
rm -f "$keyfile" 2>/dev/null || true
177+
done
178+
179+
# Delete TPM2 sealed keys
180+
if [ -d "${SECURE_AI_ROOT}/keys/tpm2" ]; then
181+
shred -vfz -n 3 "${SECURE_AI_ROOT}/keys/tpm2"/* 2>/dev/null || true
182+
rm -rf "${SECURE_AI_ROOT}/keys/tpm2" 2>/dev/null || true
183+
fi
184+
185+
# Delete MOK private key
186+
find /etc/secure-ai -name "*.key" 2>/dev/null | while read -r keyfile; do
187+
shred -vfz -n 3 "$keyfile" 2>/dev/null || true
188+
rm -f "$keyfile" 2>/dev/null || true
189+
done
190+
191+
write_panic_state 2 "keys_wiped"
192+
log "LEVEL 2 COMPLETE: all signing/encryption keys destroyed"
193+
}
194+
195+
# --- Level 3: Full Wipe ---
196+
panic_level_3() {
197+
log "PANIC LEVEL 3: FULL WIPE"
198+
audit_panic 3 "full_wipe"
199+
write_panic_state 3 "full_wipe"
200+
201+
# First do everything Level 2 does
202+
panic_level_2
203+
204+
# Re-encrypt vault with random key (makes data unrecoverable)
205+
log "Re-encrypting vault with random key..."
206+
local vault_device
207+
vault_device=$(awk -v name="${MAPPER_NAME}" '$1 == name {print $2}' /etc/crypttab 2>/dev/null || echo "")
208+
if [ -n "$vault_device" ] && [ -b "$vault_device" ]; then
209+
# Generate random passphrase and re-encrypt
210+
local random_key
211+
random_key=$(head -c 64 /dev/urandom | base64)
212+
echo -n "$random_key" | cryptsetup luksErase "$vault_device" --batch-mode 2>/dev/null || true
213+
log "Vault encryption key destroyed"
214+
fi
215+
216+
# Zero inference memory (drop caches)
217+
log "Clearing memory..."
218+
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
219+
220+
# Delete all logs
221+
log "Deleting all logs..." # This is the last log that will be written
222+
find "${SECURE_AI_ROOT}/logs" -type f -name "*.jsonl" -o -name "*.log" 2>/dev/null | while read -r logfile; do
223+
shred -vfz -n 1 "$logfile" 2>/dev/null || true
224+
rm -f "$logfile" 2>/dev/null || true
225+
done
226+
227+
# Delete registry (models)
228+
log "Deleting model registry..."
229+
if [ -d "${SECURE_AI_ROOT}/registry" ]; then
230+
rm -rf "${SECURE_AI_ROOT}/registry"/* 2>/dev/null || true
231+
fi
232+
233+
# Delete auth data
234+
if [ -d "${AUTH_DIR}" ]; then
235+
shred -vfz -n 1 "${AUTH_DIR}"/* 2>/dev/null || true
236+
rm -rf "${AUTH_DIR}"/* 2>/dev/null || true
237+
fi
238+
239+
# Delete canary database
240+
shred -vfz -n 1 "${SECURE_AI_ROOT}/canary-db.json" 2>/dev/null || true
241+
rm -f "${SECURE_AI_ROOT}/canary-db.json" 2>/dev/null || true
242+
243+
# Remove firstboot marker to force fresh setup
244+
rm -f "${SECURE_AI_ROOT}/.firstboot-done" 2>/dev/null || true
245+
246+
write_panic_state 3 "wiped"
247+
log "LEVEL 3 COMPLETE: all data destroyed, system in factory-reset state"
248+
}
249+
250+
# --- Status ---
251+
show_status() {
252+
if [ -f "$PANIC_STATE" ]; then
253+
cat "$PANIC_STATE"
254+
else
255+
echo '{"panic_active": false}'
256+
fi
257+
}
258+
259+
# --- Main ---
260+
cmd="${1:-help}"
261+
shift || true
262+
263+
case "$cmd" in
264+
panic)
265+
level="${1:-}"
266+
shift || true
267+
268+
if [ -z "$level" ] || ! echo "$level" | grep -qE '^[123]$'; then
269+
echo "Usage: securectl panic <1|2|3> [--confirm <passphrase>] [--no-countdown]"
270+
echo ""
271+
echo "Levels:"
272+
echo " 1 — Lock vault, stop services (reversible)"
273+
echo " 2 — Wipe keys + Level 1 (data needs passphrase)"
274+
echo " 3 — Full wipe + Level 2 (data unrecoverable)"
275+
exit 1
276+
fi
277+
278+
# Parse options
279+
passphrase=""
280+
do_countdown="true"
281+
while [ $# -gt 0 ]; do
282+
case "$1" in
283+
--confirm)
284+
passphrase="${2:-}"
285+
shift 2 || { echo "ERROR: --confirm requires a passphrase"; exit 1; }
286+
;;
287+
--no-countdown)
288+
do_countdown="false"
289+
shift
290+
;;
291+
*)
292+
echo "Unknown option: $1"
293+
exit 1
294+
;;
295+
esac
296+
done
297+
298+
# Level 2 and 3 require passphrase confirmation
299+
if [ "$level" -ge 2 ]; then
300+
if [ -z "$passphrase" ]; then
301+
echo "ERROR: Level ${level} requires --confirm <passphrase>"
302+
exit 1
303+
fi
304+
if ! verify_passphrase "$passphrase"; then
305+
echo "ERROR: passphrase verification failed"
306+
exit 1
307+
fi
308+
fi
309+
310+
echo ""
311+
echo " =========================================="
312+
echo " EMERGENCY PANIC — LEVEL ${level}"
313+
echo " =========================================="
314+
case "$level" in
315+
1) echo " Action: Lock vault, stop services" ;;
316+
2) echo " Action: WIPE ALL KEYS + lock" ;;
317+
3) echo " Action: FULL WIPE — DATA UNRECOVERABLE" ;;
318+
esac
319+
echo " =========================================="
320+
321+
if [ "$do_countdown" = "true" ]; then
322+
countdown 5
323+
fi
324+
325+
case "$level" in
326+
1) panic_level_1 ;;
327+
2) panic_level_2 ;;
328+
3) panic_level_3 ;;
329+
esac
330+
;;
331+
332+
status)
333+
show_status
334+
;;
335+
336+
help|--help|-h)
337+
echo "securectl — Secure AI Appliance Emergency Control"
338+
echo ""
339+
echo "Commands:"
340+
echo " panic <1|2|3> Execute emergency action at specified level"
341+
echo " status Show current panic state"
342+
echo ""
343+
echo "Options:"
344+
echo " --confirm <passphrase> Required for levels 2 and 3"
345+
echo " --no-countdown Skip 5-second countdown"
346+
;;
347+
348+
*)
349+
echo "Unknown command: $cmd"
350+
echo "Run 'securectl help' for usage."
351+
exit 1
352+
;;
353+
esac

0 commit comments

Comments
 (0)