Security Hardening Guide
Nine production-grade security practices for your Agent Secret Store deployment. These aren't theoretical — each one prevents a real class of incidents.
Defense in depth
No single security measure is sufficient. Apply all nine of these practices together. They're ordered roughly by impact — start from the top if you're prioritizing.
- 1
Use minimum-scope tokens
Never request a token broader than what the agent actually needs. If it only reads Gemini keys, scope it to secrets:read:production/gemini/* — not secrets:read:*.
Shell# ❌ Too broad — reads all secrets across all namespaces ass tokens issue --scopes "secrets:read:*" --ttl 24h # ✅ Minimum scope — only what this agent actually needs ass tokens issue \ --scopes "secrets:read:production/gemini/*" \ --ttl 1h - 2
Set appropriate token TTLs
Short-lived tokens limit the window of exposure if a token is leaked. 1 hour is appropriate for most agents; never issue tokens with TTLs longer than 24 hours without a very specific reason.
Shell# Recommended TTLs by use case # Short-lived pipeline step — 15 minutes ass tokens issue --scopes "secrets:read:production/*" --ttl 15m # Agent running inference — 1 hour ass tokens issue --scopes "secrets:read:production/gemini/*" --ttl 1h # Long-running worker process — 8 hours, with refresh ass tokens issue --scopes "secrets:read:production/db/*" --ttl 8h # Avoid max-lifetime, all-namespace tokens unless this is a controlled break-glass flow ass tokens issue --scopes "secrets:read:*" --ttl 24h # ❌Token refresh pattern
Long-running services should refresh tokens before expiry rather than requesting long TTLs. The Python and JavaScript SDKs support automatic token refresh.
- 3
Use approval workflows for critical secrets
Mark your most sensitive credentials (production payment keys, root database credentials, signing keys) as critical tier. This triggers a multi-approver workflow before access is granted.
JSON# Set up approval requirements via the dashboard or API # Critical tier secrets require approval by default # Configure in your vault settings: { "approval_policy": { "tiers": { "standard": { "required_approvals": 0 }, "sensitive": { "required_approvals": 1, "timeout_hours": 24 }, "critical": { "required_approvals": 1, "timeout_hours": 24 } }, "approvers": [ "you@company.com", "security@company.com" ] } } - 4
Enable IP allowlists on tokens
Restrict token use to known IP ranges. A token stolen by an attacker outside your allowed ranges is immediately useless.
Shell# Configure IP allowlist for a token via the API curl -X POST https://api.agentsecretstore.com/v1/tokens \ -H "Authorization: Bearer $ASS_AGENT_KEY" \ -H "Content-Type: application/json" \ -d '{ "scopes": ["secrets:read:production/*"], "ttl": "1h", "ip_allowlist": [ "10.0.1.100/32", "10.0.1.101/32", "172.16.0.0/12" ] }' - 5
Review the audit log weekly
Look for unexpected agents, unusual access patterns, or access to secrets that shouldn't be needed by that agent. Export logs to your SIEM for automated alerting.
Shell# Review recent access — look for anomalies ass audit --limit 100 # Export for SIEM / compliance ass audit --json --limit 1000 > audit-$(date +%Y-%m-%d).json # Follow live for real-time monitoring ass audit --tail - 6
Rotate secrets on a schedule
Regular rotation limits the window of exposure for leaked credentials. Automate rotation with a weekly CI/CD job or cron task.
Shell# Rotate Gemini key — store the replacement value as a new version ass secrets set production/gemini/GEMINI_API_KEY "$NEW_GEMINI_API_KEY" \ --type api_key \ --tier sensitive # Restart or invalidate caches in long-running services after the write # Automate rotation with a cron job or CI/CD schedule # .github/workflows/rotate-secrets.yml name: Weekly Secret Rotation on: schedule: - cron: '0 2 * * 0' # Every Sunday at 2am UTC jobs: rotate: runs-on: ubuntu-latest steps: - name: Rotate Gemini key env: ASS_AGENT_KEY: ${{ secrets.ASS_AGENT_KEY }} run: | NEW_KEY=$(./scripts/create-gemini-key.sh) ass secrets set production/gemini/GEMINI_API_KEY "$NEW_KEY" --type api_key --tier sensitive - 7
Register one agent per service
A compromised service should only expose secrets for that service — not your entire vault. One agent per service enforces this at the infrastructure level.
Shell# ❌ One shared agent for everything ass agents create \ --name "all-services" \ --scopes "secrets:read:production/*" # ← any compromised service = full breach # ✅ One agent per service, minimum scope ass agents create \ --name "inference-service" \ --scopes "secrets:read:production/gemini/*,secrets:read:production/bedrock/*" ass agents create \ --name "payment-service" \ --scopes "secrets:read:production/stripe/*" ass agents create \ --name "notification-service" \ --scopes "secrets:read:production/slack/*,secrets:read:production/sendgrid/*" ass agents create \ --name "data-pipeline" \ --scopes "secrets:read:production/database/*" - 8
Use namespaces to separate environments
Keep production, staging, and dev secrets in separate namespaces. A staging agent should never be able to read production secrets.
Shell# ✅ Recommended namespace structure # Separate environments production/gemini/GEMINI_API_KEY # live traffic only staging/gemini/GEMINI_API_KEY # staging/QA dev/gemini/GEMINI_API_KEY # shared dev key # Separate by service production/stripe/STRIPE_SECRET_KEY production/stripe/WEBHOOK_SECRET production/gemini/GEMINI_API_KEY production/bedrock/AWS_ACCESS_KEY_ID production/database/PRIMARY_URL production/database/REPLICA_URL # Agent scopes align to service boundaries: # payment-service: secrets:read:production/stripe/* # inference-service: secrets:read:production/gemini/*,secrets:read:production/bedrock/* # data-pipeline: secrets:read:production/database/* - 9
Never store plaintext secrets in CI/CD
Store only ASS_AGENT_KEY in your CI/CD secret store. All other credentials should be fetched at runtime. This reduces your attack surface from N secrets to 1.
Shell# ❌ Never store production secrets in CI/CD variables # GitHub Actions secrets: # GEMINI_API_KEY=gemini-api-key-placeholder ← plaintext in GitHub # STRIPE_SECRET_KEY=sk_live_xyz ← plaintext in GitHub # ✅ Store only the vault key — everything else fetched at runtime # GitHub Actions secrets: # ASS_AGENT_KEY=ass_xxxx ← only this one key # In your workflow: - name: Fetch secrets env: ASS_AGENT_KEY: ${{ secrets.ASS_AGENT_KEY }} run: | export GEMINI_API_KEY=$(ass secrets get production/gemini/GEMINI_API_KEY --silent) export STRIPE_SECRET_KEY=$(ass secrets get production/stripe/STRIPE_SECRET_KEY --silent)The vault key is a single-credential blast radius
If ASS_AGENT_KEY is compromised, you can revoke it from the dashboard instantly — invalidating all derived tokens and access. With plaintext secrets in CI/CD, you'd need to rotate each one individually.
Additional: minimum-scope in Python
import os
from agentsecretstore import AgentVault
vault = AgentVault(agent_key=os.environ["ASS_AGENT_KEY"])
# ❌ Requesting a broad token
broad_token = await vault.request_token(
scopes=["secrets:read:*"],
ttl="24h",
)
# ✅ Requesting minimum-scope token
token = await vault.request_token(
scopes=["secrets:read:production/gemini/*"],
ttl="1h",
)Additional: requesting approval in Python
import os
from agentsecretstore import AgentVault, ApprovalRequiredError
vault = AgentVault(agent_key=os.environ["ASS_AGENT_KEY"])
# Requesting a critical secret triggers an approval workflow
try:
secret = await vault.get_secret("production/stripe/STRIPE_SECRET_KEY")
except ApprovalRequiredError as e:
print(f"Approval required: {e.approval_id}")
status = await vault.get_approval_status(e.approval_id)Hardening checklist
- All tokens have the minimum required scope
- No token TTL exceeds 24 hours without documented justification
- Sensitive and critical secrets require approval
- Production tokens have IP allowlists configured
- Audit log reviewed weekly (or SIEM integration active)
- Secrets rotated on a documented schedule (max 90 days)
- One agent per service — no shared agent keys
- production/* namespace separated from staging/* and dev/*
- Only ASS_AGENT_KEY stored in CI/CD — all others fetched at runtime