A copy of a password somebody else rotates
A credential you copy is a credential you have to keep. Connect's database password is not in .env at all: RDS owns the master credential and rotates it into Secrets Manager, MAYA_MAIL_DB_SECRET_ARN names the secret, and boot reads the value from there. Before that change, a stale copy made the *next* restart authenticate as nobody — long after the rotation, with a deploy in between to take the blame.
The shape of the incident#
Three things have to line up, and all three are ordinary. A managed database owns its own master credential and rotates it on a schedule nobody watches. A copy of that credential sits in a deployment environment file because it was needed once. And a long-running process reads its configuration exactly once, at start.
The consequence is a fault with a delay built into it. The rotation lands while the application is up and connected, so nothing breaks: existing connections keep working and no new authentication happens. The stale copy becomes visible only when the process next starts — which might be hours or days later, and is almost always just after somebody deployed something.
- Cause
- A credential was copied, and the authority for that credential changed it
- Symptom
- Authentication refused for a value nobody edited
- Delay
- Between the rotation and the next restart — unbounded
- Prime suspect
- Whichever change happened to be deployed inside that window
Why it reads as a bad deploy, and how to tell#
Everything about the timing points at the release. The application was healthy before, a change went out, and now it cannot reach its database. The natural next move — roll back — does not help, because the previous revision reads the same stale copy and fails identically. That second failure is the useful signal, and it usually arrives after the rollback has already spent the window it was meant to save.
- Read the error rather than the timeline. An authentication refusal names credentials; a bad deploy usually names an import, a migration or a port.
- Ask whether the rolled-back revision fails the same way. If it does, the cause is older than both revisions.
- Ask what else changed that nobody deployed — a rotation, an expiry, a revoked key, a certificate. Each has its own schedule, and none of them appear in a change log.
- Only then compare the two revisions.
Reading the value instead of holding a copy#
The repair is not a better copy — it is not holding one. .env names the secret rather than carrying its contents: MAYA_MAIL_DB_SECRET_ARN is an identifier, and boot resolves it against Secrets Manager to get whatever is current at that moment. A rotation then costs nothing, because the next boot asks again.
| Where the value lives | What a rotation costs | How failure surfaces |
|---|---|---|
| Copied into a deployment file | Every copy must be found and updated by hand | At the next restart, whenever that happens to be |
| Named by reference, resolved at boot | Nothing; the next boot reads the current value | Immediately and honestly, as a secret-store or permission error |
| Resolved for every connection | Nothing, and no restart needed | As a hard dependency on the secret store in the connection path — not what Connect does |
The same principle runs through the rest of the product's handling of credentials. Provider credentials are sealed by settings_store on every save and never echoed back to a screen, so there is no route by which a second copy gets made at all. And there is no password sign-in anywhere in Connect — Google OAuth is the only way in — which leaves the database's own credential very nearly the only one of its kind in the system.
What this does not solve#
- A rotation that lands *between* boots is still invisible until the next one. Reading at boot removes the stale copy; it does not make a running process notice a change.
- The secret store becomes a boot dependency. If it is unreachable, or the instance's permission is wrong, the application does not start — the correct failure, but a new one to recognise.
- The identifier in
.envis configuration, not a secret, and it can still be wrong. Pointing at the wrong secret fails in a way that looks exactly like a bad credential. - None of this is a certification of anything. It is one decision about where a value is read from.
Questions#
Why did it fail at a restart rather than when the rotation happened?
Because authentication happens when a connection is made, and a healthy application is not making new ones against a fresh credential. The rotation changed the answer to a question nobody asked again until the process started. That gap between cause and symptom is the whole difficulty.
Is the secret identifier itself sensitive?
It names a secret rather than containing one, and reading the secret still requires the running instance's own permission. Treat it as configuration: not something to publish for no reason, but not a credential either, and not something whose exposure would require a rotation.
What is the general rule?
If another system is the authority for a value, keep the address of it rather than a copy. That covers rotated database passwords, refreshed OAuth tokens and anything else with an expiry — and it is why a mailbox's config is a real setter rather than a read-only property: the token refresh rebuilds the dict and assigns it back, and a read-only property would drop every refreshed access token.