Containment Chamber exposes six logical API surfaces on a single HTTP server. Understanding how they relate — and how a request moves from the network boundary to a signature — makes the configuration and auth model easier to reason about.
The Six Surfaces
Section titled “The Six Surfaces”| Surface | Route Prefix | Purpose |
|---|---|---|
| Signing | /api/v1/eth2/* |
Web3Signer-compatible BLS signing and key discovery |
| Key Manager | /eth/v1/keystores |
EIP-3076 hot keystore loading and removal |
| Chamber Operations | /api/v1/chamber/status, /api/v1/chamber/seal, /api/v1/chamber/attestation |
Status, break-glass seal, and attestation |
| Chamber Keys | /api/v1/chamber/keys/* |
DynamoDB-backed key generation, import, and lifecycle |
| Auth | /api/v1/auth/* |
Runtime policy and token management |
| Health | /upcheck |
Liveness probe — no auth required |
Metrics are served on a separate port (default 3000) and are not part of the main API surface.
All chamber, signing, key-management, and auth routes participate in policy-based auth evaluation. The Key Manager API uses the same auth engine, accessed by validator clients speaking the standard EIP-3076 protocol.
Use the API Reference for the exact endpoint list, request schemas, response schemas, and error responses for each surface.
Request Lifecycle
Section titled “Request Lifecycle”-
HTTP ingress
Axum receives the request. Tower middleware applies backpressure (
LoadShedLayer→ConcurrencyLimitLayer→TimeoutLayer). If the queue is full, the client receives 503 before any auth or signing logic runs. -
Token extraction
The
Authorizationheader is parsed. Bearer tokens are primary; HTTP Basic is accepted as a compatibility shim (username ignored, password treated as the token). No header → unauthenticated request. -
CIDR check
If the token is CIDR-bound, the source IP is checked against the allowed ranges. Failure returns 403.
-
Policy evaluation
The evaluator loads the token’s bound policies and checks each rule against the request:
- Does the route’s required scope match an allowed scope?
- Does the requested public key match an allowed key list?
- Does the requested signing operation match an allowed operation list?
Matching rules use deny-overrides-allow semantics. Any matching deny rejects the request; otherwise at least one matching allow is required. If no rule matches, the default is deny.
-
Signing pipeline (signing routes only)
- Network guard validates
fork_infoagainst the configured network. Mismatch → 400. - Anti-slashing checks the request against the protection database. Slashable → 412.
- Key lookup finds the private key. Missing → 404.
- BLS signing produces the signature and returns it.
- Network guard validates
Token Lifecycle
Section titled “Token Lifecycle”Tokens are created at runtime and never stored in plaintext. The server only persists the HMAC-SHA256 hash of the token secret.
The operator CLI is the normal way to manage tokens. If you are integrating directly, the API Reference documents the token-create, lookup, renew, and revoke schemas.
-
Creation
A management token calls
POST /api/v1/auth/tokens. The server generates a random secret, derives an accessor, hashes the secret for storage, and returns the plaintext token once. It is never retrievable again. -
Usage
The client sends the token as
Authorization: Bearer <token>. The server hashes the presented value and compares it against stored hashes. -
Expiry
Tokens with a
ttl_seconds > 0expire automatically. The server rejects expired tokens with 401. -
Revocation
A management token calls
DELETE /api/v1/auth/tokens/<accessor>. The stored hash is removed; the token is immediately invalid.
Scope Model
Section titled “Scope Model”Scopes are coarse-grained capabilities that map to route families. They are not fine-grained HTTP-method permissions — a scope grants access to an entire functional area.
| Scope | Grants access to |
|---|---|
sign |
All signing routes (/api/v1/eth2/sign/*) |
public_keys |
Key discovery (/api/v1/eth2/publicKeys) |
list_keys |
EIP-3076 keystore listing (/eth/v1/keystores) |
import_keystores |
EIP-3076 keystore import (/eth/v1/keystores) |
delete_keys |
EIP-3076 keystore deletion (/eth/v1/keystores) |
chamber_status |
Seal state and health details |
chamber_seal |
Break-glass seal operation |
chamber_keys_list |
Chamber key inventory |
chamber_keys_generate |
DynamoDB key generation |
chamber_keys_import |
Runtime key import |
chamber_keys_patch |
Activate / deactivate chamber keys |
chamber_keys_delete |
Remove chamber keys |
Why scopes instead of HTTP methods? Because the API is resource-oriented, not CRUD-oriented. A validator client needs sign and public_keys — it does not care about GET vs POST. Scopes express intent, not mechanics.
For the precise endpoint-to-scope mapping, use the API Reference. This page explains the model; Scalar is the source of truth for path-level details.
Chamber Lifecycle
Section titled “Chamber Lifecycle”The chamber lifecycle is fully automatic — there are no operator ceremony steps, no passphrases, no quorum, and no init/unseal commands. Custody is handled at boot by the static ceremony configuration and AWS KMS.
State Machine
Section titled “State Machine”The chamber has exactly two states:
direction: right
unsealed: Unsealed { shape: rectangle style.fill: "#d4edda"}sealed: Sealed { shape: rectangle style.fill: "#f8d7da"}
unsealed -> sealed: Break-glass seal\n(SEAL_OVERRIDE latch) {style.stroke: "#dc3545"}Unsealed— signer is operational; master key is resident in memory.Sealed— break-glass latch (SEAL_OVERRIDE) is present; terminal until restart.
There is no Sealed → Unsealed transition. Recovery from Sealed requires an IAM DeleteItem on the SEAL_OVERRIDE row followed by a restart of every replica.
Boot Sequence
Section titled “Boot Sequence”On every boot the background watcher inspects DynamoDB. A fresh chamber auto-inits directly to Unsealed:
Fresh table (no MASTER_KEY row):
The server auto-initializes:
- Generates a new master key.
- Shamir-splits it M-of-N across the custody KMS keys in the ceremony configuration.
- KMS-wraps each share; runs a recovery self-test (attested KMS decrypt + Shamir-combine + commitment check) before any durable write.
- Writes the
MASTER_KEYrow to DynamoDB. - Creates the root token, age-encrypts it to the
ceremony.root_token_recipients, and stores it in theROOT_TOKEN_BOOTSTRAProw. - Transitions to
Unsealed.
Existing MASTER_KEY row:
The server auto-unseals:
- Attested KMS decrypt of each share.
- Shamir-combines the threshold set; verifies the master-key commitment and share-binding HMAC.
- Transitions to
Unsealed.
SEAL_OVERRIDE latch present at boot:
Transitions directly to Sealed (terminal).
Custody Parameters Are Static
Section titled “Custody Parameters Are Static”The KMS key set, Shamir threshold, and root-token recipients come from the compiled-in, PCR0-measured CEREMONY static on Nitro builds, or the ceremony: config block on non-Nitro builds. They are never supplied by an operator at runtime.
On a non-Nitro build, the ceremony: block looks like:
ceremony: generation: 1 kms_threshold: 2 # Shamir M (must decrypt M of N shares) kms_keys: # N custody keys; each entry is one logical (multi-Region) key - arns: ["arn:aws:kms:us-east-1:123456789012:key/aaa", "arn:aws:kms:us-west-2:123456789012:key/aaa-replica"] - arns: ["arn:aws:kms:us-east-1:123456789012:key/bbb"] - arns: ["arn:aws:kms:us-east-1:123456789012:key/ccc"] retired_kms_keys: [] # old keys kept decryptable across a rotation; empty in steady state root_token_recipients: # age X25519 recipients — the root-token bootstrap is encrypted to any-of these - "age1qz…"A Nitro build rejects this block; the compiled PCR0-measured static is authoritative.
Rotation
Section titled “Rotation”Rotation is declarative. To rotate custody keys or recipients:
- Update the
ceremony:block (or redeploy a new Nitro image with the updated compiled static): add newkms_keys, adjustkms_threshold, move old keys toretired_kms_keys, bumpgeneration. - Redeploy.
After unseal, the background watcher reconciles the MASTER_KEY row — generation-gated, single DynamoDB transaction, proves the new keys decrypt before retiring the old. There are no rotate verbs or CLI rotation commands.
Break-Glass Seal
Section titled “Break-Glass Seal”containment-chamber operator seal (scope chamber_seal) is a fleet-wide break-glass operation:
- Writes the
SEAL_OVERRIDElatch to DynamoDB (HMAC’d by a master-key subkey). - Zeroizes the local master key and clears the auth plane.
- Every replica’s background watcher observes the latch and seals itself. Boot also honours the latch.
Recovery: IAM DeleteItem on the SEAL_OVERRIDE row (PK SEAL_OVERRIDE), then restart every replica. There is no unseal endpoint and no auto-resume.
For operational runbooks, see Seal & Unseal Operations.
Attestation
Section titled “Attestation”containment-chamber operator attestation (GET /api/v1/chamber/attestation, scope chamber_status) returns the NSM attestation document on Nitro builds. The custody parameters are PCR0-measured, so the attestation proves what ceremony static the enclave is running.
Policy Evaluation Rules
Section titled “Policy Evaluation Rules”Policies use deny-overrides-allow semantics:
- Deny overrides allow — any matching deny rule rejects the request, even if another rule or policy would allow it.
- Default deny — if no rule matches, the request is rejected.
- Scope, key, operation filters — rules can narrow by any combination.
Example: a policy with two rules
{ "name": "example", "rules": [ { "effect": "deny", "operations": ["VOLUNTARY_EXIT"], "keys": ["0xabc..."] }, { "effect": "allow", "scopes": ["sign", "public_keys"] } ]}A VOLUNTARY_EXIT request for 0xabc... is denied because the deny rule matches first. An ATTESTATION request for 0xabc... is allowed because the deny rule does not match and the allow rule does.
Default Behavior
Section titled “Default Behavior”Anonymous requests are denied unless static_auth.anonymous references named policies that allow them. This keeps the evaluator deny-by-default while still letting stateless deployments grant token-less access from the config file.
| Mode | static_auth.anonymous |
Anonymous request |
|---|---|---|
| Stateless | Not configured | Rejected with 401 |
| Stateless | Configured | Checked against the referenced named policies |
| State-backed | Not configured | Rejected with 401 |
| State-backed | static_auth set |
Invalid config; startup rejects it |
State-backed deployments use runtime policies and tokens from the Auth API. Stateless deployments cannot persist those, so they declare auth in config via static_auth — named policies, an optional anonymous binding, and static tokens.
Next Steps
Section titled “Next Steps”- Auth Policies & Tokens — full operating guide with JSON examples
- API Reference — interactive OpenAPI spec with request/response schemas
- Production Hardening — token security, CIDR binding, and audit logging

