highCWE-532OWASP A09:2021

Secrets Leaked in CI Logs

Printing or echoing environment variables containing secrets in CI scripts exposes them in build logs, which are often accessible to all repository collaborators and sometimes publicly visible on open-source projects.

How It Works

CI systems like GitHub Actions automatically mask secrets registered in the repository settings when they appear in log output. However, this masking is imperfect. If a secret is echoed indirectly (piped through base64, split across lines, or embedded in a JSON payload), the masking fails and the secret appears in plain text. Additionally, secrets passed as command-line arguments appear in process listings, and secrets written to files may be included in uploaded artifacts. Build logs are retained for 90 days by default and are visible to anyone with repository read access. On public repositories, the logs are visible to everyone on the internet.

Vulnerable Code
# BAD: secrets printed in CI logs in various ways
name: Deploy
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Deploying with key: ${{ secrets.DEPLOY_KEY }}"
          echo ${{ secrets.API_TOKEN }} | base64  # masking bypassed!
          curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.example.com
          # curl commands show in logs, and the -H value may not be masked
          env | grep -i secret  # prints all env vars matching 'secret'
Secure Code
# GOOD: secrets never printed, used only in masked env vars
name: Deploy
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: |
          # Never echo secrets, even for debugging
          echo "Deploying to production..."
          curl -sf -H "Authorization: Bearer $API_TOKEN" https://api.example.com
        env:
          API_TOKEN: ${{ secrets.API_TOKEN }}
      - name: Validate deployment
        run: |
          # Use --silent and redirect output to avoid leaking response data
          STATUS=$(curl -sf -o /dev/null -w '%{http_code}' https://app.example.com/health)
          echo "Health check status: $STATUS"

Real-World Example

In the 2021 Codecov breach, attackers modified the Codecov bash uploader script to exfiltrate environment variables (including CI secrets) from customer CI pipelines. The secrets were extracted from the CI environment and sent to an attacker-controlled server. Thousands of organizations had their CI secrets stolen because environment variables containing tokens were available in the CI runner environment and were not properly scoped.

How to Prevent It

  • Never echo, print, or log secret values in CI scripts -- not even for debugging purposes
  • Use environment variables in env: blocks instead of inline ${{ secrets.* }} interpolation to ensure GitHub's automatic masking works correctly
  • Add set +x at the start of shell steps to prevent bash from printing commands as they execute (set -x prints every command including secrets)
  • Regularly audit CI logs for accidentally exposed credentials using tools like trufflehog or gitleaks on CI output

Affected Technologies

GitHub ActionsNode.jsPythonDocker

Data Hogo detects this vulnerability automatically.

Scan Your Repo Free

Related Vulnerabilities