---
title: Secret rotation and exposed secrets
slug: secret-rotation-and-exposed-secrets
summary: What to do in the first hour after a secret is exposed, and how to rotate keys, tokens and passwords routinely without an outage.
order: 15
updated: 2026-11-11
status: ready
---

Use this page when a secret shows up somewhere it should not (a wiki page, a ticket, a commit, a build log, a chat) and for scheduled rotation of access keys, database passwords and API tokens.

An exposed secret is compromised. Deleting the page or commit does not fix it, because copies exist in history, caches, backups and other people's screens. The only fix is rotation.

## First hour after exposure

- [ ] **Do not copy the value anywhere**, including into the ticket that reports it. Record where it is: page, line, commit hash.
- [ ] Identify what it unlocks and who owns it.
- [ ] Tell the owner and security in a private channel. Treat it as a security incident if it grants production or customer-data access.
- [ ] Check for use: provider audit logs (CloudTrail for AWS keys, the vendor's audit log for API tokens) since the exposure time.
- [ ] Rotate: issue new, deploy new, revoke old.
- [ ] Only then remove it from the source (edit the page, purge the history if the platform allows).

```bash
# Was an AWS access key used, and from where? (read-only)
aws --profile <readonly-profile> iam get-access-key-last-used --access-key-id <key-id>
aws --profile <readonly-profile> --region <region> cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=<key-id> \
  --query 'Events[].{t:EventTime,what:EventName,src:CloudTrailEvent}' --max-results 20
```

The access key ID is an identifier, not the secret; it is safe to use in queries. The secret access key is never written down.

## Rotate without an outage: the two-key dance

```mermaid
flowchart LR
  A[Create new credential] --> B[Store in secret manager]
  B --> C[Deploy consumers to read new value]
  C --> D[Verify consumers use new]
  D --> E[Disable old]
  E --> F[Wait, watch errors]
  F --> G[Delete old]
```

- [ ] Most systems allow two active credentials at once (AWS allows two access keys per user). Use that overlap.
- [ ] Disable before delete. A disabled key can be re-enabled if something breaks.
- [ ] Wait at least one full business cycle (often 24 hours, longer for monthly batch jobs) between disable and delete.

## Make rotation routine

- [ ] Store secrets only in a secret manager (AWS Secrets Manager, SSM Parameter Store SecureString, Vault). Applications read at start-up or on a schedule.
- [ ] Turn on managed rotation where it exists (for example Secrets Manager rotation for RDS).
- [ ] Replace long-lived keys with roles: SSO for people, instance and task roles for workloads, [OIDC](./oidc-from-ci-to-cloud.md) for CI.
- [ ] Track the age of every remaining key; alert at 90 days.

```bash
# Access keys older than 90 days (read-only)
aws --profile <readonly-profile> iam generate-credential-report >/dev/null; sleep 5
aws --profile <readonly-profile> iam get-credential-report --query Content --output text \
  | base64 -d | awk -F, 'NR==1 || $9=="true" {print $1","$9","$10}'
```

## Find secrets before someone else does

- [ ] Secret scanning on every repo and MR (your host's built-in scan, gitleaks, trufflehog).
- [ ] Scan CI definitions and build specs for plaintext environment variables.
- [ ] Periodically scan wikis and ticket systems with the vendor's search for obvious patterns like `AKIA`, `-----BEGIN`, `password=`.

```bash
gitleaks detect --source . --redact --report-path gitleaks.json
```

## Pitfalls

- Pasting the exposed value into the report "so they know which one". Point to the location instead.
- Rotating without knowing every consumer. Search code, CI variables and config for the secret's name first.
- Deleting the old key immediately. Disable first.
- Rotating and forgetting the backup systems, DR region or batch jobs that run monthly.
