Skip to content
$cd ..

// post

Secrets Management in CI/CD Pipelines

Hardcoded credentials in source code remain one of the most consistently exploited attack vectors, yet it keeps happening. Not because engineers don’t know better, but because the path of least resistance in most CI/CD setups still leads directly to an .env file or a hardcoded string in a workflow YAML.

This post is about fixing that — practically, not just in principle.

Why Secrets Leak

Secrets end up in source code for predictable reasons:

The problem compounds over time. A secret committed six months ago may still be valid and actively scanned for by automated tools running against public (and private) repositories right now.

The Threat Model

Before choosing tooling, understand what you’re defending against:

  1. Leakage via source control — secrets committed to git, including in branches and history
  2. Leakage via CI logs — secrets echoed or printed during pipeline execution
  3. Leakage via artifacts — Docker images, compiled binaries, or build caches that contain secrets
  4. Unauthorized access — legitimate secrets exposed to the wrong pipelines, jobs, or people

These require different controls. Scanning helps with #1. Masking handles #2. Build hygiene covers #3. Secrets management platforms address #4.

Scanning First: Find What’s Already There

Before you can fix anything, you need to know your current exposure. Two tools worth running immediately:

Gitleaks — scans git history, staged changes, and the working tree for secrets using regex patterns and entropy analysis.

# Scan full repo history
gitleaks detect --source . --log-opts="--all"

# Pre-commit hook usage
gitleaks protect --staged

TruffleHog — goes deeper, scanning git history with verified detection against live APIs.

trufflehog git file://. --only-verified

Run both as part of onboarding and in CI. A pre-commit hook with Gitleaks is low-friction and catches the majority of accidental commits before they happen.

Platform-Native Secrets: The Baseline

Every major CI/CD platform has a secrets store. Use it.

GitHub Actions:

jobs:
  deploy:
    steps:
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
        run: ./deploy.sh

GitLab CI:

deploy:
  variables:
    API_KEY: ${{ secrets.API_KEY }}
  script:
    - ./deploy.sh

Key rules for platform-native secrets:

Going Further: HashiCorp Vault

For organizations that need audit trails, dynamic secrets, fine-grained access policies, and rotation automation, a dedicated secrets manager is the right answer. HashiCorp Vault is the most widely used option.

The key concept in Vault is dynamic secrets — instead of long-lived credentials, Vault generates a short-lived credential on demand for each pipeline run.

# Pipeline retrieves a temporary database credential
vault read database/creds/my-role
# Returns: username, password, lease_duration (e.g. 1h)

The credential expires automatically. There’s nothing to rotate manually. If the pipeline is compromised, the attacker has a credential that’s already expiring.

Vault integrates natively with GitHub Actions via the hashicorp/vault-action:

- uses: hashicorp/vault-action@v2
  with:
    url: https://vault.example.com
    method: jwt
    role: ci-deploy
    secrets: |
      secret/data/prod/db password | DB_PASSWORD

SOPS for Configuration Files

For secrets that need to live in files (Kubernetes manifests, Helm values, config files), SOPS encrypts individual values within YAML/JSON files while leaving the structure readable.

# values.yaml — encrypted with SOPS, safe to commit
db_password: ENC[AES256_GCM,data:abc123...,type:str]
db_host: postgres.internal  # plaintext — fine to commit

SOPS integrates with AWS KMS, GCP KMS, Azure Key Vault, and age keys, so the encryption key stays out of the repository entirely.

What Good Looks Like

A mature secrets setup in CI/CD has these properties:

The goal isn’t a perfect system on day one. It’s moving consistently away from the dangerous defaults — and the biggest win is usually just getting scanning in the pre-commit hook and moving secrets out of .env files committed to repos.

Previous
Software Supply Chain Security and SBOMs
Next
Why I Started Building ScanDog