Operations: Rotation & Incident Response
What to do when secrets change, when keys change, when people change, and when things go wrong. None of these should be "I'll figure it out in the moment" — they're the questions a serious security reviewer will ask, and the right time to answer them is before you need the answers.
Rotation: routine
Rotate a single secret
A value changed at its provider — Stripe key regenerated, OAuth client secret rolled, database password updated — and you need to update the .sec file:
That's it. dotsec set updates the value in-place using the existing DEK; the rest of the file's encrypted entries are untouched. CI on the next deploy picks up the new value via dotsec run. The old plaintext was never written to disk.
Rotate the data encryption key (DEK)
After a suspected compromise, on a periodic schedule, or after offboarding someone who had the key file:
This decrypts every encrypted value with the old DEK, generates a fresh DEK, re-encrypts every value under the new DEK, and refreshes the dek= and mac= fields in the @dotsec(...) directive. Every ENC[...] blob in the file changes; the plaintext values do not.
For the local age provider, the DEK is wrapped under your existing .sec.key. For KMS, rotate-key calls GenerateDataKey for a fresh wrapped DEK. Either way: commit the new .sec, ship it.
If a value itself was leaked (Stripe key exposed in a log file), rotating the DEK does not help — the value was visible at its provider's API surface, not in the .sec file. Rotate the actual secret at the provider, then dotsec set the new value. Treat DEK rotation as orthogonal to value rotation; both have their place.
Rotate the keypair (local provider only)
After a key file leak or to ship a fresh keypair to a new team:
Then share the new .sec.key via your password manager and have everyone replace their local copy. For KMS, this isn't a thing — there's no keypair to rotate. You rotate the KMS key itself in AWS (aws kms enable-key-rotation for annual automatic rotation, or manual key migration if you need policy changes).
Revocation: people changing
Offboard a developer (local provider)
When someone leaves the team, the .sec.key file they had on disk is still valid forever. The model is "anyone with this file can decrypt." So:
There's no concept of "revoke just this person" with the local provider — rotation is the only mechanism. If your team turns over often, this is friction. The KMS provider exists partly because of this.
Offboard a developer (KMS provider)
Done. Instant. They can no longer call kms:Decrypt, the wrapped DEK in the .sec file is opaque to them, no key file changes hands. This is the structural reason to prefer KMS for any team larger than 1.
You can also restrict their access to specific time windows or specific files via IAM conditions if you need more granularity — but plain group membership covers most use cases.
Incident response
A .sec.key (or DOTSEC_PRIVATE_KEY value) leaked
Treat every value in every .sec file the key unlocks as exposed. Decryption is now possible for anyone holding the leaked key.
- Rotate the affected secrets at their providers. The Stripe key, the OAuth client secret, the database password — go to each provider's console, rotate, paste the new value into a
dotsec setcommand. - Generate a new keypair and rotate the DEK so historical
.secfiles in git (which the attacker may have copied) become useless going forward: - Investigate the leak vector. Was the key file committed accidentally? Did a backup tool copy it off-machine? Was it in a CI log? Close that path before redistributing the new key.
- Distribute the new keypair to teammates via your password manager and update CI secrets.
Note: the values rotated at their providers (step 1) are the actual recovery. The DEK rotation (step 2) is hygiene — it stops future use of the leaked key, but it doesn't unleak values an attacker may already have decrypted.
A KMS key was misused or compromised
If you suspect a principal with kms:Decrypt access decrypted things they shouldn't have:
- Query CloudTrail to see exactly what was decrypted by whom and when. With encryption-context binding, every dotsec decrypt is filterable on
requestParameters.encryptionContext.dotsec:format=v3: - Revoke the principal's access immediately — remove the IAM grant or detach the policy.
- Rotate the affected secrets at their providers (same as above — recovery happens at the source, not in the
.secfile). - Decide whether to rotate the KMS key itself. Usually not needed because the key was used legitimately by an over-permissioned principal, not extracted from the HSM. Tighten IAM, rotate the secrets, move on. If you have reason to believe the KMS key material was extracted, that's an AWS support case.
A compromised CI runner held kms:Decrypt
A malicious dependency ran in a CI job that had been granted KMS access, and it called kms:Decrypt on your wrapped DEKs and exfiltrated plaintext.
- Revoke the CI role's
kms:Decryptimmediately — block further decrypts while you investigate. - CloudTrail will show exactly which
.secfiles were decrypted during the compromise window (via encryption-context binding). - Rotate every value that was decryptable from that role, at its provider.
- Investigate the CI compromise: which dependency, which install step, was it a lifecycle script that ran before the dotsec call, did the runner allow outbound traffic to the attacker's exfil endpoint?
- Harden the CI posture so the same vector doesn't reopen. That's your standard CI security work — pin actions, pin dependencies, separate the install job from the decrypt job — and lives in your platform docs, not dotsec's. The dotsec-specific knobs (CI/CD → dotsec knobs) cover encryption-context binding, per-env keys, and step-level secret scoping.
A value leaked from your application at runtime
A secret showed up in an application log, a crash report, a frontend bundle, a docker inspect output — somewhere downstream of dotsec run. dotsec didn't help here; the leak happened after decrypt.
- Rotate the leaked value at its provider immediately — no DEK rotation needed because the
.secfile isn't what leaked. - Patch the leak source. Scrub env vars from your log formatter / crash reporter, remove the
process.env.Xreference from frontend code, lock down access to the docker daemon, whatever the vector was. - Document for the postmortem. This is exactly what the "Runtime exposure dotsec does NOT solve" list in the security model names — your app and runtime own this surface.
"Force re-encrypt" — dotsec encrypt
You edited a directive (@encrypt ↔ @plaintext toggle, @push target change), or you edited dotsec.schema, and the next dotsec run errors with an integrity-tag mismatch:
That's the file-level MAC catching your intentional edit. To accept the new state:
This re-runs the encrypt pipeline with the current DEK and refreshes the mac= field. Use it deliberately — running dotsec encrypt on a file someone else tampered with would silently legitimize the tamper. If you didn't change anything yourself, restore from git instead.
Audit trail questions
Quick answers to "what does dotsec tell me about who did what?":
The KMS column is the answer for any team that has compliance evidence requirements. The local provider is fine for solo / hobby / small-team use where "who did what" is socially answered.