Incidents
This page helps you respond when something goes wrong in staging or production: a key or secret leaks, an administrator account is in the wrong hands, auth goes down, or mail stops going out. Each runbook says how you notice, what to do in order, and how to check that it’s over.
Run every auth-service command below in the affected environment, with
piquelctl app exec -t sourceorigin auth --env production -- auth-service … (see
running auth-service commands).
-t gives the command a terminal: most commands below ask you to confirm what
they will change, and refuse without one. The
command line needs only the database, so it works while auth’s pages and the
admin dashboard don’t.
Warning: after rotating a secret, never restore a deployment made before the rotation: a restore, like an automatic rollback, brings back the secret versions it ran with. Watch the deployment that rotates it, and fix forward if it fails.
A compromised signing key
How you notice
A signing key’s private key opens only with both the database and the
key-encryption key (AUTH_KEY_ENCRYPTION_KEY). Treat every stored key as
compromised when both may have leaked: a copy of the database and the
key-encryption-key secret together, or a host that runs auth.
One key has a copy outside the database: the signing-key secret holds the
private key of primary, the key it was imported as, in plaintext PEM. If that
secret leaks, primary is compromised on its own: revoke it if keys list
still lists it. Revoking deletes its row, not the secret, which also stays the
accepted token digest key, so follow a leaked token digest key
too.
What to do
-
If whoever holds them may still read the database or the host, close that access first. The key-encryption key can’t be changed: it seals every key auth generates, including the one that replaces the revoked key. So someone who holds it and reads the database again can open the new key too.
-
Find the key to revoke: the one in state
active, orprimarywhen only thesigning-keysecret leaked:auth-service keys list -
Revoke it:
auth-service keys revoke <id>On the dashboard, select Revoke next to it on the Signing keys page. When the revoked key was
active, thenextandretiringkeys go with it, since their private keys were stored beside it, and a new key signs at once. Revoking any other key removes only that key. A retired key is never in the key set auth publishes, so after revoking a retiredprimary, skip steps 4 and 5. -
Wait 15 minutes after the revocation. Each replica serves its own copy of the key set and reloads it each minute, and one that can’t reload stops serving within 15 minutes, so only then has every replica dropped the revoked keys.
https://auth.sourceorigin.dev/.well-known/jwks.jsonno longer listing them shows only that the replica that answered has. -
Then restart the first-party resource servers, which drops the keys they cached. Restarting sooner could cache the revoked keys again from a replica that still serves them. The SDKs keep a known key until their cache expires, so other verifiers may accept the revoked keys for up to an hour from this point.
Every access and ID token the revoked keys signed stops working at auth,
including as an id_token_hint. Refresh tokens don’t depend on signing keys, so
apps refresh and receive tokens signed by the new key.
Check that it’s over
auth-service keys listlists none of the revoked keys, and a newactivekey if you revoked the active one.https://auth.sourceorigin.dev/.well-known/jwks.jsonno longer lists their IDs.- An hour after step 5, verifiers that respect
Cache-Controlhave dropped them. Until then, whoever holds a revoked key can mint tokens that third parties accept, with anyexp, so token lifetimes don’t shorten this window. Verifiers that ignoreCache-Controlmay accept them longer.
Signing keys explains revocation and reloads.
A leaked client secret
How you notice
A secret starting with socs_ appears where it shouldn’t: in a repository, a
log, a ticket or a message. Secret scanners recognize the prefix. The app’s
owner may report it too.
With the secret, someone can authenticate as the app at the token endpoint, with a code or a refresh token the app was given.
What to do
-
Find the client the secret belongs to, with the app’s owner, or in
auth-service client list. -
Revoke every earlier secret at once, and get a new one:
auth-service client rotate-secret <client-id> --grace 0On the dashboard, open the client, select Rotate the secret, choose Stop them now for Earlier secrets keep working, and select Rotate secret. For an account’s client, its owner can do the same on its Secrets tab in the developer console.
The app can’t sign anyone in until it has the new secret.
-
Give the app the new secret, printed once, and deploy it. For a client a deployment registers with
client apply, regenerate the deployment’s secret instead: its nextclient applywould register the leaked value again. -
If more than the secret leaked, such as the app’s server with the refresh tokens it holds, also stop the client until the server is clean:
auth-service client disable <client-id>stops it from authorizing and refreshing tokens at once. Deleting the client revokes its refresh tokens for good (see OIDC clients).
Check that it’s over
auth-service client show <client-id>lists the new secret without an end, and every earlier one ending at the rotation.- The app signs users in again.
A leaked token digest key
How you notice
The token-digest-key secret, AUTH_TOKEN_DIGEST_KEY, appears outside the
environment: in a backup, a log, or on a compromised host.
Auth stores refresh tokens, authorization codes and pending logins as HMAC digests under this key, and email verification, recovery and unsubscribe links derive from it. With the key and the ID of an address, a recovery or an account, someone can rebuild the link auth sent for it. An unsubscribe link only turns that account’s announcements’ email off, but it never expires.
What to do
The usual change takes two deployments and keeps the old key accepted for about 30 days (see changing the token digest key). For a leaked key, replace it at once instead:
-
Generate a new key:
piquelctl env secret sourceorigin --env production regenerate token-digest-keyIf the
signing-keysecret leaked with it, regenerate it too: it is the accepted key,AUTH_TOKEN_DIGEST_KEY_ACCEPTED. It is also the RSA key imported asprimary, so ifauth-service keys liststill listsprimary, in any state, revoke it as a compromised signing key describes. -
Deploy production’s own release again, as pick up a changed secret describes. For staging, run
piquelctl --timeout 30m env deploy sourceorigin staging.Every refresh token, code and login in progress under the old key ends, so apps sign their users in again. During the rollout, each release refuses what the other issued.
-
Deal with the recoveries started in the last day, which
auth-service audit list --kind recovery_startedlists. For each account, runauth-service user undo-recovery <user> --reason "…"without--yes: it prints the account’s recent recoveries with where each stands, then asks to confirm what it will do.- If the recovery says link not used, and the prompt says it will cancel
its unused link, confirm. Start a new recovery with
auth-service user recoveronce the new key is deployed: the new link derives from it. - If the link was used since the leak, the passkey it enrolled may be the attacker’s. Don’t confirm: undoing removes that passkey and every session, which locks the account holder out if the passkey was theirs. Ask the holder, through a channel support already trusts, whether they used it, and undo it only if they didn’t.
- If the recovery says link not used, and the prompt says it will cancel
its unused link, confirm. Start a new recovery with
Links already sent are checked against the digest stored when they were made, so they keep working until they expire, within 24 hours. That includes the links that confirm an email address: for that day, someone with the old key could confirm an address an account added but hasn’t confirmed yet. Unsubscribe links are checked by deriving them again, so those built from the old key stop working once the new key replaces it.
Check that it’s over
auth-service keys digest-usagecounts new digests undercurrent, and the old ones asunknownuntil cleanup deletes them.- 24 hours have passed since the deployment, so every link derived from the old key has expired.
A stolen administrator session or a compromised administrator account
How you notice
- The Admin activity dashboard shows changes nobody you know made: rights granted, accounts disabled or viewed, refused sign-ins.
- An owner asks about a change “By a SourceOrigin administrator” in their security history that they never asked for.
- An administrator finds an entry in their own security history that isn’t
theirs, and reports it, which signs out their other sessions.
auth-service audit list --kind compromise_reportedlists these reports. auth-service admin listlists an administrator nobody granted.- An alert says The audit chain is broken or The audit chain’s checkpoints stopped: someone changed the history (see respond to an alert).
What to do
Act at the terminal: the dashboard never changes an administrator’s account, while the command line may.
-
Revoke the account’s rights, which ends its dashboard access on its next request:
auth-service admin revoke <handle>If it holds the last rights, first grant them to an account you trust with
auth-service admin grant <handle>. -
End its sessions, which ends its dashboard session with them:
auth-service session revoke-all <handle> -
If the account’s passkey may be in someone else’s hands, not just a session, stop it signing in while you find out:
auth-service user disable <handle> --reason "Compromised administrator account"Then return the account to its owner, following the evidence policy: enable it, since a recovery needs an active account, and recover it with
--revoke(see recover an account someone else held). -
Find what it did: Actions by administrator and Administrative events on the Admin activity dashboard, the dashboard’s Events page, and
auth-service audit list, such as--kind administrator_granted. -
Undo each change: revoke the rights it granted, enable the accounts it disabled, lift the suspensions it made, and undo the recoveries it started with
auth-service user undo-recovery. -
Verify that the history you relied on is intact, from a trusted build:
auth-service audit verify
A change made at the terminal records its operator’s $USER and host name. One
that nobody made points at a compromised host: treat the secrets on it as
leaked, starting with the runbooks above.
Check that it’s over
auth-service admin listlists only the administrators you expect.auth-service account <handle> sessions listlists no session you don’t recognize.- The Admin activity dashboard shows only work you know of.
auth-service audit verifyreports the chain intact.
Auth is down
How you notice
- auth.sourceorigin.dev answers errors, or nothing, and users report it.
https://auth.sourceorigin.dev/health/readydoesn’t answer204. It checks the database and that the replica’s signing keys were reloaded recently.piquelctl env show sourceorigin productionreports a service unhealthy.- The Auth overview dashboard’s Server errors rise. Grafana keeps working, since it never signs in through auth (see sign in to Grafana).
What to do
-
See what piqueld sees:
piquelctl env history sourceorigin production piquelctl env logs sourceorigin production --service auth --since-seconds 900 -
If a deployment caused it, return to the previous one. When its rollout failed, production already restored the last successful deployment on its own. Otherwise, restore it: see roll back production.
-
Read Grafana’s Auth overview: Server errors, Database connections and Logs.
-
If the replicas aren’t ready because of the database, read
piquelctl env logs sourceorigin production --service postgres. -
If they aren’t ready because of their signing keys, the logs say which key failed. A replica that can’t reload its keys fails readiness after 5 minutes, and answers
503where it signs or verifies after 15. A key that won’t open is removed withauth-service keys revoke <id>, which works even when the active key can’t be opened (see replace a key that doesn’t open).
Meanwhile, every administrative operation runs at the terminal: the command
line is the break-glass path when the dashboard or auth’s pages are
unreachable. piquelctl app exec needs a running auth container: if none
runs, restore a deployment first. Nothing auth runs on needs auth to sign in: piqueld, the servers, the
tailnet, PostgreSQL, DNS, the tunnel, Grafana and Resend (see
recovering auth).
Check that it’s over
piquelctl --timeout 10m env wait sourceorigin production --ready routesreturns: the deployment runs healthy, and every route answers. It waits for the deployment that was newest when it started, so if that one failed and production rolled back, checkpiquelctl env history sourceorigin productionandpiquelctl env url sourceorigin productioninstead.https://auth.sourceorigin.dev/health/readyanswers204.- Server errors on the Auth overview dashboard are back to their usual level.
Mail not going out
How you notice
- Users don’t receive verification, recovery or notice emails.
- On the Auth overview dashboard’s Deliveries row, the
emailchannel’s Pending deliveries grow, its Oldest pending delivery gets older, and Delivery attempts show retries or failures.
What to do
-
List what is waiting and what failed:
auth-service deliveries list --channel email --status pending auth-service deliveries list --channel email --status failedEach delivery’s last error is the SMTP reply code.
-
Fix the cause the code points at:
535,530or534: Resend rejected the key. Store a working one, as rotate a Resend key describes. Rejected logins retry for 24 hours, so mail queued meanwhile goes out once auth runs with the new key.- Another
5xx: Resend refused the message for good, and the delivery failed. Check the sending domain’s status and its DNS records in Resend’s dashboard. - A timeout, or a connection or TLS failure: auth can’t reach Resend. Check Resend’s status, and auth’s logs.
-
Once the cause is fixed, retry the failed deliveries still worth sending:
auth-service deliveries retry <id>A retry makes the delivery due now, and gives it another 24 hours. On the dashboard’s Deliveries page, select Retry.
Mail that arrives but lands in spam points at the DNS records, not the queue.
Check that it’s over
- The
emailchannel’s Pending deliveries are back to their usual level. - New deliveries in
auth-service deliveries list --channel emailaresucceeded, and Resend’s dashboard lists the messages as delivered.