Agent Secret Store DocsSign up
📘 Guides

Secret Rotation

Rotate credentials on a schedule or on demand — with zero agent downtime thanks to version-aware grace periods and automatic cache invalidation via webhooks.

Why rotation matters

Limits exposure window

A leaked key is only useful until it's rotated. Rotate weekly and a breach window is 7 days max.

🔍

Compliance requirement

SOC 2, PCI-DSS, and HIPAA all recommend or mandate periodic key rotation with documented evidence.

🧹

Eliminates orphaned keys

Rotation cleans up keys created by former employees or deprecated services before they become attack vectors.

Manual rotation

Manual rotation is the simplest approach: generate a new credential at the provider, then update the vault. The dashboard supports one-click rotation from the secret detail page, or use the API:

Python

rotate.py
from agentsecretstore import AgentVault

async def rotate_openai_key(new_key: str):
    """Manual rotation: update a secret with a new value."""
    async with AgentVault() as vault:
        # Update creates a new version automatically
        updated = await vault.update_secret(
            path="production/openai/api-key",
            value=new_key,
            # Optional: keep old version valid for 15 minutes (grace period)
            grace_period_seconds=900,
        )
        print(f"Rotated to version: {updated.version}")
        print(f"Old version valid until: {updated.grace_expires_at}")

curl

Shell
# Rotate via REST API
curl -X PATCH https://api.agentsecretstore.com/v1/secrets/production%2Fopenai%2Fapi-key \
  -H "Authorization: Bearer $ASS_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "value": "sk-proj-new-key-here",
    "grace_period_seconds": 900
  }'

Grace period

When you rotate a secret with a grace_period_seconds value, both the old and new versions are valid during the grace window. This lets in-flight agents complete their work before the old key is invalidated — zero downtime rotation.

GRACE PERIOD TIMELINE

T+0s
Rotation triggeredNew secret version activated
T+0–900s
Grace period activeBoth old version v2 and new version v3 are valid
T+900s
Grace period endsOld version v2 invalidated; rotation.grace_period_ended fires
T+900s+
Only new version validAgents that cached old key receive 410 Gone

Recommended grace period

Set grace periods to at least the maximum expected agent task duration. For agents that run hour-long tasks, use grace_period_seconds=7200 (2 hours) to ensure no in-flight task is interrupted.

Scheduled rotation

Define a cron schedule and let the vault handle rotation automatically. For provider-managed keys (OpenAI, Anthropic, etc.), the vault calls a webhook on your rotation server which creates the new key and confirms back.

Cron expression reference:

Shell
# Common cron expressions for rotation schedules

# Every 24 hours at midnight UTC
0 0 * * *

# Every Sunday at 2 AM UTC (weekly)
0 2 * * 0

# 1st of every month at midnight (monthly)
0 0 1 * *

# Every 90 days (quarterly — use a cron job that fires daily, checks 90-day mark)
0 3 * * *   # fires daily; rotation logic checks elapsed time

# Every 6 hours (for high-rotation environments)
0 */6 * * *

Python — configure schedule

rotation_schedule.py
from agentsecretstore import AgentVault
from agentsecretstore.rotation import RotationSchedule, RotationStrategy

async def configure_scheduled_rotation():
    async with AgentVault() as vault:
        # Rotate every 30 days, generate a new key via the provider
        await vault.rotation.create_schedule(
            path="production/openai/api-key",
            schedule=RotationSchedule(
                cron="0 2 * * 0",          # Every Sunday at 2 AM UTC
                strategy=RotationStrategy.PROVIDER_WEBHOOK,
                provider="openai",
                grace_period_seconds=1800, # Old key valid 30 min after rotation
            ),
        )

        # Monthly rotation (1st of each month at midnight)
        await vault.rotation.create_schedule(
            path="production/stripe/restricted-key",
            schedule=RotationSchedule(
                cron="0 0 1 * *",
                strategy=RotationStrategy.EXTERNAL_WEBHOOK,
                webhook_url="https://your-rotation-server.com/rotate/stripe",
                grace_period_seconds=3600,
            ),
        )

        # List all active schedules
        schedules = await vault.rotation.list_schedules()
        for s in schedules:
            print(f"{s.path}: {s.cron} (next: {s.next_run})")

Automated rotation with webhooks

For fully automated rotation, run a small rotation server that receives webhook events, creates new credentials at the provider, and confirms back to the vault. Here's a complete FastAPI example for OpenAI key rotation:

rotation_server.py
# rotation_server.py — Receives rotation webhooks from Agent Secret Store
# and handles the provider-side key rotation

import hmac
import hashlib
import os
import asyncio
from fastapi import FastAPI, Request, HTTPException
import httpx

app = FastAPI()

WEBHOOK_SECRET = os.environ["ASS_WEBHOOK_SECRET"]

def verify_signature(payload: bytes, signature: str) -> bool:
    """Verify Agent Secret Store webhook signature."""
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

