Back to Cheat Sheets

🔑 Insufficient Credential Hygiene

OWASP Top 10 CI/CD Security Risks - CICD-SEC-6

HIGH RISK

📋 What Is It?

Insufficient Credential Hygiene is the failure to properly handle the large volume of secrets that flow through a modern pipeline. Every build, test, deploy, and release step needs credentials - cloud keys, registry tokens, database passwords, signing keys, SaaS tokens - and each is created, stored, passed between steps, printed, cached, and (ideally) rotated. A secret is not stored in one place; it is a river that flows through code, Git history, logs, environment variables, artifacts, and cloud IAM. When any link is careless, a secret leaks, lingers, or is over-privileged, and whoever obtains it inherits the access it grants.

CICD-SEC-6 OWASP Rank
Secrets Focus Area
Reuse Attack Style

⚠️ Common Exploits

  • Hardcoded Secrets: Keys pasted into pipeline YAML, code, or Dockerfiles
  • Secrets in Git History: Removed from HEAD but still recoverable from old commits
  • Long-Lived Static Keys: Broadly scoped cloud keys, years old, never rotated
  • Secrets in Build Logs: set -x tracing and echo defeating masking
  • Over-Shared "God" Tokens: One token reused by every pipeline and repo
  • Secrets Baked into Artifacts: Build-time ARG/ENV persisting in image layers
  • No Scanning: Leaks discovered by the attacker's scanner first

🔴 Attack Flow

1. Harvest secret from history, logs, env, artifact
↓
2. Validate scope (whoami / list / describe)
↓
3. Reuse against cloud / registry / DB / prod
↓
4. Pivot via over-shared tokens; mint new keys
↓
5. IMPACT: Full account or supply-chain takeover - no exploit needed!

❌ Vulnerable Code

# .github/workflows/deploy.yml - static keys pasted into the workflow name: deploy on: { push: { branches: [main] } } jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy to S3 run: aws s3 sync ./dist s3://prod-assets env: # Long-lived static keys, committed to VCS, recoverable from history AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY # build job - secrets leaked straight into the log build: script: - set -x ← traces EVERY expanded variable - echo "Using token $DEPLOY_TOKEN" ← explicit print defeats masking - curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com # Dockerfile - secret baked into image layers forever ARG NPM_TOKEN=npm_9f8a7b6c5d4e3f2a1b0c ENV NPM_TOKEN=$NPM_TOKEN ← visible via `docker history` RUN npm ci

✅ Secure Code

# .github/workflows/deploy.yml - no stored keys; OIDC mints a short-lived token name: deploy on: { push: { branches: [main] } } permissions: id-token: write # request a signed OIDC identity token contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/ci-deployer aws-region: us-east-1 # role minted per job, expires in minutes - name: Deploy to S3 run: aws s3 sync ./dist s3://prod-assets # Trust policy binds the role to THIS repo + branch, nothing else: # "sub": "repo:acme/app:ref:refs/heads/main" # build job - never print the secret; masking is only a backstop build: script: - set +x ← never trace around secrets - 'echo "::add-mask::$DEPLOY_TOKEN"' ← register derived secret - curl --silent -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com # Dockerfile - BuildKit secret mount: available for one step, stored in NO layer # syntax=docker/dockerfile:1 RUN --mount=type=secret,id=npm_token \ NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci

✓ Prevention Checklist

  • Keep secrets out of code, YAML, and Dockerfiles entirely
  • Prefer short-lived OIDC-federated credentials over static keys
  • Scope secrets per environment / pipeline / step, least privilege
  • Rotate on a schedule AND immediately on suspected exposure
  • Rotate first, then purge from history (deletion is not the fix)
  • Never echo secrets; keep set -x off around them
  • Register dynamically derived secrets with the log masker
  • Use BuildKit secret mounts; never bake secrets into images
  • Scan pre-commit and in CI across full history
  • No standing human access to production secrets
  • Use ephemeral, single-use runners
  • Isolate untrusted workloads from production secrets

🔍 Detection & Tools

Secret Scanning:

gitleaks TruffleHog git-secrets GitHub Push Protection detect-secrets

Secrets & Identity:

HashiCorp Vault OIDC (short-lived tokens) Docker BuildKit secrets git-filter-repo / BFG Cloud audit logs

How to Test:

  • Scan full Git history: trufflehog git file://. --only-verified
  • Grep pipeline logs for token patterns (gh[pousr]_...)
  • Inspect image layers: docker history --no-trunc
  • List cloud key age and scope for CI IAM users
  • Confirm a "removed" secret is unrecoverable from old commits
  • Verify a leaked test secret was rotated, not just deleted

🌍 Real-World Incident Classes

  • Cloud keys committed to public repos: Automated bots continuously scan public code-hosting for credential patterns and reuse hits within minutes - often before the developer finishes pushing follow-up commits.
  • Secrets recoverable from Git history: A team "removes" a committed secret but never rotates it; anyone who clones - including forks, mirrors, and former contributors - recovers the still-valid value.
  • Secrets leaked through build logs: Debug tracing or an echo pushes a secret past CI masking into retained, widely readable, forwarded logs.
  • Secrets baked into container images: Build-time ARGs persist in layers and travel to every registry, laptop, and environment the image reaches.

Note: exact leak counts vary by report and year - treat any single figure as illustrative. The durable takeaway is that secrets leak constantly, are found fast, and are reused faster.

📌 Quick Tips

  • DO NOT hardcode secrets in code or YAML
  • DO NOT treat deletion as remediation - rotate
  • DO NOT share one token across all pipelines
  • DO mint short-lived OIDC credentials
  • DO scope secrets per environment and step
  • DO scan repos, history, and logs continuously
  • DO use ephemeral runners and Vault JIT access

📜 Compliance

Related Standards:

  • SLSA Provenance & isolated build environments
  • NIST 800-53 IA-5 - Authenticator Management
  • CIS Software Supply Chain Benchmarks
  • PCI-DSS Req 8 / 3.5 - Key Management
  • SOC 2 CC6.1