Secrets in CI skill for Codex
Short-lived OIDC over long-lived keys. Codex hardens how a pipeline handles secrets: swap long-lived cloud keys for OIDC federation with tightly scoped trust policies, keep secrets away from fork PRs and untrusted actions, set least-privilege workflow permissions, pin actions to commit SHAs, and stop leakage into logs and artifacts. When a secret leaks it rotates first and treats exposure as compromise.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- secrets-in-ci Installs to ~/.agents/skills/secrets-in-ci/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install secrets-in-ci 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-in-ci Installs to .agents/skills/secrets-in-ci/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/secrets-in-ci.zipunzip -o secrets-in-ci.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-in-ci 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-in-ci, 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
Protects secrets in CI/CD pipelines (GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite) - removing long-lived cloud keys in favour of OIDC federation, scoping and masking secrets, blocking exfiltration through pull-request workflows and third-party actions, preventing secrets in logs and artifacts, and rotating what leaked. Use when a pipeline holds static cloud credentials, a fork PR needs secrets, a token leaked in a build log, or when hardening workflow permissions and pinning actions.
Use it when
- A pipeline stores long-lived cloud credentials
- Fork PRs or third-party actions can reach secrets
- A secret leaked in a build log or artifact
- You are hardening workflow permissions and pinning actions
Use something else when
- You need to find and purge secrets committed to git history: use secret-scanning
- You are choosing a runtime secrets manager for the app: use secrets-management
How to check the result
Logs and artifacts contain no secret after a run, fork PR builds run without secrets, production environment secrets are unreachable from a feature branch, and breaking the OIDC trust sub denies the role assumption.
The mistake it prevents
An OIDC trust policy whose sub condition is a wildcard or missing, letting workflows from other repositories assume the deploy role.
Done well means: no long-lived cloud key sits in CI when short-lived OIDC tokens will do, every secret is least-privilege and scoped to the jobs that need it, untrusted code (fork PRs, third-party actions) never reaches a secret, and logs and artifacts are clean. CI is a high-value target: it has credentials and runs code on every push.
Assess the pipeline first
rg -n -i "AKIA|aws_secret|AIza|ghp_|xox[baprs]-|-----BEGIN|password|token" \
.github/ .gitlab-ci.yml .circleci/ Jenkinsfile # static secrets committed in config
rg -n "secrets\.|\$\{\{ secrets" .github/workflows/
rg -n "uses:\s" .github/workflows/ | rg -v "@[0-9a-f]{40}" # actions not pinned to a SHA
rg -n "pull_request_target|workflow_run" .github/workflows/ # dangerous triggers to review
Read AGENTS.md and the provider’s settings (repo/org secrets, environments, OIDC trust).
Note which jobs actually need which secret; most need none.
Prefer OIDC over stored cloud keys
Replace static AWS_ACCESS_KEY_ID / GCP service-account JSON / Azure client secrets with
federated short-lived credentials. The CI provider issues a signed OIDC token; the cloud
exchanges it for a session scoped by a trust policy.
# GitHub Actions -> AWS, no stored keys
permissions:
id-token: write # required to request the OIDC token
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4 # pin to a SHA in practice
with:
role-to-assume: arn:aws:iam::111122223333:role/ci-deploy
aws-region: eu-west-1
The AWS IAM trust policy must pin the subject tightly, or any repo can assume the role:
"Condition": {
"StringEquals": {"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"},
"StringLike": {"token.actions.githubusercontent.com:sub": "repo:ORG/REPO:ref:refs/heads/main"}
}
- Scope
subto the exact repo and ref/environment.repo:ORG/*or a bare audience check is how misconfigured trust policies get abused across repos. - GitLab (
id_tokens), CircleCI and Buildkite have equivalent OIDC federation to AWS, GCP and Azure. Vault also accepts CI OIDC for dynamic, short-TTL secrets. - Where a static secret is unavoidable (a third-party API with no federation), store it in the provider’s secret store with the narrowest scope and rotate it on a schedule.
Keep untrusted code away from secrets
The core rule: secrets are available to workflows triggered by trusted code, never to arbitrary fork contributions.
- GitHub:
pull_requestfrom a fork runs without secrets by default — keep it that way.pull_request_targetandworkflow_runrun in the base repo’s context with secrets and must never check out and execute the PR’s code. If you need to build a fork PR with secrets (for example to deploy a preview), gate it behind an environment with required reviewers, or split into an untrusted build job (no secrets) and a trusted deploy job. - Set least-privilege
permissions:at the top of every workflow (contents: readdefault); grantid-token,packages,deploymentsonly to the jobs that need them. - Pin third-party actions to a full commit SHA, not a tag — a tag can be moved to malicious code. Review what an action does before adding it; it runs with your token and can read env. Prefer first-party or widely-audited actions.
- Use GitHub environments with protection rules and environment-scoped secrets so production credentials are not readable from a feature-branch run.
Stop leakage through logs and artifacts
- Register dynamic secrets as masks (
echo "::add-mask::$VALUE"); provider masking only covers values it already knows. Neverechoa secret, and avoidset -x/bash -xin steps that touch them. - Do not pass secrets as command-line arguments (they show in process lists and some logs); use env vars or files with restricted permissions, deleted at job end.
- Exclude
.env, credential files and~/.awsfrom uploaded artifacts and caches; a cache key that captures a token persists it. - Add a secret scanner to CI (gitleaks, trufflehog) and enable the provider’s push
protection so a secret is caught before merge. Detection and rotation of committed secrets
is
$secret-scanning.
If a secret leaked
Treat exposure as compromise, in this order:
- [ ] 1. Rotate/revoke the credential at the provider now (do not wait to confirm misuse)
- [ ] 2. Invalidate derived sessions/tokens
- [ ] 3. Review access logs for use during the exposure window
- [ ] 4. Remove it from logs/artifacts; purge affected caches
- [ ] 5. Replace with OIDC or a scoped secret so it cannot recur
Rotation touches live credentials and can break running deploys. Confirm the new credential
works and coordinate before revoking the old one; get the user’s go-ahead on the cutover.
Purging a secret from git history ($secret-scanning) is separate from rotating it, and
rotating comes first — the committed value must be assumed public.
Gotchas
- Masking is not containment: a masked secret can still be exfiltrated by malicious code (base64, split, send). Masking protects against accidental logging only.
- Org/repo secrets are readable by every workflow in scope, including a new malicious one in a PR to a non-default branch if triggers allow. Scope with environments.
- Self-hosted runners on public repos execute fork PR code on your infrastructure; never attach secrets or persistent state to them without strong isolation.
GITHUB_TOKENdefaults can be write; set the org default to read-only and widen per job.- Reusable workflows and composite actions inherit secrets only when passed; pass the minimum.
Verification
- Grep merged workflow logs and artifacts for the secret pattern after a run: nothing.
- Confirm a fork PR build runs with no secrets (the deploy job is skipped or gated).
- From a non-privileged branch, confirm production environment secrets are not accessible.
- For OIDC: break the IAM trust
subcondition in a test and confirm the role assumption is denied, then restore it. rg "uses:" .github/workflows | rg -v "@[0-9a-f]{40}"returns only first-party actions you accept unpinned.
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 in CI"
short_description: "Short-lived OIDC over long-lived keys"
default_prompt: "$secrets-in-ci Replace our AWS access keys in GitHub Actions with OIDC" 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.