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.

  • Security
  • $secrets-in-ci
  • 131 lines
  • 2 files
  • ~1,550 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- secrets-in-ci

Installs to ~/.agents/skills/secrets-in-ci/ for every project on this machine.

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

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.

~/.agents/skills/secrets-in-ci/SKILL.md

--- name: secrets-in-ci description: Protects secrets in CI/CD pipelines (GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite) - removing long-lived cloud keys in favour of OIDC… license: MIT ---

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 sub to 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_request from a fork runs without secrets by default — keep it that way. pull_request_target and workflow_run run 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: read default); grant id-token, packages, deployments only 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. Never echo a secret, and avoid set -x/bash -x in 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 ~/.aws from 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_TOKEN defaults 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 sub condition 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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem