|
| 1 | +# Incident Response & Key-Compromise Runbook |
| 2 | + |
| 3 | +How to respond when something in the trust chain goes wrong. The planned-upgrade and |
| 4 | +rollback flow lives in [vm-code-upgrade.md](vm-code-upgrade.md); this document covers |
| 5 | +the *adversarial* and *failure* scenarios that one does not. |
| 6 | + |
| 7 | +> **Status:** several response steps below depend on operational decisions that are |
| 8 | +> not yet finalized (marked **TODO/DECIDE**). Treat this as the working runbook and |
| 9 | +> resolve the open items before relying on it in a real incident. |
| 10 | +
|
| 11 | +## Roles & contacts |
| 12 | + |
| 13 | +| Role | Who | Responsibility | |
| 14 | +|---|---|---| |
| 15 | +| Incident commander | **TODO/DECIDE** | Owns the response, declares severity, coordinates comms | |
| 16 | +| Safe signers | 4 keyholders (2-of-4 quorum) | Execute on-chain governance actions (whitelist removal, owner change) | |
| 17 | +| Phala account owner | **TODO/DECIDE** | Holds `PHALA_CLOUD_API_KEY`; can redeploy/stop CVMs | |
| 18 | +| Comms / disclosure | **TODO/DECIDE** | External notifications, status page, `security@` triage | |
| 19 | + |
| 20 | +Confirm the live Safe signer set before any action: |
| 21 | + |
| 22 | +```bash |
| 23 | +cast call 0xF688411c0FFc300cAb33EB1dA651DBb3E6891098 \ |
| 24 | + "getOwners()(address[])" --rpc-url https://mainnet.base.org |
| 25 | +``` |
| 26 | + |
| 27 | +## Key facts that shape the response |
| 28 | + |
| 29 | +- **Governance is 2-of-4 with no timelock.** A quorum can act immediately — good for |
| 30 | + emergency response, but also means two compromised signer keys are sufficient to |
| 31 | + change the whitelist. There is no delay to "catch" a malicious change in flight; |
| 32 | + detection must be real-time (see [Monitoring](#detective-controls)). |
| 33 | +- **Key derivation is deterministic per compose hash.** The same attested code always |
| 34 | + derives the same application keys from the KMS root. This is why rollback works |
| 35 | + without re-issuance — and why a compromised *code version* that obtained keys must |
| 36 | + be treated as having compromised the data those keys protect. |
| 37 | +- **Removing a compose hash blocks key issuance, not a running process.** See below. |
| 38 | + |
| 39 | +## Scenario 1 — Malicious or vulnerable compose hash got whitelisted |
| 40 | + |
| 41 | +Goal: stop the bad version from obtaining keys and serving. |
| 42 | + |
| 43 | +1. **Revoke the whitelist.** Have the Safe execute `removeComposeHash(badHash)` on |
| 44 | + `DstackApp` (`0x3F91…05FfC`). 2-of-4 approval required. |
| 45 | +2. **Stop the running CVM.** Revocation prevents *future* key issuance and prevents |
| 46 | + the version from booting again — but a CVM that already holds its keys keeps |
| 47 | + running until restarted. The Phala account owner must explicitly **stop/redeploy** |
| 48 | + the affected CVM via the Phala CLI/dashboard to take it out of service. |
| 49 | + **TODO/DECIDE:** confirm exact Phala command and whether gateway can drain traffic |
| 50 | + first. |
| 51 | +3. **Re-deploy a known-good version** whose compose hash is still whitelisted. |
| 52 | +4. **Assess data exposure.** Any data the bad version could decrypt with its derived |
| 53 | + keys must be considered exposed. |
| 54 | + |
| 55 | +## Scenario 2 — Safe signer key compromised |
| 56 | + |
| 57 | +1. **If still below quorum (1 key):** rotate immediately but no funds/governance at |
| 58 | + immediate risk. The remaining 3 honest signers still control the 2-of-4. |
| 59 | +2. **Replace the signer:** Safe executes `swapOwner` (or `removeOwner` + |
| 60 | + `addOwnerWithThreshold`) to drop the compromised key. 2-of-4 of the *honest* |
| 61 | + signers required. |
| 62 | +3. **If two keys compromised (quorum reached by attacker):** treat as full governance |
| 63 | + compromise — the attacker can whitelist arbitrary code. **TODO/DECIDE:** there is |
| 64 | + currently no higher-authority backstop above the Safe. Mitigations to evaluate: |
| 65 | + raising the threshold, adding a timelock to create a reaction window, or a |
| 66 | + guardian/recovery module. |
| 67 | + |
| 68 | +## Scenario 3 — `PHALA_CLOUD_API_KEY` leaked |
| 69 | + |
| 70 | +The Phala API key can redeploy and reconfigure CVMs and read/set CVM secrets. It |
| 71 | +**cannot** mint keys for un-whitelisted code (the on-chain gate still holds), but it |
| 72 | +can disrupt availability and reconfigure encrypted env injection. |
| 73 | + |
| 74 | +1. **Revoke the key** in the Phala dashboard (Avatar → API Tokens) and issue a new one. |
| 75 | +2. **Rotate all CVM-injected secrets** (see Scenario 5) — assume they were readable. |
| 76 | +3. **Audit recent deployments** for unexpected CVM changes. |
| 77 | + |
| 78 | +## Scenario 4 — Base / KMS availability failure |
| 79 | + |
| 80 | +- The KMS issues keys at boot. If Base RPC or the KMS is unreachable, **already-running |
| 81 | + CVMs are unaffected** (they hold their keys); only restarts/new boots are blocked. |
| 82 | +- Do **not** force a redeploy during a KMS outage — a restarted CVM may fail to |
| 83 | + obtain keys and go down. |
| 84 | +- **TODO/DECIDE:** document RPC redundancy / fallback endpoints used by the KMS path. |
| 85 | + |
| 86 | +## Scenario 5 — Secret compromise (Stripe, AWS, GCP, RPC) |
| 87 | + |
| 88 | +These are injected as encrypted Phala env vars and are **outside the attestation |
| 89 | +measurement** (not part of the compose hash). See |
| 90 | +[secrets-management.md](secrets-management.md). |
| 91 | + |
| 92 | +1. Rotate the upstream credential (Stripe dashboard, AWS IAM, GCP service account). |
| 93 | +2. Update the encrypted Phala secret and redeploy. |
| 94 | +3. Scope the blast radius per credential (e.g. Route53 IAM should be DNS-01 only). |
| 95 | + |
| 96 | +## Detective controls |
| 97 | + |
| 98 | +Real-time detection matters more here than usual because there is no governance |
| 99 | +timelock to provide a reaction window. |
| 100 | + |
| 101 | +- **Alert on governance writes.** Monitor `DstackApp` and the Safe for |
| 102 | + `addComposeHash` / `removeComposeHash`, `addOwner` / `swapOwner` / `removeOwner`, |
| 103 | + and `changeThreshold` events; page on any unplanned occurrence. **TODO/DECIDE:** |
| 104 | + wire this into the existing Grafana stack (see chipotle monitoring) or a |
| 105 | + contract-watcher. |
| 106 | +- **Alert on attestation drift.** Periodically confirm the live `compose_hash` from |
| 107 | + `/info` is still whitelisted and matches the expected release. |
| 108 | +- **Alert on CVM lifecycle changes** from the Phala account. |
| 109 | + |
| 110 | +## Post-incident |
| 111 | + |
| 112 | +- Capture a timeline and the on-chain transaction hashes of every governance action. |
| 113 | +- File a coordinated-disclosure note per [SECURITY.md](../../SECURITY.md) if external |
| 114 | + parties are affected. |
| 115 | +- Open follow-ups for any **TODO/DECIDE** item this incident exercised. |
| 116 | + |
| 117 | +## References |
| 118 | + |
| 119 | +- [vm-code-upgrade.md](vm-code-upgrade.md) — planned upgrade & rollback |
| 120 | +- [derot-key-issuance.md](derot-key-issuance.md) — how keys are issued |
| 121 | +- [secrets-management.md](secrets-management.md) — out-of-band secret injection |
| 122 | +- [SECURITY.md](../../SECURITY.md) — disclosure policy |
0 commit comments