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.
