> ## Documentation Index
> Fetch the complete documentation index at: https://docs.afauth.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Recover a compromised key

> Your agent's key leaked. Lock the thief out, re-key, and re-link — in the right order — to get the agent running again.

Your agent's private key is compromised. This runbook locks the attacker out and restores service. It applies to **claimed** accounts — the agent can't fix this itself (its signature is exactly what's compromised), so recovery runs through the **human owner** and, if you use it, the **trust attestor**.

<Note>
  Pre-claim (unclaimed) accounts have no owner to recover through — re-create the account with a fresh key. See [Rotate keys](/guides/rotate-keys).
</Note>

## Order matters

Recovery spans two layers that share no transaction — the attestor (stops new attestations) and each service (holds its own revocation list and account state); these are the **global** and **local** levers of the [revocation model](/concepts/revocation). **You are the sequencer**, and two asymmetries make the order load-bearing:

* **Per-binding revoke is permanent; whole-account pause is reversible.** Resuming a paused account restores minting for *every* binding you didn't separately revoke — so a careless pause-then-resume silently re-arms the stolen key.
* **A re-key produces a new DID with no attestor binding.** Re-keying restores the agent at your *services*, but can't reach `attested_only` services until you re-link the new key at the attestor.

Do these in order:

1. **Revoke the compromised binding** at the attestor — permanent.
2. **Revoke the key** at each service that trusts the signature directly (§8.4).
3. **Re-key** each of those services to a fresh key (§8.2).
4. **Re-link** the new key at the attestor.
5. **Resume** the account — only if you blanket-paused it in step 1, and only after the compromised binding is revoked.

<Steps>
  <Step title="Revoke the compromised binding at the attestor — permanent">
    If your agent links to a human through [the trust attestor](/concepts/trust-attestor), sign in at the attestor dashboard, find the compromised agent under **Linked agents**, and **Revoke** it. This is per-agent and **permanent** — there is no un-revoke — so it's the durable containment for the stolen key, and it leaves your other agents running.

    Revoking stops new attestation tokens for that key immediately. Services that re-check attestation on each request — or that hold an [attested session](/guides/keep-attested-access-live) (§10.7) — lock the thief out within the token lifetime (≤ 15 min), since the agent can no longer mint a refresh. Tokens already minted stay valid until they expire — revoking stops *new* issuance, it does not recall outstanding tokens.

    <Warning>
      Prefer per-binding **Revoke** over **Pause all agents** for a single stolen key. Pausing stops *every* agent under the human and is **reversible**: resuming restores minting for every binding you didn't revoke. Use **Pause** only as a temporary blanket stop when you can't tell which agent leaked — and **still revoke the compromised binding before resuming** (step 5).
    </Warning>
  </Step>

  <Step title="Revoke the key at each service that trusts it directly">
    A service that authenticates the agent's **signature** — rather than re-checking an attestation on every request — never hears about the attestor revoke. At each such service the owner revokes the key: see [Revoke an account](/guides/revoke-an-account). The old key then fails with `401 revoked_key`.
  </Step>

  <Step title="Re-key each service to resume (§8.2)">
    Revoke pauses the account. To bring the agent back **on the same account**, the owner re-keys from a fresh owner session:

    ```ts theme={null}
    // POST /afauth/v1/accounts/me/keys/rekey
    // body { current_account_did, new_account_did }
    export async function handleOwnerReKey(req: Request, session: OwnerSession) {
      return server.handleKeyReKey(req, session);
    }
    ```

    The owner binding carries forward and **`account_id` is stable** — the re-key swaps the credential within the same account (§10.4.4: one account, many devices), it does not create a new one. The agent's credential becomes the **new** DID (returned in the response as `agent_did`); the old DID stays revoked. The agent restarts with the new key.
  </Step>

  <Step title="Re-link the new key at the attestor">
    A re-key gives the agent a **new** `did:key`, and the attestor binding was keyed to the *old* DID — so nothing carries over automatically. Until you re-link, the new key has no attestation and **cannot reach `attested_only` services** (the SDK default).

    Re-run the link flow under the new key, from the same human account:

    ```bash theme={null}
    afauth trust link        # run as the re-keyed agent; the human confirms as before
    ```

    This creates a fresh binding for the new DID. Because `sub_h` is keyed on the **human**, not the key, each service sees the **same** `sub_h` as before — per-human policy and state carry forward (§10.5.1). Full flow: [Link your agent to a human](/guides/link-to-a-human).
  </Step>

  <Step title="Resume the account — only if you paused it">
    If in step 1 you used the blanket **Pause all agents** (not per-binding revoke), resume it now at `trust.afauth.org/account` — resuming requires a fresh sign-in (§7.5). **Resume only after the compromised binding is revoked**: resuming restores minting for every binding that isn't individually revoked.

    If you used per-binding **Revoke** in step 1, skip this — the account was never paused.
  </Step>
</Steps>

## What recovery can and can't reach

* **The agent's local state is untrusted.** Don't rely on the agent cleaning up its own cache or ledger — the attacker controls it. Recovery is owner- and service-side only.
* **Signature-only, non-attested services are a blind spot for attestor-side action.** Only that service's own revoke reaches them (step 2). For high-value operations, gate on live attestation — per-request, or a lighter-weight [attested session](/guides/keep-attested-access-live) (§10.7) — so an attestor-side revoke actually takes effect.
* **Re-key swaps the credential, not the account.** `account_id` is stable across a re-key (§10.4.4), so the owner binding and any state you keyed on `account_id` carry forward; only the agent's credential DID changes (returned as `agent_did`), so service-internal state keyed on the raw DID must be moved. Agent credentials are [`did:key`](/concepts/identity-and-keys) — there is no stable-identifier agent *DID* method, so the credential rolling on re-key is inherent.

## You don't need routine rotation

Ed25519 auth keys have no usage-based wear — there's no security reason to rotate on a schedule. Rotate to recover from compromise (this guide) or to migrate identifiers, not by the calendar.

Spec: [§8.2](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#82-post-claim-key-rotation-re-key), [§8.4](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#84-owner-initiated-revocation), [§8.5](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#85-revocation-coverage-and-its-limits).