@app.post("/rotate/openai")
async def rotate_openai_key(request: Request):
    """Handle OpenAI key rotation webhook."""
    payload = await request.body()
    sig = request.headers.get("X-ASS-Signature", "")

    if not verify_signature(payload, sig):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = await request.json()

    if event["event"] != "rotation.requested":
        return {"status": "ignored"}

    # Step 1: Create a new key at the provider
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://api.openai.com/v1/api_keys",
            headers={"Authorization": f"Bearer {os.environ['OPENAI_ADMIN_KEY']}"},
            json={"name": f"rotated-{event['rotation_id']}"},
        )
        new_key = resp.json()["key"]

    # Step 2: Confirm rotation — vault updates the secret with the new value
    # and starts the grace period
    from agentsecretstore import AgentVault
    async with AgentVault() as vault:
        await vault.rotation.confirm(
            rotation_id=event["rotation_id"],
            new_value=new_key,
        )

    # Step 3: Delete the old key at the provider AFTER grace period
    # (vault fires a rotation.grace_period_ended webhook)
    return {"status": "rotated", "rotation_id": event["rotation_id"]}

@app.post("/rotation/grace-ended")
async def grace_period_ended(request: Request):
    """Called when the grace period expires — safe to delete old key."""
    payload = await request.body()
    sig = request.headers.get("X-ASS-Signature", "")

    if not verify_signature(payload, sig):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = await request.json()
    old_key_id = event["metadata"]["old_key_id"]

    # Revoke the old key at the provider
    async with httpx.AsyncClient() as client:
        await client.delete(
            f"https://api.openai.com/v1/api_keys/{old_key_id}",
            headers={"Authorization": f"Bearer {os.environ['OPENAI_ADMIN_KEY']}"},
        )

    return {"status": "old_key_deleted"}

Agent notification on rotation

Long-running agents that cache secrets need to know when a secret has rotated. Subscribe to secret.rotated and rotation.grace_period_ended webhook events to invalidate caches and pre-warm with the new value:

cache_invalidation.py
from agentsecretstore import AgentVault
from agentsecretstore.webhooks import WebhookServer
import os

WEBHOOK_SECRET = os.environ["ASS_WEBHOOK_SECRET"]

async def handle_rotation(event: dict):
    """
    Called when a secret is rotated.
    Agent should invalidate its cached copy and re-fetch.
    """
    if event["event"] in ("secret.rotated", "rotation.grace_period_ended"):
        path = event["resource_path"]
        print(f"Secret rotated: {path}")

        # Invalidate local cache entry
        secret_cache.delete(path)

        # Optionally: pre-warm cache with new value
        async with AgentVault() as vault:
            new_secret = await vault.get_secret(path)
            secret_cache.set(path, new_secret.value, ttl=300)

        print(f"Cache refreshed for {path}")

server = WebhookServer(secret=WEBHOOK_SECRET)
server.on("secret.rotated", handle_rotation)
server.on("rotation.grace_period_ended", handle_rotation)
await server.listen(port=8080)

Rollback to previous version

If a rotated credential turns out to be broken (wrong permissions, wrong key format, API provider error), roll back to the previous version instantly. Rollback creates a new active version pointing to the prior plaintext:

rollback.py
from agentsecretstore import AgentVault

async def rollback_bad_rotation():
    """
    Rollback to previous version if the new credential is broken.
    The grace period must still be active OR the old version must still exist.
    """
    async with AgentVault() as vault:
        # List versions to find the target
        versions = await vault.list_versions("production/openai/api-key")
        for v in versions:
            print(f"  {v.version}: active={v.is_active}, created={v.created_at}")

        # Rollback to the previous version
        await vault.rollback_secret(
            path="production/openai/api-key",
            version="v2",
        )
        print("Rolled back to v2 — investigate why v3 was broken")

Rollback window

Versions are retained for 90 days (Growth) and 365 days (Enterprise). On the Starter plan, only the current and immediately prior version are retained. Plan your rollback strategy accordingly.

Rotation setup checklist

  1. 1

    Set grace periods on all production secrets

    Use a grace period of at least 2× your longest expected agent task duration. For short tasks (under 5 minutes), 900 seconds (15 min) is usually sufficient.

  2. 2

    Subscribe to rotation webhooks

    Configure secret.rotated and rotation.grace_period_ended webhooks so agents can invalidate cached credentials immediately.

  3. 3

    Test rotation in staging first

    Run a manual rotation on a staging secret and verify agents recover without errors before enabling scheduled rotation on production secrets.

  4. 4

    Set up rotation schedules

    Configure schedules based on sensitivity: API keys every 90 days, database credentials every 30 days, payment keys every 14 days. Use the dashboard or SDK.

  5. 5

    Verify rollback path

    Confirm that version history is accessible and that your team knows the rollback command. Document the process in your runbooks.

Vault Storage

Learn about versioning, access tiers, and secret metadata.

Security Best Practices

Recommended rotation schedules and monitoring patterns.

Audit Trail

Review rotation events and compliance exports.

Approval Workflows

Require approval for critical secret rotations.