Keyring

When Keyring is down

The three onUnavailable modes, and exactly what the default admits in each state of the cache.

The SDK verifies from a policy set it keeps in memory. When the control plane is unreachable, that set goes stale, and the question is what a request does then. onUnavailable answers it, globally or per route.

ModeA key in the cacheA key the cache has never seen
stale-then-open (default)Served however stale, degraded set past the budgetAllowed, degraded: true, verified: false, unless the cache can prove it does not exist
stale-then-closedServed however stale401
closed503 past maxStalenessMs503

maxStalenessMs is 60 seconds by default.

What the default admits, exactly

The cache is complete for the project, not a partial memo, so a key absent from a fresh complete snapshot is a key that does not exist, and that miss is a 401 in every mode. The fail-open branch is reached only when the store cannot prove the miss, and what it admits then is a function of the store's state:

Store stateWhat a miss admits
Complete and freshNothing. 401.
Complete, stale within maxStalenessMsStill nothing. A revocation issued inside the window is the only thing not yet honoured, on a hit.
Complete, stale past maxStalenessMsAny well-formed key. Measured on a rehearsal: a never-minted key answered 401 for 54.6 s, then 200.
Never loaded (cold start, no snapshot on disk)Any well-formed key, for as long as the control plane stays unreachable.
Evicted under maxCachedKeysAny well-formed key not resident, for as long as the store stays incomplete.
Tenant-narrowed (tenantIds)Any well-formed key of a tenant outside the filter, permanently, by construction.

The key format's checksum is CRC-32, a public algorithm, so producing a well-formed key costs an attacker nothing. Read the last four rows as the radius: in those states, on the default mode, an unknown key is admitted with tenant: null, no scopes and no scope check, until the next successful poll. The window is not a fixed length: it stays open for as long as the outage lasts, and closes on the first successful poll after recovery.

What to set

  • Money routes: closed. A route declaring scopes is deliberately not scope-checked on a fail-open decision, so scopes alone protects nothing there.
  • Internal routes that must never admit a stranger: stale-then-closed. Known keys keep working through an outage; unknown ones do not.
  • Serverless, or any fresh-process-per-request platform: stale-then-closed on the client, because every invocation starts in the "never loaded" row.
  • A long-lived server with mostly-read routes: the default, which is what makes an outage of the control plane invisible to your customers.

req.keyring.degraded is true on every decision that came from stale or incomplete policy, and verified is false when there was no policy record at all, so a handler can be stricter than its route.

The counters and the idempotency store

These are separate questions with separate defaults: rate limits fail open, idempotency fails closed. Rate limits and Idempotency explain the asymmetry.

On this page