# Contain an Endpoint

> Contain a Whisper endpoint: flip its firewall default to deny for a hold you can lift, revoke the identity for good, or act on the host itself.

*Source: https://www.whisper.security/docs/endpoint/operate/contain*
*Published: 2026-09-19*
*Last updated: 2026-09-19*

---
Start with the hold you can lift. Reach for the permanent cut only when you mean it.

| Layer | Action | What it does | What it needs |
|---|---|---|---|
| Network, off the host | `firewall default-deny` | flips the address's default from allow to deny, so every flow not explicitly allowed stops at the edge | nothing on the machine |
| Network, off the host | `revoke` | cuts the identity itself: DNS stops resolving, the per-address firewall drops, and the forward-confirmed reverse DNS chain is withdrawn | nothing on the machine |
| Host | `kill` | the sensor ends a running process, pinned so it cannot be swapped underneath | the sensor armed for response |
| Host | `quarantine` | the sensor moves a file aside, hash-verified | the sensor armed for response |

The two network actions act on Whisper infrastructure, not on the endpoint, which is why they hold even when the machine is entirely in someone else's hands. [The response ladder](/docs/endpoint/operate/response) is why they are separated this way.

## 1. Get the address

```bash
whisper list
```

Or take it from the host's row in the console's fleet view. Everything below is keyed on that address.

## 2. Place the hold

Set the default to deny, and allow only what the machine needs while you work on it. The first matching rule wins, and anything that matches nothing falls to the default.

```bash
curl -s -X POST https://graph.whisper.online/api/query \
  -H 'content-type: application/json' \
  -H 'X-API-Key: <your key>' \
  --data-binary @- <<'JSON'
{"query": "CALL whisper.agents({op: 'firewall', address: '2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478', default: 'deny', rules: [{action: 'allow', match: 'host', value: 'updates.example.com'}]})"}
JSON
```

A rule matches on `ip`, `cidr`, `host` or `port`. A `host` value covers that name and everything under it.

## 3. Read it back

Omit `rules` and the same call answers with what is in force:

```bash
curl -s -X POST https://graph.whisper.online/api/query \
  -H 'content-type: application/json' \
  -H 'X-API-Key: <your key>' \
  --data-binary @- <<'JSON'
{"query": "CALL whisper.agents({op: 'firewall', address: '2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478'})"}
JSON
```

Do this before you tell anyone the machine is held. The read-back is the evidence; the write only says the request was accepted.

## 4. Lift it

The same call with `default: 'allow'` returns the address to normal. Nothing else needs undoing, and the host does not need to be reachable for the hold to lift.

## Cutting the address for good

```bash
whisper kill --revoke 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
```

```text
whisper: 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478 - released
```

This withdraws the address, its reverse DNS and its egress credentials, and refuses the identity's access to the control plane. It is permanent. The machine can be brought back later, but under a new address, with a new record and none of the old one's history attached to it. The command confirms before it acts, and in a non-interactive run `--yes` is required, because nothing should be able to destroy an identity without somebody saying so.

Your key needs the authority to revoke, which is not the same grant as the one that writes a firewall rule: see [Keys and scopes](/docs/control-plane/auth). The wire contract for both is [Firewall and response](/docs/control-plane/operations/contain), and [Revoke](/docs/control-plane/operations/revoke) covers the identity side on its own.

## Proving it held

Every containment is committed to a tamper-evident, signed transparency log, so what was cut and when can be shown to somebody who does not have your key and does not take your word for it. [The transparency log](/docs/identity/transparency) is how to read it, and once an address is revoked the keyless checks in [verifying an identity](/docs/identity/verify) answer that it is gone.
