Skip to content

Auth Policies

Auth policies let you run a single Containment Chamber instance for multiple clients while controlling exactly which routes and operations each client can access. Each client authenticates with a unique token, and policies define what that token is allowed to do.

By default, all routes are open without authentication. When auth_policies is configured, requests must include HTTP Basic auth. Requests without a valid token are rejected (unless an unauthenticated_policy is defined).

Tokens must follow these rules:

  • Minimum 16 characters
  • Alphanumeric characters and dashes only (a-z, A-Z, 0-9, -)
  • Use the env: prefix to load a token from an environment variable at startup:
    token: "env:VC1_TOKEN" # resolved from $VC1_TOKEN
    If the referenced environment variable is not set, startup fails with a clear error.

Clients embed the token in the signer URL using HTTP Basic auth:

http://policy-name:your-token-here@signer:9000

The username is the policy name (the key under auth_policies in config). The password is the token. The signer uses the username to look up the correct policy, then validates the token against it.

For single-policy deployments, validator clients that don’t support custom usernames can use any non-empty string — but it must match a policy name in the config. A common pattern is to name your policy x for simplicity:

auth_policies:
x:
token: "env:VC_TOKEN"
allowed_scopes: [sign, public_keys]
http://x:my-token@signer:9000

This works with all major validator clients: Lighthouse, Teku, Prysm, Lodestar, and Nimbus.

Scopes control which routes a policy can access. Omit allowed_scopes and denied_scopes to allow all routes.

ScopeGrants access toDescription
signPOST /api/v1/eth2/sign/{pubkey}BLS signing operations
public_keysGET /api/v1/eth2/publicKeysList loaded validator public keys
list_keysGET /eth/v1/keystoresList keystores via Key Manager API
import_keystoresPOST /eth/v1/keystoresImport keystores via Key Manager API
delete_keysDELETE /eth/v1/keystoresRemove keystores via Key Manager API
chamber_keys_generatePOST /api/v1/chamber/keys/generateGenerate new validator keys in DynamoDB
chamber_keys_writePOST /api/v1/chamber/keysImport keys into DynamoDB or memory
chamber_keys_listGET /api/v1/chamber/keysList all loaded keys with source and status
chamber_keys_patchPATCH /api/v1/chamber/keysChange key status (active/inactive)
chamber_keys_deleteDELETE /api/v1/chamber/keysDelete keys from DynamoDB or memory
chamber_sealPOST /api/v1/chamber/sealEmergency seal — zeroize master key, block all signing
chamber_rotatePOST /api/v1/chamber/rotate/kmsRotate KMS keys (re-split master key with new ARNs)
POST /api/v1/chamber/rotate/unsealStart operator rotation (new passphrases)
DELETE /api/v1/chamber/rotate/unsealCancel in-progress operator rotation
POST /api/v1/chamber/rotate/modeSwitch between kms_only and unseal modes
DELETE /api/v1/chamber/unsealDiscard accumulated unseal shares
chamber_statusGET /api/v1/chamber/statusView current seal state, key counts, enclave info
GET /api/v1/chamber/attestationRetrieve Nitro Enclave attestation document

The following endpoints use dedicated tokens instead of auth policy scopes:

EndpointAuthToken source
POST /api/v1/chamber/initSetup token (one-time)Printed to stdout on first boot
DELETE /api/v1/chamber/initSetup tokenSame as above
POST /api/v1/chamber/unseal/registerRegistration token (per-operator)Returned in the init response
POST /api/v1/chamber/unsealPassphrase (no token)Operator’s passphrase is the authentication

The following operation names can be used in allowed_signing_operations and denied_signing_operations:

OperationDescription
AGGREGATION_SLOTAggregation slot selection
AGGREGATE_AND_PROOFAggregate attestation proof
ATTESTATIONAttestation signing
BLOCK_V2Block proposal
RANDAO_REVEALRANDAO reveal
SYNC_COMMITTEE_CONTRIBUTION_AND_PROOFSync committee contribution and proof
SYNC_COMMITTEE_MESSAGESync committee message
SYNC_COMMITTEE_SELECTION_PROOFSync committee selection proof
VALIDATOR_REGISTRATIONValidator registration
VOLUNTARY_EXITVoluntary exit

