CI/CD systems hold more powerful credentials than almost any other part of the estate, and they spread those credentials into code, logs, images and build hosts. OWASP lists this as CICD-SEC-6 Insufficient Credential Hygiene in the Top 10 CI/CD Security Risks. This guide explains what the risk covers, the five flaws OWASP names, how to move pipelines from stored static secrets to scoped, short-lived credentials, how to assess and measure progress, and how the risk maps to NIST, CIS, ISO and MITRE frameworks. It is written for platform engineers, DevSecOps teams and the owners of pipeline service identities, and goes deeper than the Top 10 CI/CD Security Risks overview.
Key takeaways
- It is a secrets problem with five faces. OWASP names credentials committed to source control, insecure use inside builds, secrets in container image layers, secrets printed to console output, and unrotated credentials.
- History outlives deletion. A secret removed from a branch still sits in commit history for anyone with read access, so the only real fix for a committed secret is revocation and rotation.
- Masking is a safety net, not a control. Both GitHub and GitLab state that log redaction is not guaranteed; transformed, encoded or structured values slip through.
- Federation beats rotation. OWASP prefers temporary credentials over static ones; OIDC federation from the pipeline to the cloud removes the stored secret altogether.
- Every pipeline credential is a non-human identity. It needs an owner, a scope, an expiry and an inventory entry, exactly like a service account. See the OWASP NHI Top 10 NHI7 Long-Lived Secrets Guide.
What it is and who it applies to
OWASP defines insufficient credential hygiene as an attacker's ability to obtain and use secrets and tokens spread throughout the pipeline, because of flaws in access controls around credentials, insecure secret management and overly permissive credentials. The entry stresses context: application runtime credentials, deployment credentials used by pipelines to reach production, and credentials engineers use in test environments, code and artefacts all live side by side, and each context offers a different way to leak them.
The risk applies to every organisation that builds or deploys software automatically, whether on hosted services (GitHub Actions, GitLab.com, CircleCI) or self-managed platforms (Jenkins, TeamCity, GitLab self-managed). Hosted services encrypt stored secrets, but they cannot stop a workflow from printing a secret, baking it into an image or granting it more scope than it needs. OWASP's own references show the range: thousands of credentials stored as environment variables stolen through the Codecov breach, secure variables injected into fork pull request builds in the Travis CI secrets exposure, and a GitHub token shown on a public Jenkins environment variables page in the Homebrew token exposure.
CICD-SEC-6 overlaps with its neighbours. OWASP CI/CD Top 10 CICD-SEC-5 Insufficient PBAC Guide decides which secrets a running step can reach; CICD-SEC-6 is about the secrets themselves: where they are stored, how long they live, what they can do and where they leak to.
Structure and key requirements
The OWASP entry has a definition, a description of five flaws, an impact statement, eight recommendations and six real-world references. The table pairs each flaw with the recommendation that addresses it and a practical control.
| OWASP flaw | What goes wrong | OWASP recommendation | Practical control |
|---|---|---|---|
| Credentials pushed to SCM | Secret exposed to everyone with read access, and stays in commit history after deletion | Detect secrets with an IDE plugin, scanning on every push, and periodic scans of the repository and its past commits | Pre-commit hooks, server-side push protection, scheduled full-history scans, revoke then rotate on every finding |
| Insecure use in build and deploy | Pipelines reach credentials they do not need, including from unreviewed code | Scope secrets so each pipeline and step reaches only what it requires; avoid sharing one set across contexts | Environment-scoped secrets with required reviewers, protected variables, per-job vault roles |
| Credentials in image layers | A build-time secret remains in a layer that anyone who pulls the image can read | Remove secrets from all artefacts: image layers, binaries, Helm charts | BuildKit secret mounts instead of build arguments, image scanning before push |
| Credentials printed to console | Clear-text secrets in build logs, then copied to log management platforms | Prevent secrets being printed in future builds and clean existing outputs | Register every derived value for masking, delete affected logs, rotate |
| Unrotated credentials | Deploy keys and tokens stay valid for years, known to a growing group of people and artefacts | Prefer temporary credentials; rotate static ones and detect stale ones; limit use to predetermined conditions such as source IP or identity | OIDC federation, expiry on every token, network and identity conditions on cloud roles |
The first OWASP recommendation sits above all of these: establish procedures to continuously map credentials across the engineering ecosystem, from code to deployment, and confirm each set has exactly the permissions its consumer needs. Without that map, none of the other controls can be shown to be complete.
Platform features that implement the recommendations
- GitHub Actions: organisation, repository and environment secrets (limits of 1,000, 100 and 100), encrypted client-side with libsodium sealed boxes. Organisation secrets can be limited to selected repositories, and environment secrets can require reviewer approval before a job receives them. Environment secrets are read only when a job referencing the environment starts. GitHub notes that anyone with write access to a repository can read all secrets configured in it.
- GitLab CI/CD: the Protect variable option limits a variable to pipelines on protected branches or tags. Visibility can be Visible, Masked or Masked and hidden; masked values must be a single line of at least 8 characters, and hidden values cannot be revealed in the UI after saving. By default, pipelines from forks cannot access the parent project's variables.
- Jenkins: credentials (secret text, username and password, secret file, SSH key, certificate) are stored encrypted on the controller and bound into builds through the Credentials Binding plugin. Use the narrowest scope (Global for pipeline items, System for controller functions) and restrict who holds Credentials > Create through Matrix-based security.
- Container builds: Docker's documentation warns that build arguments and environment variables persist in the final image. Use
docker build --secretwithRUN --mount=type=secret, which mounts the secret at/run/secrets/<id>for that instruction only, and--sshfor Git access during builds.
Identity and NHI requirements
Every credential in a pipeline authenticates a machine, a bot or a workflow, which makes CICD-SEC-6 a non-human identity problem as much as a secrets problem. The same lifecycle questions apply: who owns this credential, what can it do, where is it used, when does it expire and who will notice if it is abused.
| Credential type | Typical CI/CD use | Preferred replacement or control |
|---|---|---|
| Static cloud access keys | Deploying infrastructure and artefacts | OIDC federation to a cloud role; GitHub states the cloud token is valid for a single job and then expires. See the Workload Identity Federation Guide. |
| Personal access tokens | Cross-repository checkout, release automation, bots | GitHub App or fine-grained token with minimum permissions and expiry; GitHub recommends Apps because they are not tied to a user who may leave |
| Deploy keys | Read or write access to one repository | Read-only by default, one key per repository and purpose, recorded rotation date |
| Package registry tokens | Publishing npm, PyPI or container images | Trusted publishing through OIDC where the registry supports it; otherwise scoped, expiring tokens held in a protected environment |
| Signing keys | Signing releases and artefacts | HSM or managed signing service, never a general CI variable |
| Platform job tokens | GITHUB_TOKEN, CI_JOB_TOKEN | Set default permissions to read-only and grant write per job |
- Federation trust policy: the job needs the
id-token: writepermission to request the OIDC token. The cloud role must then check the subject claim, for examplerepo:octo-org/octo-repo:environment:prod, so that only the intended repository, branch or environment can assume it. A wildcard subject turns federation into a shared credential. - Conditions on static credentials: where a static key must remain, OWASP asks for use to be limited to predetermined conditions such as a source IP range or identity; in AWS that means policy conditions such as
aws:SourceIporaws:SourceVpc. - No shared credentials: OWASP warns that sharing one credential across contexts undermines least privilege and accountability. One credential, one pipeline, one environment. See the OWASP NHI Top 10 NHI9 NHI Reuse Guide.
- Leak response as an identity process: a leaked pipeline secret is handled like any exposed NHI credential: revoke, rotate, review usage logs for the exposure window. See the OWASP NHI Top 10 NHI2 Secret Leakage Guide.
For the wider picture of how pipelines authenticate, see the CI/CD Pipeline Identity Security Guide.
Implementation steps
- Inventory every pipeline credential. Export secrets and variables from each CI platform (organisation, repository, environment, group and folder level), list deploy keys, bot accounts and tokens, and record owner, consumer, scope, creation date and last use. This is the continuous mapping OWASP asks for.
- Stop new leaks at the source. Turn on push protection or a pre-receive secret scanner for all repositories, add pre-commit scanning for developers, and enable GitLab's Prevent pushing secret files push rule where applicable. Run in audit mode for two weeks to tune false positives before blocking. See the Git Secret Scanning Guide.
- Scan history and artefacts once, fully. Scan every repository's full commit history, existing build logs, container images in registries and Helm charts. Treat each live finding as an incident: revoke, rotate, then remove from history if required.
- Replace static cloud credentials with federation. Start with production deployment pipelines. Create one role per pipeline and environment with a strict subject condition, run the federated and static paths in parallel for one release, then delete the static key. Roll back by re-enabling the static key from the vault, never by widening the trust policy.
- Scope what remains. Move production secrets into environments with required reviewers or protected variables, restrict organisation secrets to named repositories, and remove secrets from jobs that run untrusted code such as fork pull requests and dependency installation.
- Fix logging behaviour. Register every derived value (JWTs, Base64 forms, URL-encoded forms) with
::add-mask::or the platform equivalent, never store structured JSON or YAML blobs as a single secret, disable debug options that print environment data, and delete any log that contained a secret before rotating it. - Harden image builds. Replace
ARGandENVsecrets with secret mounts, add an image secret scan as a gate before push, and rebuild any published image found to contain credentials. - Set expiry and rotation for every static credential. Apply a maximum lifetime (for example 90 days for tokens with write scope), automate rotation through the secrets manager, and alert on stale credentials unused for 30 days. See the Secrets Rotation Runbook Template.
Choosing an approach
- Small teams on a hosted CI service: native secrets plus environment protection, push protection and OIDC to one cloud provider cover most of the risk with no extra infrastructure.
- Large or multi-platform estates: a central secrets manager or vault issuing dynamic, per-job credentials gives one inventory, one audit trail and consistent rotation across Jenkins, GitLab and GitHub. See the Secrets Management Guide.
- Third-party targets without federation: keep the static secret, but scope it to one environment, add network conditions on the target side, and rotate on a schedule.
Evidence and assessment
Assessors and internal audit should be able to see, for any pipeline, which credentials it holds, why, and when they last changed. Useful evidence includes the credential inventory export, secret scanning and push protection reports with bypass records, cloud trust policy definitions, and rotation logs from the secrets manager.
| Metric | How to measure | Target direction |
|---|---|---|
| Share of deployment pipelines using federated or dynamic credentials | Pipelines with OIDC or vault dynamic secrets divided by all deploying pipelines | Rising towards 100 percent for cloud targets |
| Static credentials older than policy | Inventory entries past their maximum lifetime | Zero |
| Mean time to revoke a leaked secret | From scanner alert to confirmed revocation | Hours, not days |
| Push protection bypasses | Bypass events per month and the share with reason "I'll fix it later" still open | Falling, with every open bypass owned |
| Secrets shared across pipelines | Credentials referenced by more than one pipeline or environment | Falling |
| Images and logs with secrets | Findings from registry and log scans per month | Zero after the initial clean-up |
Detection: monitor audit events for secret changes (GitHub logs org.update_actions_secret for organisation secrets), cloud authentication events for CI roles from unexpected subjects or networks, use of CI credentials outside pipeline run windows, and new or unusual consumers of package registry and signing credentials. The OWASP CI/CD Top 10 CICD-SEC-10 Insufficient Logging and Visibility Guide covers the logging side in depth.
Mapping to other frameworks
| Framework | Reference | Relevance to CICD-SEC-6 |
|---|---|---|
| NIST CSF 2.0 | PR.AA-01, PR.AA-05, PR.DS-01 | Credentials for services managed; access permissions defined and reviewed; data at rest protected |
| NIST SP 800-53 Rev. 5 | IA-5, IA-5(7), SC-12, SC-28, AC-6 | Authenticator management; no embedded unencrypted static authenticators; key management; protection at rest; least privilege |
| CIS Controls v8.1 | Control 3 Data Protection, Control 5 Account Management, Control 6 Access Control Management, Control 16 Application Software Security | Secret handling, service account lifecycle, least privilege, secure development pipeline |
| ISO/IEC 27001:2022 Annex A | 5.17 Authentication information, 8.4 Access to source code, 8.24 Use of cryptography, 8.25 Secure development life cycle | Managing secrets, protecting repositories, keys, secure build practice |
| MITRE ATT&CK | T1552.001 Credentials In Files, T1552.004 Private Keys, T1213.003 Code Repositories, T1078.004 Cloud Accounts, T1528 Steal Application Access Token | How attackers find and use leaked pipeline credentials |
| MITRE D3FEND | D3-CRO Credential Rotation, D3-CR Credential Revocation, D3-CTS Credential Transmission Scoping | Defensive techniques that limit the life and reach of a leaked credential |
| OWASP NHI Top 10 | NHI2 Secret Leakage, NHI7 Long-Lived Secrets, NHI9 NHI Reuse | The same failure seen from the identity side |
Common pitfalls
- Deleting the commit instead of rotating the secret. OWASP notes the credential remains in history; clones, forks and caches already hold it. Revocation is the fix, history rewriting is housekeeping. The Uber 2016 breach began with an AWS key committed to a private GitHub repository.
- Trusting masking. GitHub states that the runner only redacts secrets used in the current job and that transformed values may escape; GitLab notes that shell escaping defeats masking. A JWT minted from a private key and never registered for masking will print in clear.
- Secrets as environment variables for every step. The Codecov breach worked because a coverage uploader ran in jobs that held cloud keys, tokens and signing material as environment variables. Scope secrets to the step that needs them.
- Secrets reachable by untrusted builds. The Travis CI exposure gave fork pull request builds access to public projects' secrets for about a week. Fork and dependency jobs should run with no secrets at all.
- CI configuration pages as a leak path. In the Homebrew token exposure, a public Jenkins environment variables page revealed a bot token with push access to core repositories. Check what your CI UI shows to anonymous and read-only users.
- "If it isn't broken, don't fix it" deploy keys. OWASP highlights deploy keys left unrotated for years. If rotation would break a pipeline, that pipeline is undocumented, which is a finding in itself.
Practitioner checklist
- Maintain an inventory of every pipeline credential with owner, scope, consumer, creation date and last use.
- Enable push protection or pre-receive secret scanning on all repositories and review every bypass.
- Scan full commit history, build logs, container images and Helm charts, and revoke every live finding.
- Replace static cloud keys in deployment pipelines with OIDC federation and strict subject conditions.
- Put production secrets behind environments with required reviewers or protected variables.
- Run fork, pull request and dependency installation jobs with no secrets.
- Register derived values for masking, avoid structured secrets, and delete any log that exposed a secret.
- Use BuildKit secret mounts, never build arguments, for credentials needed during image builds.
- Set maximum lifetimes for static tokens and alert on credentials unused for 30 days.
- Keep signing keys in an HSM or managed signing service, outside general CI variables.
Standards and references
- OWASP: CICD-SEC-6 Insufficient Credential Hygiene
- OWASP: Top 10 CI/CD Security Risks
- GitHub Docs: Secure use reference for GitHub Actions
- GitHub Docs: OpenID Connect
- GitHub Docs: About push protection
- GitLab Docs: GitLab CI/CD variables
- Docker Docs: Build secrets
- Jenkins: Using credentials
Related NHI Mgmt Group resources: Top 10 CI/CD Security Risks · OWASP NHI Top 10 NHI2 Secret Leakage Guide · OWASP NHI Top 10 NHI7 Long-Lived Secrets Guide · Secrets Management Guide · OWASP CI/CD Top 10 CICD-SEC-7 Insecure System Configuration Guide