Detecting Credential Leaks in CI/CD Pipelines
The article explores how credential leaks can occur at different stages of CI/CD pipelines, from source code and build logs to Docker images and deployment artifacts. It shows how automated secret scanning, AI-assisted alert analysis, and secure credential management can help DevOps teams detect threats earlier, reduce security risks, and prevent compromised credentials from reaching production.

Tomasz Olszowy
•
0
min.
It only took a few lines of code and a small tweak to the pipeline. All tests pass, the build succeeds, and the application gets deployed.
The pipeline works. Deployment complete.
Except we may have just exposed a production token to everyone with access to the logs.
Not a great day for security.
GitHub Actions automatically masks many secrets, but not every value retrieved during pipeline execution is covered by that protection. If a token isn't recognised or properly masked, it can end up directly in the logs. And those logs aren't always accessible only to the people responsible for deployment.
Sometimes all it takes is enabling additional logging while investigating an issue and forgetting to disable it afterwards. The pipeline keeps working, but it also records information that should never have appeared in the logs.
Just like that, a pipeline designed to make deployments safer has become another place where credentials can slip through unnoticed.
Secrets don't leak only through Git
When we talk about credential leaks, the first thing that usually comes to mind is a password or token accidentally committed to a repository.
For example:
That's an obvious case. Tools such as Gitleaks, TruffleHog and GitHub Secret Scanning can detect many situations like this.
But take a look at any reasonably large pipeline.
A token for the container registry here, a cloud access key there, and somewhere further down, a password for the test environment.
Before long, we've collected quite a few secrets that somehow need to be managed.
The trouble starts when we lose track of where those secrets end up. A token might accidentally appear in the logs, an SSH key in a Docker image, or a .env file in the build artifacts. And someone won't necessarily notice straight away.
That's why scanning the repository, although necessary, doesn't solve the whole problem.
One pipeline, several places to check
Take a typical pipeline. A pull request, tests, building a Docker image, publishing it to a registry, and deployment.
Nothing unusual.
And yet, at every one of these stages, we could accidentally expose credentials.
At the beginning of the pipeline, we can scan the code, configuration files and commit history.
While the pipeline is running, we need to pay attention to what gets written to the logs, especially when scripts pass credentials between processes.
Once the build finishes, we should also inspect its output. Not just whether the Docker image works correctly, but whether it contains information that should never leave the build environment.
We won't catch everything with a single scanner. But we don't have to rebuild the entire pipeline either. I'd begin with the repository and see what the scanner picks up. Logs and build artifacts can come next, once the first checks are working properly.
For a GitHub Actions workflow, the obvious place to begin is a pull request. That's where I'd add the first Gitleaks check, before anyone merges the changes.
If the repository is part of a GitHub organisation, we'll also need to configure a Gitleaks licence for this action.
This setup runs Gitleaks on pull requests, checking the repository for passwords, tokens and API keys that may have been committed by mistake. When the scan reports a leak, the job can fail. If the check is required by the repository's branch protection rules, the pull request cannot be merged until the problem is resolved.
With branch protection configured correctly, this allows us to stop a change before it reaches the repository's main branch.
Better to hold up a pull request for a few minutes than spend hours later trying to figure out how long an access key has been sitting in Git history.
There is one problem, though. A repository scanner won't detect a secret that appears only during pipeline execution, such as a value returned by an external API.
The repository is only one part of the picture. We also need to look at what the pipeline writes to its logs and what ends up in the build artifacts.
Tokens fetched at runtime deserve particular attention. In GitHub Actions, we can mask their values with the ::add-mask:: workflow command, provided we register them before anything prints them. A clean repository scan won't help if a script later dumps a live token into the build log.
Not every suspicious string is a secret
Let's say that during the first week after introducing a scanner, we receive 37 alerts. After checking them, we discover that 32 refer to example tokens in documentation, three are related to test data, and only two require further investigation.
If similar alerts keep appearing every day, it's easy to fall into a routine and overlook something that actually matters.
Of course, we could investigate every alert manually, but as the number of findings grows, so does the time needed to review them.
And this is where AI can help.
A scanner identifies a suspicious string, while AI can help analyse its context: which file it appeared in, whether it was added in the latest commit, whether a deployment script uses it, and whether a similar case has already been investigated.
That doesn't give us absolute certainty, but it can help us identify alerts that deserve a closer look.
The final decision should still be made by a person or follow established security rules.
We also need to be careful about what information we send to the AI model. Sending complete values of potentially sensitive credentials to an external service could create another security problem.
Which is exactly what we're trying to avoid.
Detecting a leak is only half the job
Let's assume the scanner finds a genuine access token for the production environment.
What next?
Remove it from the code?
Removing the token from the file won't make the problem disappear. It may already be in someone's hands, and as long as it's still valid, they can use it. So before moving on, we need to disable the old token, issue a replacement and check where else it might have ended up.
I'd also check the Git history, pipeline logs and any signs that someone used the credential before we caught the leak.
We can automate part of this process. The scanner detects the issue, the system creates a ticket, notifies the responsible person and starts the rotation procedure.
That doesn't mean every detected token should immediately be revoked automatically, without checking what it's used for or preparing a safe replacement.
Disabling a critical service account without having a replacement ready could bring production to a halt.
And the goal is to improve security, not create another incident.
What if we simply had fewer secrets?
We've talked quite a bit about finding leaked credentials. But there's another question worth asking: do all those credentials really need to be stored in the pipeline?
Think about an AWS access key stored as a GitHub Actions secret. It might be valid for six months, even though the deployment only takes a few minutes. For the rest of that time, the key is still there, waiting to be used.
I'd prefer not to store that key in GitHub at all. OIDC gives us another option. When a deployment runs, GitHub Actions can use its identity token to request temporary access to AWS. The IAM role's trust policy determines whether that request is allowed. Once AWS issues the credentials, they remain usable only until they expire. No permanent AWS access key needs to be stored in the repository settings.
Temporary credentials still need proper access controls. An overly permissive IAM role could give a workflow much more access than it actually needs, even if the credentials expire after a short time.
The easiest secret leak to prevent is the one involving a secret we never needed to store.
A green pipeline doesn't always mean a secure deployment
Consider what happens when we add secret scanning to the deployment workflow.
A developer opens a pull request. The tests pass, and everything looks ready to merge. Then the secret scanner flags an access token in one of the changed files.
The scanner blocks the workflow. A closer look at the flagged file reveals that the token is real. It's not a dummy value left behind in a test or an example from the documentation.
The token is still active, so simply removing it from the commit won't do. It has to be revoked and replaced. There's also the question of whether it appeared in earlier commits or ended up somewhere outside the repository.
The release will have to wait.
Nobody likes finding this kind of problem just before deployment, especially when the tests have already passed and the team is ready to ship. Still, dealing with a leaked token now is a much better option than investigating suspicious activity in production a few days later.
The tests were green. The deployment wasn't.
And in this case, stopping it was the right outcome.