Each policy is a named entry under auth_policies with a token and optional restrictions.

By default, Containment Chamber runs without any auth policies. When no auth_policies block is present, all routes are open without tokens.

This is the simplest setup — no auth configuration needed:

server:
listen_address: "0.0.0.0"
listen_port: 9000
key_sources:
filesystem:
paths:
- ./keystores
antislashing:
backend: sqlite
path: ./slashing-protection.sqlite
# No auth_policies = all requests allowed (Web3Signer compatible)
Diagram

Default behavior: When no auth_policies and no unauthenticated_policy are configured, auth is disabled — all requests are allowed regardless of whether a token is sent.

Named policiesUnauthenticated policyNo tokenValid credentialsInvalid credentials
❌ None❌ None✅ Allow (auth off)✅ Allow (auth off)✅ Allow (auth off)
❌ None✅ Set✅ Check unauthenticated✅ Check unauthenticated✅ Check unauthenticated
✅ Set❌ None❌ 401✅ Check policy❌ 403
✅ Set✅ Set✅ Check unauthenticated✅ Check policy❌ 403

When no named policies are configured, tokens are ignored — there’s nothing to validate them against. All requests are evaluated against the unauthenticated_policy (if set) or allowed unconditionally (if not set).

When named policies ARE configured, sending an unrecognized or invalid token always results in 403 — tokens are never silently downgraded to unauthenticated access.

Once a policy is matched, the request is checked against:

  • allowed_scopes or denied_scopes — which API routes are accessible
  • allowed_keys — which validator public keys can be signed (signing scope only)
  • allowed_signing_operations or denied_signing_operations — which signing operations are permitted

The two key listing endpoints have different visibility rules:

GET /api/v1/eth2/publicKeys (Web3Signer API)

Section titled “GET /api/v1/eth2/publicKeys (Web3Signer API)”

Returns only keys the token can sign with. This endpoint is designed for validator clients — showing keys the client can’t sign would cause it to accept validator duties and then fail, resulting in missed attestations and penalties.

The response is filtered by:

  1. The token must have the public_keys scope (otherwise 403)
  2. The token must also have the sign scope — keys you can’t sign with are excluded
  3. If allowed_keys is set, only those keys are returned
Token scopesallowed_keysResult
sign + public_keysNot setAll loaded keys
sign + public_keys["0xabc...", "0xdef..."]Only those 2 keys
public_keys onlyAnyEmpty list (can’t sign = can’t see)
sign onlyAny403 (no public_keys scope)

Returns all loaded keys regardless of allowed_keys. This endpoint is for operators managing the signer — they need full visibility to import, delete, and monitor key status. Requires the chamber_keys_list scope.

When the signer is sealed (master key not in memory), endpoints respond differently:

EndpointBehavior when sealed
GET /upcheck200 with seal status text (e.g., "sealed", "kms_unsealed"). Always returns 200 — load balancers see the process as healthy regardless of seal state. Only returns 503 if the anti-slashing backend is down.
GET /api/v1/eth2/publicKeys200 with empty list []. No keys are loaded when sealed, so there’s nothing to return. No error.
POST /api/v1/eth2/sign/{id}503 "signer is sealed". The seal check runs before auth — even a valid token gets 503.
GET /api/v1/chamber/keys503 "signer is sealed". Same as signing — requires an unsealed signer.
Chamber seal endpointsWork in all states — they manage the seal lifecycle.

The signer uses two different authentication mechanisms:

Used by: signing endpoints, key manager, chamber keys API.

Format: Authorization: Basic base64(policy-name:token)

Tokens and scopes are configured in auth_policies. The policy name is the username in the Basic auth header.

Used by: chamber seal/unseal ceremony endpoints (/api/v1/chamber/init, /api/v1/chamber/unseal/register).

Format: Authorization: Bearer token

These are one-time tokens generated at runtime — the setup token is logged on first boot, registration tokens are returned in the init response. They are not configured in auth_policies.

The operator CLI subcommands use the correct format automatically:

  • --auth-token → sent as Bearer for seal operations, not applicable for auth policy endpoints
  • --setup-token → sent as Bearer
  • --registration-token → sent as Bearer
  • Passphrase-based auth (unseal) → no token needed