JWKS Rotation Plan, Shared Sandbox Issuer

Quarterly rotation of the DOCS_ATTESTATION_ED25519_SEED, JWKS publishing, grace window for in-flight attestations, provii-mobile cache invalidation, and compromise response.

Public

JWKS Rotation Plan, Shared Sandbox Issuer

Implements: Cryptography Policy §4 (Key Management) for the shared sandbox issuer identity Scope: DOCS_ATTESTATION_ED25519_SEED (provii-issuer sandbox env) and the /.well-known/jwks.json endpoint published by the docs sandbox issuer Cross-Reference: UC-186, Docs Sandbox DPIA, Key Rotation Procedure (general) Owner: Cryptography Specialist Last Reviewed: 13 April 2026 Next Review: 13 July 2026


Why this exists

The docs sandbox issues short-lived Ed25519 attestations signed by a single shared seed, DOCS_ATTESTATION_ED25519_SEED. Every sandbox developer collectively relies on that one seed, which makes it an attractive target and makes compromise a multi-tenant event. UC-186 establishes the three-layer isolation chain (compile-time feature flag, CI bundle-grep, runtime prefix rejection) that prevents a sandbox attestation from being honoured in production. This document defines the planned rotation cadence and the associated response if the seed is compromised.

The general Key Rotation Procedure covers production signing keys, customer API keys, and Cloudflare tokens. The shared sandbox issuer is called out separately here because:

  • It rotates on a shorter cadence (quarterly, not annual)
  • Every rotation invalidates all in-flight sandbox attestations; the general procedure assumes production long-lived credentials
  • Rotation rehearsal includes a docs-sbx-* KV-sweep that does not apply to production keys
  • Wallet-mobile’s 24-hour JWKS cache creates a stale-attestation window that production flows do not face

Rotation cadence

Quarterly, aligned with the ISMS management review cycle. Scheduled dates and target windows:

QuarterRotation windowReview meeting
Q11-7 JanuaryManagement Review Q1
Q21-7 AprilManagement Review Q2
Q31-7 JulyManagement Review Q3
Q41-7 OctoberManagement Review Q4

The rotation is performed inside the nominated week so that any follow-up work falls inside the same review cycle. Missing a window is treated as a non-conformity and logged against the management review record.

Target state per rotation

At the end of each rotation window the deployment runs the new signing seed, JWKS publishes both old and new public keys, and every downstream cache is either reading the new key directly or trusting both. The steady-state window during which both keys appear in JWKS is the “grace window” in this document. It is 24 hours long, aligned with the provii-mobile JWKS cache TTL.

Rotation procedure

T-7 days, announcement

  1. Post the upcoming rotation to #docs-sandbox and the developer newsletter draft queue.
  2. Confirm that no known sandbox integrator has an outage or release window falling inside the rotation week. If one does, move the rotation to the next available week inside the quarter.
  3. File a change-management record entry per Change Management Procedure.

T-1 day, pre-flight

  1. Verify the JWKS endpoint serves only the current active key.
  2. Verify the production provii-verifier still refuses any docs-sbx-* prefix (sanity check on UC-186 layer 3) by running the ingress prefix-rejection test suite.

T-0, rotation

  1. Generate a new Ed25519 seed with the provii-issuer keygen tool. Do not reuse any previous seed value.
  2. Commit the new public key to the JWKS publisher configuration with a fresh kid value, leaving the previous kid entry in place. The JWKS document now has two entries.
  3. Deploy the JWKS publisher. Confirm the endpoint returns both keys.
  4. Update the DOCS_ATTESTATION_ED25519_SEED secret in the provii-issuer sandbox worker to the new seed:
    wrangler secret put DOCS_ATTESTATION_ED25519_SEED --env sandbox
  5. Deploy the provii-issuer sandbox worker. New attestations now carry the new kid.
  6. Observe the provii-issuer and provii-verifier logs for one hour. A spike in verification failures against the new kid means a downstream cache did not pick up the new JWKS. Investigate before proceeding.

T+24h, old-key retirement

  1. Remove the previous kid from the JWKS document.
  2. Deploy the JWKS publisher.
  3. Confirm that any attestation still presenting the previous kid now fails verification. This is expected behaviour for attestations older than 24 hours; provii-mobile should not be holding any, and the docs gateway mints attestations on the fly.

T+7 days, rehearsal closeout

  1. Run the documented docs-sbx-* and mwallet-sbx-* KV-sweep procedure from the Incident Response Playbook against the sandbox KV namespaces. This is a rehearsal, not a response; it proves the sweep procedure still executes cleanly.
  2. File the rehearsal outcome in the ISMS evidence register entry UC-186.2.
  3. Close the change-management record.

Grace window for in-flight attestations

