Secrets Management skill for Codex
No plaintext keys, rotation you have rehearsed. Codex inventories where credentials live without printing them, picks a store that fits the platform (cloud secret manager, Vault or OpenBao, SOPS in Git), and wires workloads to read only their own secrets through an identity they already have. Rotation uses overlapping credentials so nothing breaks, and a leaked key is revoked before anyone touches Git history.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- secrets-management Installs to ~/.agents/skills/secrets-management/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install secrets-management The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project secrets-management Installs to .agents/skills/secrets-management/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/secrets-management.zipunzip -o secrets-management.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) secrets-management PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $secrets-management, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Moves application secrets out of code, env files and CI variables into a managed store - HashiCorp Vault or OpenBao, SOPS with age keys in Git, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, External Secrets Operator for Kubernetes - and sets up least-privilege access, dynamic credentials and zero-downtime rotation. Use when choosing where secrets live, encrypting secrets committed to a repo, wiring secrets into Kubernetes or CI, rotating a credential, or responding to a leaked key.
Use it when
- Secrets sit in code, env files, CI variables or Helm values in plaintext
- A team is choosing between Vault, SOPS and a cloud secret manager
- Kubernetes workloads need secrets synced from an external store
- A credential needs rotating or has leaked
Use something else when
- You need to scan repositories and history for leaked secrets: use secret-scanning
- The concern is how CI jobs receive credentials: use secrets-in-ci
How to check the result
Scanners find no live secrets in the tree or history, workloads start with store access and fail clearly without it, and a staging rotation drill completes with zero failed requests.
The mistake it prevents
Scrubbing a leaked key from Git history before revoking it, when the key must be assumed copied the moment it was pushed.
Done well: no plaintext secret in Git, images, logs or shell history; each workload reads only its own secrets with an identity it already has (IAM role, Kubernetes service account, OIDC) rather than a bootstrap password; every secret has an owner and a rotation path that has been exercised; and a leak is handled by revoking first.
Inspect first
- Find where secrets live today:
rg -n --hidden -g '!.git' -i "(api[_-]?key|secret|passw|token|BEGIN .*PRIVATE KEY)" .and check.env*, CI variables, Helm values, Terraform state, Docker build args. Usegitleaks git .ortrufflehog git file://.for history if installed (seesecret-scanning). - Read
AGENTS.mdand infra code to find the cloud, the orchestrator, and the existing identity mechanism (instance roles, workload identity, IRSA or EKS Pod Identity). - List each secret with: consumer, owner, how it is issued, and whether the issuer supports two valid credentials at once (needed for zero-downtime rotation).
Never print secret values in chat or logs while inspecting; report locations and names only.
Choose the store
| Situation | Default |
|---|---|
| Single cloud, app on that cloud | That cloud’s manager: AWS Secrets Manager (or SSM Parameter Store SecureString for config), GCP Secret Manager, Azure Key Vault |
| Multi-cloud or on-prem, need dynamic DB/cloud credentials | Vault (or its open-source fork OpenBao) |
| Small team, GitOps, secrets must be versioned with config | SOPS with age keys (or cloud KMS) committed encrypted |
| Kubernetes consuming any of the above | External Secrets Operator syncing into Secrets |
| Developer laptops | Password manager CLI (op run, bw) or sops exec-env, not shared .env files |
Do not run Vault just to store ten static strings; its operational cost (unseal, HA, upgrades, audit) is real.
SOPS with age
age-keygen -o ~/.config/sops/age/keys.txt # private key stays local or in CI secret
# .sops.yaml
creation_rules:
- path_regex: secrets/prod/.*\.yaml$
age: age1prodrecipient...,age1breakglass...
- path_regex: secrets/.*\.yaml$
age: age1devrecipient...
encrypted_regex: ^(data|stringData|password|token|.*_key)$
sops --encrypt --in-place secrets/prod/api.yaml
sops secrets/prod/api.yaml # edit decrypted in $EDITOR, re-encrypts on save
sops --decrypt secrets/prod/api.yaml | kubectl apply -f - # only with user confirmation
sops updatekeys secrets/prod/api.yaml # after changing recipients in .sops.yaml
- Keys stay readable in diffs; only values are encrypted. Review that
encrypted_regexcovers every sensitive field. - Removing a recipient does not revoke what they already decrypted; rotate the secrets too.
- Prefer cloud KMS recipients (
kms:,gcp_kms:,azure_keyvault:) for production so access follows IAM rather than a key file.
Cloud managers
aws secretsmanager get-secret-value --secret-id prod/api/db --query SecretString --output text
gcloud secrets versions access latest --secret=api-db-password
az keyvault secret show --vault-name acme-prod --name api-db-password --query value -o tsv
- Grant read on specific secret ARNs or names, not
secretsmanager:*on*. - Fetch at startup and cache in memory with a refresh interval; per-request calls cost money and add latency. AWS provides caching clients for common languages.
- Name secrets by environment and service (
prod/api/db) so IAM policies can use prefixes.
Vault
vault kv put secret/api/prod [email protected] # @file avoids shell history
vault kv get -field=db_password secret/api/prod
- Authenticate workloads with the Kubernetes, AWS, or JWT/OIDC auth methods; never bake a token into an image.
- Use the database secrets engine for per-pod, short-TTL DB credentials where possible; it removes rotation as a separate task.
- Policies per service path (
path "secret/data/api/prod" { capabilities = ["read"] }); note KV v2 policies usesecret/data/...while the CLI usessecret/.... - Enable an audit device before going to production.
Kubernetes
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: { name: api-db }
spec:
refreshInterval: 1h
secretStoreRef: { kind: ClusterSecretStore, name: aws-sm }
target: { name: api-db }
data:
- secretKey: DB_PASSWORD
remoteRef: { key: prod/api/db, property: password }
- Kubernetes Secrets are base64, not encrypted. Enable encryption at rest (KMS provider)
and restrict
get/liston Secrets via RBAC. - Mount as files when the app can reload; env vars need a pod restart to pick up rotation (use a checksum annotation or Reloader).
- Older clusters may only serve
external-secrets.io/v1beta1; match the installed CRD version.
Rotation without downtime
- Create the new credential while the old one stays valid (dual users, two API keys, or Secrets Manager’s alternating-users rotation).
- Update the store; roll consumers (or wait for the refresh interval).
- Confirm no consumer still uses the old credential (DB
pg_stat_activityby user, API provider’s last-used timestamp, access logs). - Revoke the old credential.
Schedule: database and service credentials 90 days or dynamic; signing keys per their spec; anything a departed employee could have read, immediately.
Leaked secret response
- Revoke or rotate the credential first, before cleaning Git history. A pushed secret must be assumed copied, even from a private repo.
- Check the issuer’s logs for use since the leak time.
- Remove it from code, replace with a store lookup, add a pre-commit scanner.
- History rewrite (
git filter-repo) and force push only with explicit user approval; coordinate with everyone who has clones, and remember forks and CI caches keep copies.
Rotating production credentials, deleting secrets or versions, and force pushes all need the user’s explicit confirmation.
Verification
- Scanner over the working tree and history finds no live secrets.
- Each workload starts with its store access and fails closed (clear error) without it.
- A rotation drill in staging completes with no failed requests.
- IAM or Vault policy review: no wildcard read across environments.
Output template
## Secrets inventory: <system>
| Secret | Consumer | Store | Access identity | Rotation | Owner |
|---|---|---|---|---|---|
| prod/api/db | api pods | AWS SM | IRSA role api-prod | 30-day alternating users | platform |
Found in plaintext: <file:line, values not shown>
Actions requiring approval: <rotations, deletions, history rewrite> What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Secrets Management"
short_description: "No plaintext keys, rotation you have rehearsed"
default_prompt: "$secrets-management Get the database password and API keys out of our .env files and into AWS Secrets Manager" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.