---
title: "Rotate secrets automatically · Agent Wranglers"
url: https://agentwranglers.se/playbook/infrastructure/configuration-and-secrets/automatic-secret-rotation
updated: 2026-10-20
licence: "All rights reserved"
---

# Rotate secrets automatically

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Configuration and secrets](https://agentwranglers.se/playbook/infrastructure/configuration-and-secrets)
- Rule: Let the secrets store rotate each secret on a schedule, every 90 days at most. The app picks up the new value without a deploy.
- How: Turn on the store's rotation, such as rotation functions in AWS Secrets Manager or dynamic secrets in Vault. Keep the old and new value valid together while the switch happens. The app caches a secret for 1 hour at most. It fetches it again when a call fails with an authentication error. Rotate at once after a leak, with the same job. Where the store cannot rotate a secret, rotate it by hand within the same 90 days, as [keys with the least power](https://agentwranglers.se/playbook/adversarial-testing/data-and-secrets/least-privilege-keys) requires.
- Check: The query below lists no secret without rotation or with a last rotation over 90 days ago. Documented secrets rotated by hand within 90 days are the only exception. Trigger a rotation on staging, and the app keeps serving without a restart.

Example:

```sh
# Prints every secret that does not rotate or rotated over 90 days ago. Expect nothing.
aws secretsmanager list-secrets --output json | jq -r --arg limit "$(date -u -d '-90 days' +%FT%T)" \
  '.SecretList[] | select(.RotationEnabled != true or (.LastRotatedDate // "") < $limit) | .Name'
```

```ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

const CACHE_MS = 60 * 60 * 1000;
const store = new SecretsManagerClient({});
const cache = new Map();

export async function getSecret(name, { refresh = false } = {}) {
  const cached = cache.get(name);
  if (!refresh && cached && Date.now() - cached.fetchedAt < CACHE_MS) return cached.value;
  const { SecretString } = await store.send(new GetSecretValueCommand({ SecretId: name }));
  cache.set(name, { value: SecretString, fetchedAt: Date.now() });
  return SecretString;
}
// On an authentication error: getSecret(name, { refresh: true }) and retry once.
```

```python
import time
import boto3

CACHE_SECONDS = 3600
store = boto3.client("secretsmanager")
cache = {}

def get_secret(name, refresh=False):
    cached = cache.get(name)
    if not refresh and cached and time.monotonic() - cached[1] < CACHE_SECONDS:
        return cached[0]
    value = store.get_secret_value(SecretId=name)["SecretString"]
    cache[name] = (value, time.monotonic())
    return value
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers infrastructure playbook: https://agentwranglers.se/playbook/infrastructure/configuration-and-secrets/automatic-secret-rotation`.

- Previous: [Give services the identity of their machine](https://agentwranglers.se/playbook/infrastructure/configuration-and-secrets/workload-identity)
- [Configuration and secrets](https://agentwranglers.se/playbook/infrastructure/configuration-and-secrets)
