Skip to content

Sandbox realm

A live realm can have one paired sandbox realm. The sandbox is a realm in its own right, with its own host, issuer, keys and passkey RP ID. Its environment ceiling is staging, so the developer portal’s flow test console works there while the live realm stays at production. The design is ADR-019 Decision 11 and ADR-020 §4.

What the sandbox shares, and what it does not

Section titled “What the sandbox shares, and what it does not”

The sandbox runs in the same deployment as its live realm. It shares what the deployment holds:

Shared with the live realmWhy
The databaseEvery realm’s rows live in one database; each row names its realm, and every read is scoped to one.
The KEKThe sandbox’s keys are wrapped with the deployment’s key-encryption key, as every realm’s are (Keys).
The signer roleThe same signer process holds both realms’ keys and signs for each with that realm’s own keys.
The trust domainWhoever operates the deployment operates the sandbox. A sandbox is for testing configuration, not for isolating an attacker.
The deployment’s environment switchesProcess-wide variables, such as the listeners and the KEK provider, apply to every realm.

It shares nothing that belongs to a realm:

Its ownWhat that means
HostThe sandbox has a primary host of its own. Hosts are unique across all realms, the sandbox twins included, so the sandbox’s host is never the live realm’s. You provision its DNS and TLS like any realm host.
IssuerIts issuer is on its own host and is permanent, as every realm’s is. Discovery and the JWKS at the sandbox host describe the sandbox.
KeysIt gets its own token signing keys (RS256 as well, as a new realm does), cookie key, event signing key and data-at-rest key when it is created. No key of the live realm is copied or shared.
Passkey RP IDIts RP ID is its own host. A passkey enrolled for the live realm does not work in the sandbox.
Users and sessionsIt starts with no users. A user of the live realm cannot sign in to the sandbox: realm isolation keeps every account in the realm that created it. Create test users in the sandbox.
Clients, consents, grantsIt starts with none. Register the clients you test with in the sandbox.
PoliciesIt is seeded from the environment’s defaults, like any new realm, and copies nothing from the live realm. Two values differ from the seeds: the environment ceiling is staging, and the mail transport is mock, which only logs.
SecretsNone is copied. Live secrets stay on the live realm.
Upstream providersConfigure them with test accounts of the provider. That is configuration, not code.

A sandbox’s ceiling (posture.maxEnvironmentLaxity, see Environment-aware authorization) is staging when it is created. A client is always held to the stricter of its own environment and the ceiling, so:

  • a sandbox client set to development runs with the staging profile: PKCE required, https redirect URIs only, no static API keys, refresh-token rotation, lenient rate limits;
  • a sandbox client set to staging or production keeps its own profile.

The ceiling can be tightened to production, and set back to staging. It can never be development: the database refuses the value whoever writes it, and the server’s realm policy update answers a request for it with 422 and the problem type sandbox_ceiling. The admin API operation that updates a realm’s policies, which calls that update, is #517.

A token the sandbox issues names the sandbox’s issuer and is signed with the sandbox’s keys. A resource server of the live realm checks the issuer, as every resource server must, so it refuses a sandbox token. No claim marks a token as a sandbox token: a same-issuer sandbox flag was rejected, because every resource server would have to check it, and one that forgot would accept sandbox tokens.

Migration 0056 adds realms.sandbox_of_realm_id, which names a sandbox’s live realm. The rules hold for every writer, a hand-written SQL statement included:

RuleHeld by
A live realm has at most one sandbox.the unique index idx_realms_sandbox_of_realm_id
A realm is not its own sandbox.the CHECK realms_sandbox_not_self
A sandbox has no sandbox.the trigger realms_sandbox_pairing
A sandbox’s live realm exists before the sandbox, not later in the same statement.the trigger, and the foreign key
The operator realm is no sandbox and has none.the CHECK realms_sandbox_not_operator and the trigger
The pairing is set when the sandbox is created and never changes.the trigger
A sandbox’s ceiling is never development.the CHECK realms_sandbox_ceiling
A live realm cannot be deleted while its sandbox exists. Delete the sandbox first.the foreign key, ON DELETE RESTRICT

Because the operator realm has no sandbox, a deployment in single-realm simple mode, where the default realm is also the operator realm, has none either. A sandbox needs a live realm of its own, next to the operator realm (ADR-019 Decision 1).

One check reads rows other than the one written: moving the operator flag onto a realm looks for that realm’s sandbox. In a REPEATABLE READ transaction it looks in the transaction’s snapshot, which a sandbox committed in the meantime is not in. Move the operator flag by hand only under READ COMMITTED, as the server does, under its realm provisioning lock.

The server creates a sandbox in one transaction: the realm and its primary host from the issuer you choose, the pairing and the staging ceiling, its policies with the test mail transport, and its keys. Either all of it is stored, or none of it.

The admin API operation that creates a sandbox and reads the pairing comes with the realm lifecycle operations (#694). Creating a realm is a security operation. Until that operation ships, a sandbox is created only from the server’s code (createSandboxRealm). The admin API’s realm representation (#517) is to show the pairing as two read-only fields: sandboxOf, the live realm’s id on a sandbox, and sandbox, the sandbox’s id on a live realm; each is null otherwise. The server already reads both (sandboxPairingOf).

Moving settings from the sandbox to the live realm goes through the realm file’s diff, where fields that describe the sandbox’s environment (its ceiling, http redirect URIs, test upstreams, test webhook receivers) move only when selected one by one. That comes with the realm file import.