Preventing secret leakage in CI/CD environments
Originally published on the ClearPath Security site: https://clearpathsecurity.co.uk/preventing-secret-leakage-in-ci-cd-environments/ Secrets leakage in CI/CD environments is rarely caused by one dramatic failure. More
Originally published on the ClearPath Security site: https://clearpathsecurity.co.uk/preventing-secret-leakage-in-ci-cd-environments/
Secrets leakage in CI/CD environments is rarely caused by one dramatic failure. More often, it is the result of small design choices that add up: a token stored in the wrong place, a verbose build log, a shared runner with too much access, or a deployment job that can see credentials it does not need. For UK SMEs, the challenge is not only preventing exposure, but also reducing the number of places secrets exist and the time they remain valid.
This matters because CI/CD systems sit close to source code, build artefacts, deployment targets, package registries, cloud APIs, and signing services. If an attacker reaches the pipeline, they may not need to exploit the application itself. They may simply look for credentials that let them move laterally, publish malicious artefacts, or access production systems. That is why secret hygiene belongs in secure software development, not as a separate afterthought. It also sits naturally alongside broader software supply chain assurance, as covered in our software supply chain assurance controls guidance.
Key takeaways
- Treat CI/CD secrets as a supply chain risk, not just a configuration issue.
- Use short-lived, scoped credentials and keep production access separate from build and test jobs.
- Scan code, configuration, logs, and artefacts for accidental secret exposure.
- Harden runners and isolate untrusted workflows so one compromised job cannot reach everything.
- Build detection and rotation into the process so leaked secrets can be contained quickly.
Why secrets leak in CI/CD pipelines
CI/CD systems create many opportunities for accidental disclosure because they are designed to move quickly and automate repetitive work. The same speed that helps teams ship changes can also spread sensitive values across repositories, logs, artefacts, caches, and environment variables.
Common leak paths in source control, logs, artefacts, and environment variables
The most obvious leak path is source control. Hard-coded credentials, API keys, private certificates, and deployment tokens sometimes end up in application code, test fixtures, sample configuration files, or infrastructure-as-code templates. Even when the secret is later removed, it may still exist in commit history, forks, or mirrored repositories.
Logs are another common source of exposure. Build scripts often echo commands, environment variables, or tool output. A single debug flag can expose a bearer token, a cloud access key, or a password passed to a command-line tool. Artefacts can also leak secrets if they package configuration files, generated manifests, or test outputs that were never meant to leave the build environment.
Environment variables are useful, but they are not a magic shield. They can be inherited by child processes, printed by accident, captured in crash dumps, or exposed through diagnostic tooling. In some systems, they are also visible to anyone with access to the job definition or runtime metadata. The issue is not that environment variables are always unsafe. The issue is that they are often treated as if they were invisible.
How pipeline design choices create unnecessary exposure
Pipeline design can widen the blast radius of a single secret. Common examples include reusing the same credential across build, test, and production deployment stages; giving every job access to every secret; and using long-lived tokens where a short-lived token would do. These choices are convenient, but they make compromise more valuable.
Another common pattern is centralising too much trust in a single runner or service account. If that identity can read source, write artefacts, publish packages, and deploy to production, then any compromise of the pipeline becomes a high-value event. A better approach is to split duties so each stage has only the access it genuinely needs.
Threat model for CI/CD secret exposure
A useful threat model starts with a simple question: who can reach the pipeline, and what can they do if they obtain a secret? In practice, the answer includes developers, release engineers, build agents, administrators, third-party integrations, and sometimes external contributors through pull requests or shared repositories.
Who can access secrets in a modern delivery stack
Access is not limited to humans. CI/CD platforms often expose secrets to jobs, runners, service accounts, deployment tools, package managers, scanning tools, and cloud identity providers. Each integration adds another trust boundary. If a tool can read a secret, it may also be able to print it, forward it, cache it, or store it in a temporary file.
Self-hosted runners deserve particular attention because they often sit inside the same network as internal systems. If one is compromised, the attacker may inherit access to secrets, cached credentials, mounted volumes, or local metadata endpoints. That risk is higher when runners are shared across projects or reused for untrusted workloads.
Typical attacker objectives and abuse paths
Attackers usually want one of four things: access to production systems, the ability to publish malicious code or artefacts, access to customer or internal data, or a foothold for further movement. A leaked cloud token might let them enumerate storage buckets or create new compute resources. A signing key could let them produce artefacts that look legitimate. A package registry token could allow them to tamper with releases. A deployment secret could let them alter infrastructure or application configuration.
These are not theoretical concerns. In many environments, the pipeline is the shortest path from a low-privilege foothold to a trusted release channel. That is why secret exposure should be treated as a supply chain issue as well as an access control issue.
Inventory the secrets your pipeline actually uses
Before improving controls, identify what the pipeline really needs. Many teams discover they are protecting a long list of credentials that no one has documented properly. A practical inventory makes it easier to remove unnecessary secrets and replace others with safer alternatives.
Build, test, deploy, signing, and third-party integration credentials
Start by grouping secrets by function. Common categories include source control access, build system tokens, test environment credentials, deployment credentials, package registry tokens, cloud provider keys, container registry credentials, code signing keys, and secrets used by scanning or monitoring tools. If a secret is used only for one stage, it should not be available to the rest of the pipeline.
It is also worth identifying secrets that are not obvious at first glance. Examples include webhook signing keys, licence server credentials, database connection strings used for integration tests, and credentials embedded in release automation. These often persist because they are treated as operational details rather than security-sensitive values.
Separating long-lived secrets from short-lived tokens
Where possible, replace long-lived secrets with short-lived tokens issued just in time. Short-lived credentials reduce the window of abuse and make rotation less painful. For example, cloud workloads can often use federated identity or workload identity rather than static access keys. Similarly, deployment jobs can use temporary credentials scoped to a specific environment and time period.
Long-lived secrets are sometimes unavoidable, especially for legacy systems or third-party services. In those cases, document the owner, purpose, scope, rotation interval, and revocation path. If no one can answer those questions quickly, the secret is probably too widely trusted.
Store and retrieve secrets safely
The safest approach is to keep secrets out of code and out of general-purpose configuration wherever possible. A dedicated secrets manager or vault gives you a central place to control access, audit retrieval, and support rotation. It also helps avoid the sprawl that comes from copying the same value into multiple systems.
Using a dedicated secrets manager or vault
A vault should support role-based access control, audit logging, and integration with your identity provider. The pipeline should retrieve secrets at runtime rather than storing them in the repository or embedding them in build definitions. Where supported, use dynamic secrets or short-lived leases so the secret expires automatically after use.
For cloud-native environments, this may mean using managed secret stores, workload identities, and federated authentication rather than static keys. For containerised workloads, inject secrets at runtime through secure mounts or platform-native secret references rather than baking them into images. The important point is that the pipeline should ask for a secret when it needs it, not keep a permanent copy lying around.
Avoiding plaintext variables, repository secrets sprawl, and shared credentials
Repository-level secret stores are convenient, but they can encourage overuse. If every project stores its own copy of the same credential, rotation becomes inconsistent and auditability suffers. Shared credentials are even worse because they make it hard to tell which pipeline, job, or team used a secret and when.
Prefer scoped secrets with clear ownership. Use separate credentials for development, test, staging, and production. Avoid plaintext variables in pipeline definitions, shell scripts, and configuration files. If a value must be passed into a job, ensure it is injected securely and removed from the environment as soon as practical.
Reduce exposure in pipeline design
Good secret hygiene is not just about storage. It is also about limiting where a secret can travel once a job starts. Least privilege should apply to runners, jobs, service accounts, and deployment targets.
Least privilege for runners, jobs, and service accounts
Each pipeline job should receive only the permissions it needs. A build job may need read access to source and package dependencies, but not deployment rights. A test job may need access to a disposable database, but not production credentials. A release job may need signing and publishing permissions, but not broad administrative access.
Apply the same principle to service accounts. Split identities by function and environment. If a job only needs to read from a package registry, do not give it write access. If a deployment tool only needs to update one namespace or one subscription, scope it accordingly. This is the practical application of least privilege, and it materially reduces the impact of a leaked token.
Environment isolation and secret scoping by branch, stage, and deployment target
Secrets should be scoped by branch, stage, and target environment where the platform allows it. For example, pull request validation should not have access to production secrets. Feature branches should not inherit deployment credentials. Staging should use separate secrets from production, even if the underlying service is similar.
This is especially important where untrusted code can influence the pipeline. If a pull request from a fork can trigger a workflow, the workflow must be designed so it cannot access sensitive values or exfiltrate them through logs, artefacts, or network calls. The article on hardening GitHub Actions against pwn requests and token theft is a useful companion if your team uses GitHub-based delivery.
Prevent accidental disclosure in code and configuration
Prevention works best when it is built into the developer workflow. The earlier you catch a secret, the less likely it is to spread into logs, artefacts, or downstream systems.
Pre-commit and repository scanning for hard-coded secrets
Use pre-commit hooks and repository scanning to detect secrets before they are pushed. Tools such as Gitleaks, TruffleHog, and similar scanners can identify common token formats, private keys, and high-entropy strings. Run them locally, in pull request checks, and in scheduled scans of the full repository history.
For teams that already use pre-commit, a simple pattern is to block commits that contain known secret formats and to fail the pipeline if a scan finds a match. The goal is not perfect detection. The goal is to catch the common mistakes early enough that they never reach the main branch. Our Gitleaks in pre-commit hooks guide covers this pattern in more detail.
Safer handling of configuration files, templates, and IaC
Configuration files and infrastructure-as-code templates often become secret carriers by accident. Use placeholders, references, or secret identifiers rather than real values. Ensure sample files cannot be mistaken for production configuration. If a template must include a sensitive field name, make it obvious that the value is injected at deploy time.
Be careful with generated files too. Build systems sometimes create manifests, lock files, or environment snapshots that include sensitive values. Review what is written to the workspace, what is copied into artefacts, and what is retained in caches. If a file is not needed after the job completes, delete it explicitly.
Protect secrets during pipeline execution
Even well-stored secrets can leak during execution if the pipeline is noisy or poorly controlled. Log hygiene matters because many incidents begin with a value that was never intended to be visible outside the job.
Masking, redaction, and log hygiene
Use the platformβs masking features, but do not rely on them alone. Masking works best for exact matches and may fail if a secret is transformed, split, base64-encoded, or embedded in structured output. Redaction should therefore be paired with careful script design and minimal logging.
Avoid printing environment variables, command traces, or full request payloads unless there is a clear operational need. Disable shell tracing by default, and only enable it temporarily in controlled troubleshooting sessions. If a tool supports structured logging, configure it to suppress sensitive fields. If the tool cannot do that reliably, consider wrapping it or replacing it.
Controlling debug output, artefact contents, and command traces
Debug output is useful, but it should be treated as sensitive. Many teams forget that a debug build or verbose deployment can expose more than the application code itself. Limit debug mode to short-lived troubleshooting windows and ensure the resulting logs are retained and accessed appropriately.
Artefact contents should also be reviewed. Do not publish workspace snapshots, temporary files, or test reports that may contain credentials. Command traces can be especially risky when they include inline secrets passed as arguments. Where possible, pass secrets through secure file handles, runtime injection, or platform-native secret references rather than command-line parameters.
Harden runners and build infrastructure
The runner is the execution boundary, so its security posture matters. A compromised runner can expose secrets from multiple jobs, especially if it is persistent or shared.
Ephemeral runners, isolation boundaries, and token lifetime controls
Ephemeral runners are preferable because they reduce the chance that secrets, caches, or temporary files survive between jobs. If a runner is destroyed after each job, the attacker has less opportunity to harvest residual data. Pair this with short token lifetimes so that any stolen credential expires quickly.
Isolation boundaries should be explicit. Use separate runners for trusted and untrusted workloads, and avoid mixing production deployments with general build activity. Where possible, run jobs in containers or virtual machines with restricted file system access, limited network egress, and no unnecessary host mounts.
Risks from self-hosted runners, shared hosts, and untrusted pull requests
Self-hosted runners can be appropriate, but they need stronger controls than many teams initially expect. Restrict administrative access, patch them promptly, monitor their integrity, and avoid reusing them across unrelated projects. If they must handle sensitive jobs, treat them as part of your production trust boundary.
Untrusted pull requests are another common exposure point. A workflow that checks out and executes contributor-controlled code should not have access to secrets. If a job must validate external contributions, split it into a low-trust validation stage and a separate trusted release stage. This separation is one of the simplest ways to reduce secret theft opportunities.
Secure third-party integrations and supply chain touchpoints
Modern pipelines depend on many external services. Each one may require a credential, and each credential deserves the same care as a production password.
Credential handling for package registries, cloud APIs, and signing services
Package registry tokens should be scoped to the minimum required actions, such as read-only access for dependency installation or write access only for release jobs. Cloud API credentials should be tied to a specific workload identity, subscription, project, or namespace. Signing services should use tightly controlled keys or managed signing workflows rather than broad administrator credentials.
If your team signs artefacts, keep signing material separate from general build credentials. Signing should be a dedicated step with its own identity, its own audit trail, and its own approval path where appropriate. That separation reduces the chance that a build compromise automatically becomes a release compromise.
Managing secrets used by dependency, scanning, and release tooling
Security and delivery tools often need access to registries, APIs, or internal services. Do not assume these tools are harmless because they are defensive. They still run with privileges and can leak secrets if misconfigured. Review what each tool can read, write, and export.
Dependency tooling, scanning tools, and release automation should receive separate credentials where possible. If one tool is compromised or misconfigured, the blast radius should be limited. This is also a good place to review whether a tool can use federated identity or temporary access instead of a static token.
Detect leakage early and respond quickly
No control is perfect, so detection and response need to be part of the design. The faster you spot a leaked secret, the less time an attacker has to use it.
Alerting on secret patterns, unusual access, and token misuse
Monitor for secret patterns in logs, repository events, and artefact storage. Alert on unusual access to secret stores, unexpected token usage, and access from unfamiliar runners or IP ranges. If your CI/CD platform supports audit logs, send them to your SIEM and correlate them with deployment activity, repository events, and cloud access logs.
Look for signs that a token is being used outside its normal pattern. Examples include access at unusual times, repeated failed authentication attempts, access from a different geography, or use against resources the pipeline does not normally touch. These indicators are not proof of compromise, but they are useful triggers for investigation.
Revocation, rotation, and post-exposure containment steps
If you suspect a secret has leaked, revoke or rotate it promptly. Then check where else the secret was used, whether it was embedded in artefacts or logs, and whether any downstream systems still trust it. Rotation is not just a replacement exercise. It is also a containment exercise.
Review the surrounding pipeline design after the immediate issue is handled. Ask why the secret was accessible in the first place, whether the same pattern exists elsewhere, and whether the exposure could have been prevented with tighter scoping or short-lived credentials. This is where incident handling feeds back into engineering improvement.
A practical CI/CD secrets hygiene checklist
For most UK SMEs, the best starting point is a small set of controls that can be implemented without redesigning the entire delivery stack.
Minimum controls for small engineering teams:
- Keep secrets out of source control and scan repositories and pre-commit hooks for hard-coded credentials.
- Use a dedicated secrets manager or vault rather than plaintext variables or copied repository secrets.
- Scope secrets by environment, job, and branch so untrusted workflows cannot reach production credentials.
- Prefer short-lived tokens and federated identity over long-lived static keys.
- Mask sensitive values in logs, disable debug output by default, and review artefacts for accidental disclosure.
- Use ephemeral or tightly isolated runners for sensitive jobs and separate trusted from untrusted workloads.
- Send audit logs to a central monitoring platform and alert on unusual secret access or token use.
Priority improvements for higher-risk pipelines:
- Split build, test, signing, and deployment identities so no single credential can do everything.
- Separate production secrets from lower environments and require stronger controls for release stages.
- Replace static cloud keys with workload identity or federated access where supported.
- Review third-party tooling permissions and remove any access that is not essential.
- Test revocation and rotation procedures so a leaked secret can be contained quickly.
For teams that want to measure maturity rather than just apply ad hoc fixes, OWASP SAMM can help structure the work across governance, design, implementation, verification, and operations. It is a useful way to turn secret hygiene into a repeatable engineering practice rather than a one-off clean-up exercise.
Preventing secret leakage in CI/CD environments is mostly about reducing trust, reducing exposure time, and reducing the number of systems that can see a credential. If you get those three things right, the rest becomes much easier to manage. If you would like help reviewing your delivery pipeline, secrets handling, or release architecture, Speak to a consultant.
Frequently asked questions
What is the safest way to pass secrets into a CI/CD pipeline?
The safest pattern is to avoid storing secrets in code or plaintext pipeline variables and retrieve them at runtime from a dedicated secrets manager using short-lived, scoped identity. Where possible, use federated identity or workload identity rather than static keys, and scope access to the specific job and environment that needs it.
How do you know if a secret has leaked from a pipeline?
Look for direct evidence such as a secret appearing in logs, artefacts, repository history, or secret scanning alerts. Also watch for indirect signs such as unusual token use, access from unexpected runners or locations, failed authentication attempts, or activity against resources the pipeline does not normally touch. If you suspect exposure, revoke or rotate the secret and review where it may have been copied.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.