# Tutorial: Live fail-closed safety and the kill-switch

**Audience:** Operators who can run a Live session (see
[first Live session](live-first-execution.md)) and now want to see the safety
behavior: how Live degrades to the safe shell when something is wrong, and how
to stop new high-interaction sessions on demand.

**Time:** about 25-35 minutes
**Outcome:** You deliberately break each Live gate, confirm the sensor keeps
serving the safe Emulated shell (never executes), and exercise the kill-switch
drain so new Live sessions are refused while an in-progress one is left alone.

!!! info "The point of this tutorial"
    Live is the most dangerous tier, so it is designed to **bias toward safety**.
    Every failure path lands on "no real execution" rather than "execute anyway."
    Seeing this yourself is the best way to trust it.

## What you will learn

1. What "fail closed" means for each Live gate
2. How to read `downgrade_reason` to diagnose why Live did not engage
3. What happens when the backend VM is unreachable
4. How the kill-switch / drain refuses new Live sessions without cutting off an
   active engagement (sticky-safe)

## Prerequisites

- The setup from [your first Live session](live-first-execution.md): a
  disposable backend VM and a `sensor` you can start in Live tier.
- Two terminals (one to drive the sensor, one to act as the attacker).

## Part A: Break the gates, watch it fail closed

Live requires the ack, production profile, and a backend zone. Remove each and
observe the downgrade.

### A1: Missing acknowledgment

Start the sensor **without** the ack:

```bash
unset CYBERHALLUCINET_LIVE_ACK
sensor --profile=production --interaction=live \
  --listen-status=127.0.0.1:8080 --listen-ssh=127.0.0.1:2222 \
  --live-backend='guest:guestpw@127.0.0.1:22022'
```

Check the tier:

```bash
curl -s http://127.0.0.1:8080/healthz | python3 -m json.tool
```

Expected:

```json
{ "requested_tier": "live", "active_tier": "contained",
  "downgrade_reason": "live_ack_required" }
```

The sensor did not enter Live. Connect as an attacker, you get the synthetic
shell, and nothing runs on the backend. Stop the sensor.

### A2, Not production profile

```bash
export CYBERHALLUCINET_LIVE_ACK=I_UNDERSTAND_LIVE_INTERACTION
sensor --profile=research --interaction=live \
  --listen-status=127.0.0.1:8080 --listen-ssh=127.0.0.1:2222 \
  --live-backend='guest:guestpw@127.0.0.1:22022'
```

`downgrade_reason` is now `production_attestation_required`. Again: safe shell,
no execution. Stop the sensor.

### A3: Live requested, but no backend configured

```bash
export CYBERHALLUCINET_LIVE_ACK=I_UNDERSTAND_LIVE_INTERACTION
sensor --profile=production --interaction=live \
  --listen-status=127.0.0.1:8080 --listen-ssh=127.0.0.1:2222 \
  --live-backend=''
```

Here the tier **resolves to `live`** (all gates pass, production + ack + Z13 via
`CYBERHALLUCINET_Z13` if you set it), but with no backend the startup log says:

```
sensor: ... no --live-backend configured; serving Emulated shell (no real execution)
```

`active_tier` reports `live`, yet attacker commands hit the **Emulated** shell.
Lesson: `active_tier=live` alone does not mean execution: a backend must be
configured. Stop the sensor.

## Part B: Backend unreachable → degrade to Emulated

Now pass all gates but point at a **dead** backend port (nothing listening):

```bash
export CYBERHALLUCINET_LIVE_ACK=I_UNDERSTAND_LIVE_INTERACTION
sensor --profile=production --interaction=live \
  --listen-status=127.0.0.1:8080 --listen-ssh=127.0.0.1:2222 \
  --live-backend='guest:guestpw@127.0.0.1:59999'   # closed port
```

Connect as the attacker and run a command. Because the proxy cannot dial the
backend, the lease is released and the engagement is served by the **Emulated
virtual shell**, you still get plausible output, but nothing executes remotely.

Confirm the downgrade was recorded. In the sensor logs / telemetry you will see
a containment event:

```
kind=live_downgrade target=backend_unavailable   (or open_failed)
```

Key safety property: a backend failure never crashes the decoy and never leaks
that it *tried* to go Live: it just serves the safe shell.

## Part C: Kill-switch drain (sticky-safe)

Now the important operational control: stopping **new** Live sessions without
cutting off one already in progress.

1. Start a healthy Live sensor (as in the first tutorial, real backend
   reachable).
2. As attacker #1, open an interactive session and keep it open:

```bash
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null app@127.0.0.1
# leave this shell open; run a command now and then
```

3. Trigger a withdraw / kill-switch. Any of these begins drain:
   - the resource-guard shed path (if the watchdog trips), or
   - the emergency stop:

```bash
./bin/honeypotctl emergency local-stop     # or deploy/kill-switch/local-stop.sh
```

4. Observe the behavior:
   - **New** Live sessions are refused (a fresh attacker connecting now gets the
     safe Emulated shell / no new lease).
   - The **already-open** session keeps working until it ends or is reaped : 
     it is **not** flipped to the fake shell mid-stream.

This is the sticky-safe rule (`SEC-L07`): draining sheds capacity and stops new
high-interaction engagements, but never performs a jarring mid-session
Live→Contained downgrade that would tip off an attacker.

!!! note "Time-box and idle reap"
    Even without a kill-switch, leases end on their own: `--live-session-minutes`
    time-boxes a session and `--live-idle-minutes` reaps idle ones. A 30-second
    reaper enforces both.

## Checkpoint: did it work?

- [ ] Each broken gate produced the matching `downgrade_reason` and no execution
- [ ] `active_tier=live` with no backend still served the Emulated shell
- [ ] An unreachable backend degraded to Emulated and logged `live_downgrade`
- [ ] After drain, new Live sessions were refused but an active one continued

## What to read next

- [Tutorial: a pool of backend VMs](live-backend-pool.md)
- [Reference: downgrade reasons & events](../reference/live-proxy.md#observable-events-containment-intents)
- [Explanation: fail-closed behavior](../architecture/live-proxy.md#fail-closed-behavior)
- [Emergency stop](../how-to/emergency.md)