Attestations minted before T-0 remain valid for up to 24 hours under the previous kid. This is the only window during which both keys appear in JWKS. The 24-hour choice lines up with:

  1. Wallet-mobile JWKS cache TTL (next section)
  2. Maximum documented sandbox session lifetime (4-hour hard cap in DOCS_SESSIONS KV) plus a five-factor margin for clock skew, mobile app suspension, and retry latency
  3. The documented Docs Sandbox DPIA attestation lifetime of “minutes to hours”

An attestation older than 24 hours at the moment of verification will fail JWKS validation even if its payload is structurally valid. This is by design: stale attestations under a retired key expose unbounded latent authorisation to whatever compromise window existed under that key.

Wallet-mobile JWKS cache TTL

Wallet-mobile fetches the docs-sandbox JWKS on first use and caches it for 24 hours. The cache TTL is hard-coded inside the JwksClient implementation and is not a user-facing setting. The cache invalidates on:

  • 24-hour TTL expiry (soft invalidation; refresh on next request)
  • Explicit user “Refresh sandbox keys” action inside the developer-mode settings screen
  • App upgrade to a version with a different pinned JWKS URL
  • App data wipe

No push-invalidation channel exists. The 24-hour cache combined with the 24-hour JWKS grace window gives a worst-case stale-key exposure of 24 hours post-T-0. During that window, a provii-mobile device still presents the old kid and the provii-issuer still honours it. At T+24h the old kid is retired from JWKS and the next verification by a stale client fails, forcing a JWKS refresh.

Implication: provii-mobile integrators MUST NOT rely on sandbox attestations surviving longer than 24 hours of clock wall time. The production flow is not affected because production never honours a docs-sbx-* prefix in the first place.

Compromise response

If DOCS_ATTESTATION_ED25519_SEED is known or suspected compromised, the quarterly cadence is abandoned. The response runs on the incident response playbook’s P1 clock.

Immediate actions (within 1 hour of confirmation)

  1. Generate a fresh Ed25519 seed. Do NOT wait for the next scheduled rotation window.
  2. Rotate the secret: wrangler secret put DOCS_ATTESTATION_ED25519_SEED --env sandbox.
  3. Deploy the provii-issuer sandbox worker.
  4. Remove the compromised kid from JWKS immediately, without the 24-hour grace window. All in-flight attestations minted by the compromised seed become invalid within seconds.
  5. Deploy the JWKS publisher.

Containment (within 4 hours)

  1. Run the full docs-sbx-* and mwallet-sbx-* KV-sweep procedure against the sandbox KV namespaces. This removes every credential that the compromised seed could have vouched for. The sweep is defined in the Incident Response Playbook.
  2. Invalidate every active __Host-docs_session cookie by rotating the DOCS_SESSION_HMAC_KEY secret (this is a separate secret but is treated as a paired rotation during a seed-compromise incident).
  3. Audit provii-issuer logs for the 90 days preceding the confirmation timestamp for anomalous attestation issuance patterns.

Notification (within 24 hours)

  1. Email every known sandbox developer via the list held by the developer relations team. The message states that the shared sandbox issuer seed was rotated, all in-flight sandbox attestations are invalid, and no further action is required by the developer beyond re-running their integration flow to mint a new attestation.
  2. File a P1 incident record per the Incident Response Playbook, cross-referenced against UC-186 and the Docs Sandbox DPIA.
  3. Update the Transparency Report at the next scheduled publication cycle; a sandbox seed compromise does not trigger an interim publication because no production data is exposed.

Post-incident (within 7 days)

  1. Root cause analysis per the standard P1 process.
  2. Update this document, the Incident Response Playbook, and the relevant evidence register entries with the lessons learned.
  3. Submit to the next management review as a formal agenda item.

Why the production flow is unaffected

Even during a worst-case sandbox seed compromise, the system is designed so that a production relying party should not be able to honour a sandbox attestation. UC-186 layer 3 (runtime prefix rejection) refuses any docs-sbx-* prefix at the production ingress, across body, path, query, and Authorisation header. Layers 1 and 2 (compile-time feature flag and CI bundle-grep) prevent a production build from carrying sandbox-only validation code at all. A sandbox seed compromise is therefore contained to the sandbox blast radius: developer accounts, sandbox dashboards, and sandbox-issued credentials.

This containment is the whole point of the shared seed being labelled “shared sandbox issuer” rather than “shared issuer”. It is a deliberate design trade-off: accept a multi-tenant blast radius inside the sandbox in exchange for operational simplicity, paired with a hard cryptographic boundary at the production ingress that prevents the blast reaching a real relying party.

Evidence

RecordLocation
Quarterly rotation logISMS evidence register entry UC-186.2, filed after each rotation
JWKS document snapshots/operational-evidence/jwks-snapshots/ (taken at T-0 and T+24h for every rotation)
KV-sweep rehearsal outcomeISMS evidence register entry UC-186.2, appended to the rotation log
Compromise incident recordFiled per Incident Response Playbook if triggered; cross-referenced to UC-186.2
Developer notification copy/operational-evidence/dev-comms/ (if a compromise notification is sent)