The Problem with Environment Variables
Environment variables are the most common way to pass secrets into CI jobs, but they have significant weaknesses:
- Visible in process listings: On a shared machine,
ps auxor/proc/<pid>/environcan expose environment variables to other processes. - Logged accidentally: A misconfigured step that runs
envorprintenvdumps all secrets to the build log. - Inherited by child processes: Every subprocess spawned by the CI job inherits all environment variables, including secrets it doesn't need.
- No audit trail: There is no record of which jobs accessed which secrets, or when.
Secrets Managers: The Right Tool
A dedicated secrets manager stores secrets encrypted at rest, controls access with fine-grained policies, provides an audit log of every access, and supports automatic rotation.
HashiCorp Vault
Vault is the most widely deployed open-source secrets manager. It stores secrets encrypted, issues dynamic credentials (e.g., generating a new database password for each CI job that expires after the job), and logs every read. A CI job authenticates to it with a short-lived token (itself issued via OIDC or AppRole auth) and reads out just the specific secret value it needs.
AWS Secrets Manager
AWS Secrets Manager integrates natively with IAM. If your pipeline uses OIDC federation (as covered above), it can fetch secrets from Secrets Manager without any additional credentials — the OIDC-assumed role simply needs read permission on the specific secret, and the job queries it directly by name.
Masking Secrets in Logs
Even with a secrets manager, secrets sometimes end up in environment variables temporarily. CI platforms provide log masking — they scan build output and replace known secret values with ***.
In GitHub Actions, any value stored in ${{ secrets.* }} is automatically masked. For dynamically fetched secrets — like a value just pulled from Secrets Manager mid-job — you must register them manually with a special masking command before they're written anywhere else, so any later step that echoes them shows *** instead of the real value in the log.
Interviewers often ask: "How do you prevent secrets from appearing in CI logs?" The answer has layers: (1) use a secrets manager rather than hardcoding in environment variables, (2) use the CI platform's native masking (::add-mask:: in GitHub Actions), (3) audit your pipeline steps to ensure no step runs env, printenv, or echoes variables directly, and (4) configure log retention policies so old logs containing accidentally leaked secrets are purged. Mentioning all four layers signals a defense-in-depth mindset.