Skip to main content
INS // Insights

Hardcoded Credentials in CI/CD: Finding and Fixing Them

Updated August 2026 · 8 min read

A hardcoded credential in a CI/CD pipeline isn't a single mistake — it's usually a symptom of a pipeline that was never designed to authenticate any other way. The AWS access key gets pasted into a GitHub Actions secret because that's the fastest path to a working deploy, and it stays there for years because it works, right up until a scanner flags it or an incident forces the question of who actually has access to that key.

Where Hardcoded Credentials Actually Live

Scanner findings cluster in a small number of predictable places once you look:

  • CI/CD platform secrets — AWS keys, database passwords, and API tokens stored as "secrets" in GitHub Actions, GitLab CI, or Jenkins credentials, which are encrypted at rest but still long-lived and often over-scoped
  • Committed .env files — usually from a git add . that swept up a local environment file never added to .gitignore
  • Git history, not just the current tree — a credential removed from the current commit but still recoverable from any prior commit in history; scanning HEAD alone misses this entirely
  • Infrastructure-as-code files — a database password or API key hardcoded directly into a Terraform variable default or a Kubernetes manifest, committed as "temporary" and never rotated out
  • Docker images — credentials baked into an image layer during build, retrievable by anyone who can pull the image even after the Dockerfile is fixed

Scanning Is the Detection Layer, Not the Fix

# .github/workflows/secret-scan.yml — gitleaks scan on every push and PR
name: secret-scan
on: [push, pull_request]
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # full history, not just the latest commit
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}

fetch-depth: 0 is the detail most teams miss when they first wire up scanning — a shallow checkout only scans the current commit's diff, which finds new secrets going forward but misses everything already sitting in history. A full-history scan (or a dedicated tool run once against the entire repository) is the only way to find what's already exposed.

Finding a hardcoded credential and rotating the underlying secret are two different steps, and skipping straight to "rotate it" without also purging it from git history leaves the exposed value permanently recoverable by anyone who clones the repository — rotation limits the blast radius of the current exposure, it doesn't undo the exposure itself.

The Real Fix Is Removing the Need for a Static Credential

Rotating a hardcoded AWS key to a new hardcoded AWS key fixes the immediate finding and recreates the exact same risk on a delay. The durable fix is removing the pipeline's dependency on a static, long-lived secret entirely — federating the pipeline's identity through OIDC so it authenticates with a short-lived, automatically-expiring token instead of a value that has to live in a secrets store at all.

# .github/workflows/deploy.yml — OIDC federation, zero static AWS credentials
permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://production-assets-bucket/

No AWS_ACCESS_KEY_ID, no AWS_SECRET_ACCESS_KEY stored anywhere — the role trust policy scopes which repository and branch can assume it, and the credential exists only for the duration of the workflow run. This is the same pattern that eliminates the underlying finding class permanently rather than treating each individual leaked credential as an isolated incident.

A Remediation Program, Not a One-Time Cleanup

A single scan-and-fix pass closes the findings that exist today. It doesn't stop the next developer from committing a .env file next month. The durable version of this work is a program with four parts: scanning wired into every push and pull request (not a periodic manual scan), a pre-commit hook that catches obvious patterns before they're ever pushed, a git-history sweep run once to close the historical exposure, and — the part that actually prevents recurrence — federation so there's no static long-lived credential left for a developer to accidentally hardcode in the first place.

Frequently Asked Questions

If we rotate a leaked credential, do we still need to remove it from git history?

Yes. Rotation invalidates the leaked value going forward, but the old value remains permanently retrievable by anyone who can access the repository's history — including former employees, compromised accounts with read access, or anyone who cloned the repo before the rotation. History rewriting (via git filter-repo or a provider's history-purge tooling) is a separate, necessary step.

Can OIDC federation cover every CI/CD-to-AWS use case?

For the vast majority of deploy, build, and test scenarios, yes — GitHub Actions, GitLab CI, and most modern CI platforms support OIDC federation to AWS IAM roles natively. The remaining edge cases are typically third-party or legacy tooling that only supports static credential injection, which should be isolated with the narrowest possible IAM policy and a short rotation cycle as a compensating control.

How do we scan for secrets that were committed years ago, before scanning was in place?

Run a full-history scan once with a tool built for that purpose (TruffleHog and Gitleaks both support historical scanning) against the entire git log, not just the current branch tip. This surfaces every historical exposure at once, which then needs to be triaged, rotated, and purged from history as a discrete cleanup project separate from the ongoing per-push scanning.

What counts as evidence for a SOC 2 auditor around secrets management?

Evidence that scanning runs on every code change (a CI job log showing the scan step executing on recent commits), a documented remediation process with actual closed findings, and — increasingly expected — evidence that credentials are federated rather than static wherever technically feasible, since "we scan for secrets" without addressing the root cause reads as incomplete to a thorough reviewer.

Is a secrets manager (AWS Secrets Manager, HashiCorp Vault) enough on its own?

A secrets manager solves secure storage and centralized rotation for credentials that must exist — it doesn't eliminate the need for a credential in the first place. For CI/CD specifically, OIDC federation is a stronger fix than moving the same static key into a secrets manager, because it removes the static, long-lived value from the system entirely rather than storing it more securely.


Rutagon runs credential elimination programs — from scanner findings and git-history sweeps to a full OIDC federation cutover with a rollback plan.

Talk to us about your credential elimination pilot → rutagon.com/contact | 907-841-8407 | contact@rutagon.com

Related reading: Replacing AWS Access Keys With OIDC in CI/CD · Secrets Sprawl Remediation Sprint Guide · Security Automation Capability

External reference: GitHub Docs — About Security Hardening With OpenID Connect