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
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
# 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
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:
# 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
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 — 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:
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:
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
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
Subscribe to rotation webhooks
Configure
secret.rotatedandrotation.grace_period_endedwebhooks so agents can invalidate cached credentials immediately. - 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
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
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.