Secrets in Pipelines

By Davy Rogers

CI/CD pipelines need secrets to deploy. Those secrets are what attackers extract first.

How secrets leak

Build logs: Command echoes, debug prints, verbose flags.

Environment dumps: env | sort exposes all.

Artefact embedding: Docker images with .env, frontend builds with process.env.

PR exploitation: PRs trigger CI with secrets.

Cache poisoning: Low-privilege job injects script into shared cache.

Providing secrets

Use platform secret stores: GitHub secrets, GitLab CI variables, Jenkins credentials.

Scope narrowly:

# GitHub - production secrets only in production environment
jobs:
  deploy:
    environment: production

Inject only where needed:

- name: Deploy
  env:
    DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

OIDC federation

No stored secrets. Short-lived tokens.

permissions:
  id-token: write

- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789:role/deploy

Preventing log leaks

Mask derived values:

TOKEN=$(echo ${{ secrets.ENCODED }} | base64 -d)
echo "::add-mask::$TOKEN"

Avoid: set -x, curl -v, DEBUG=*

Docker secrets

# BAD - persists in layer
ARG DB_PASSWORD

# GOOD - BuildKit mount
RUN --mount=type=secret,id=db_password cat /run/secrets/db_password

Rotation

API keys: 90 days. Deploy creds: 30-90 days. OIDC: no rotation needed.

If leaked: Revoke immediately, rotate, audit logs, find root cause.

The takeaway

Platform secret stores. Narrow scoping. Prefer OIDC. Mask in logs. Never embed in layers. Revoke immediately when compromised.

Want a professional to look at it?Get an AppSec Health Check.