# Credential rotation

Credential rotation is replacing a secret on a schedule while the systems that use it keep running. The hard part is never the new secret; it is every copy of the old one. Connect keeps no copy of its database password — the managed secret is read at boot — and rebuilds each mailbox's token bundle on every refresh rather than holding a snapshot of it.

- **Status:** Reference
- **Audience:** both, developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/credential-rotation/

## Rotation is a problem about copies

A rotation has three moments: the new secret exists, both secrets are accepted, and the old one stops working. A service that fetches the secret when it needs it crosses all three without noticing. A service that read the secret once and wrote it into a file crosses the first two without noticing and then fails at the third — usually not at the moment of rotation, but at the next restart, which may be days later and will look like whatever else changed in between.

That delay is what makes stale credentials expensive to diagnose. The failure is not attributed to the rotation because the rotation succeeded and nothing broke that day. It is attributed to the deploy that happened to trigger the restart.

> **Careful** A configuration file holding a password is not a cache of the secret. It is a second, unmanaged copy of it, and the rotation system has no idea it exists.

## Where Connect's database password lives

It is not in `.env`. The managed database service owns the master credential, rotates it, and writes the current value into a secrets store; `MAYA_MAIL_DB_SECRET_ARN` names that secret, and boot reads the password out of it. The application therefore has no long-lived copy to go stale, and a rotation needs no deploy and no coordination.

This shape was adopted because the other one failed exactly as described above: a stale copy left in the environment file made the *next restart* authenticate as nobody, which presented as a bad deploy rather than as a credential problem. Nothing in the release had touched authentication.

**What is stored** — An identifier pointing at the secret. Not the secret.
**When it is read** — At boot, before the connection pool is built.
**What a rotation requires of you** — Nothing. No restart is needed to pick up a rotated value; the next restart reads whatever is current.
**What breaks it** — Reintroducing a password into a file, or granting the application no permission to read the secret.

## Credentials you did not issue: OAuth tokens

A mailbox connected over Google OAuth or Microsoft Graph holds a refresh token and a short-lived access token. The access token rotates constantly and by design — every refresh produces a new one — so the code that stores it has to be able to write back.

In `TenantMailbox` the `config` bundle is a real setter rather than a read-only property, and the reason is precisely this: `oauth.store_tokens` rebuilds the dictionary and assigns it back, so a read-only property would silently drop every refreshed access token. The mailbox would keep working until the token in hand expired, then stop, and the cause would look like a provider outage.

Provider credentials that a workspace types in — an IMAP password, a messaging provider's key — are sealed by `settings_store` on every save and never echoed back to a screen. Rotating one means entering the new value, not reading the old one back to compare.

## Does Connect use credential rotation?

Yes, in three distinct places, and it is worth separating them because they rotate on different clocks and fail differently.

| Credential | Rotated by | How Connect keeps up |
|---|---|---|
| Database master password | The managed database service, into a secrets store | Read at boot from the secret named by `MAYA_MAIL_DB_SECRET_ARN`; no copy is kept |
| OAuth access tokens for a mailbox | The identity provider, continuously | `oauth.store_tokens` rebuilds and reassigns the `config` bundle on every refresh |
| Provider keys a workspace supplies | A person, when they choose | Sealed by `settings_store` on save; never displayed again |
| Your own sign-in | Google, as the only sign-in path | There is no password to rotate — password sign-in does not exist here |

What Connect does not do is claim a certification about any of this. The mechanism is documented so you can judge it; no audit badge is being asserted on the strength of it.

## Failure modes worth recognising

**Everything works, then a restart authenticates as nobody** — A stale credential copy somewhere the rotation system cannot see. Look at what holds a password, not at what deployed.
**One mailbox stops after roughly an hour** — Refreshed tokens are not being persisted. The symptom appears at access-token lifetime, not at reconnection.
**A rotation succeeds and reads fail intermittently** — Some instances hold the old value and some fetched the new one. Fetch-at-boot makes this a restart-ordering question rather than a permanent split.
**A key cannot be compared against what was entered** — Correct. Sealed values are not readable; re-enter rather than verify.

## Questions

### Does a rotation interrupt Connect?

A rotation of the database credential does not, because the value is read at boot and the managed service keeps both the old and the new secret valid across the changeover window. An OAuth refresh does not interrupt anything either — it happens inside a normal fetch. What interrupts things is a copy of a secret that nobody rotated.

### Can I see the credentials a workspace has stored?

No. Provider credentials are sealed on save and are never echoed back to a screen, for either audience. You can see that a credential is present, and you can see the health verdicts that tell you whether it still works, but the value itself does not come back out.

### Why is there no password to rotate for my own account?

Google OAuth is the only sign-in path into Connect and password sign-in is not implemented. Your account security is your Google account's security, including its own rotation and second factor.

## Related

- [Secret management](https://connectbyjbrh.com/docs/technology/secret-management/)
- [How provider credentials are stored](https://connectbyjbrh.com/docs/security/credential-sealing/)
- [OAuth 2.0](https://connectbyjbrh.com/docs/technology/oauth/)
- [Encryption at rest](https://connectbyjbrh.com/docs/technology/encryption-at-rest/)
- [Signing in with Google](https://connectbyjbrh.com/docs/account/google-sign-in/)

## What this page is based on

- `docs-source/sources/GENERAL.md` §10 — security and the database secret
- `docs-source/sources/CHANNELS.md` §1 — mailbox config as a real setter
- Connect capability registry (docs-source/facts.py)
