DEV Community

ke jia
ke jia

Posted on

Secrets in CI Logs: The Leak You Can't .gitignore

You can .gitignore a .env file. You cannot .gitignore your CI logs.

Here's a leak path that doesn't involve anyone committing anything:

  1. A developer adds console.log(process.env) to debug a flaky deploy.
  2. The log prints STRIPE_SECRET_KEY=sk_live_... to the job output.
  3. The build passes (or fails), and the log stays in the CI system for 90 days.
  4. Six months later, an intern with read access to the pipeline — or a misconfigured log retention policy shared with a vendor — sees it.

The secret never entered git. Every scanner you've ever run on the repository comes back clean. The leak lives in a log file.

Why this happens more than you'd think

Three structural reasons:

Debugging pressure. When a deploy is 40 minutes from failing in a way nobody understands, console.log(process.env) is the fastest way to see what the runtime actually got. It's a reasonable act in the moment.

Logs are treated as ephemeral. Git has history tooling, blame, and review. Logs are "just output." Nobody designs log retention the way they designs secret storage, so logs quietly accumulate years of environment dumps.

The environment is assumed to be the same everywhere. It isn't. process.env in a CI job contains that job's secrets — which in most setups means the real production ones, injected at pipeline time. The developer on their laptop can't see what the pipeline will print.

The controls, in order of effort

1. Redact at the source (free, immediate). Most CI systems can mask strings: GitHub Actions has set -x behavior you can control, and most providers support masking variable values in logs. Enable log masking for every injected secret. This doesn't prevent the console.log — it prevents the log from containing the value.

2. Don't log the whole environment (one code rule). The rule I put in my team's checklist: never log process.env as an object. Log the specific variable you're debugging, and even then, redact the middle:

// no
console.log(process.env)
// yes
console.log('stripe key present:', !!process.env.STRIPE_SECRET_KEY,
            'length:', process.env.STRIPE_SECRET_KEY?.length)
Enter fullscreen mode Exit fullscreen mode

The "presence + shape" check answers the debugging question ("did the secret arrive?") without printing the secret.

3. Bound log retention (policy, one afternoon). If logs live for 90 days by default, your leak window is 90 days. If you can set the sensitive pipeline's retention to 7 days, your leak window is 7 days. This is the control nobody thinks about until the incident.

Where the .env scanner fits (and doesn't)

This is the honest part: dotguard — the tool I use to scan .env files for exposed secrets before they get committed — does nothing about CI logs. It's a file scanner; it sees the repo, not the pipeline.

What it does do is close the adjacent hole that feeds this one. The most common reason a secret ends up in a CI log is that the secret was first committed (in a .env, a config file, a test fixture), and then the pipeline reads it from the repo instead of from a secret store. A scanner in CI that fails the build on exposed env files keeps the repo clean, which keeps the pipeline from ever having the value in a place where a stray log can reach it.

The full control set for this leak class:

Layer Control Catches
Repo dotguard in CI Secrets in env files before merge
Runtime Log masking Printed secrets in logs
Code No console.log(process.env) The dump itself
Policy Short log retention for sensitive jobs How long a printed secret survives

No single layer is sufficient. The scanner is the cheap one that removes the upstream cause, so the other three layers only have to deal with secrets that came from the proper secret store — which is where log masking and retention policies actually work as designed.

Check your pipeline logs this week. Grep for sk_live, AKIA, ghp_, postgres://. If you find something, rotate first, argue later.

npx @wuchunjie/dotguard
Enter fullscreen mode Exit fullscreen mode

More Tools

Tool What it does Command
scaffoldx-cli Production-ready project templates in seconds npx scaffoldx-cli
dotguard Scan .env files for exposed secrets npx @wuchunjie/dotguard
gitpulse Git repo analytics in your terminal npx @wuchunjie/gitpulse
snippetx Terminal code snippet manager npx @wuchunjie/snippetx

If these save you time, consider buying me a coffee. All tools are MIT-licensed, zero-dependency, and run fully offline.

Top comments (0)