GitGuardian's 2024 State of Secrets Sprawl report found over 12 million new hardcoded secrets in public GitHub repositories in a single year. This includes API keys, OAuth tokens, cloud provider credentials, database passwords, and private keys. Many were already being actively exploited within hours of the commit being pushed. Secrets sprawl is not an exotic attack vector — it is one of the most reliable ways an attacker can go from zero access to privileged access in minutes.
Where Secrets End Up
- --Source code repositories (committed directly or in configuration files included by accident)
- --CI/CD environment variables (logged in build output, accessible to any workflow with pull-request trigger)
- --Container images (baked into layers, extractable with docker history)
- --Log files (applications that log request headers or full request bodies)
- --Client-side JavaScript bundles (any secret in frontend code is public)
- --Cloud instance metadata services (IMDS credentials exposed via SSRF)
- --Developer workstation dotfiles (.env files, shell history, IDE settings)
Detecting Secrets in Code
# Scan a repository for secrets using Gitleaks
# Install: brew install gitleaks or download from GitHub releases
gitleaks detect --source . --report-format json --report-path gitleaks-report.json
# Scan git history (finds secrets that were deleted but still in commits)
gitleaks detect --source . --log-opts="--all" --report-path history-report.json
# Use TruffleHog for high-entropy string detection in git history
trufflehog git file://. --only-verified
# Scan container images for secrets
trufflehog docker --image nginx:latest
# Pre-commit hook: prevent secrets from being committed
# Install pre-commit: pip install pre-commit
# .pre-commit-config.yaml:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaksShort-lived Credentials as the Solution
The root cause of secrets sprawl is long-lived credentials. A static API key that never expires will eventually be found — in a leaked repository, a developer's laptop, a log file, or a compromised system. Short-lived, automatically rotated credentials fundamentally change the risk profile: by the time a leaked credential is discovered, it has already expired.
# GitHub Actions: Use OIDC for keyless AWS authentication
# No AWS credentials stored as GitHub secrets
name: Deploy
on: [push]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: eu-west-1
# No access-key-id or secret-access-key needed
# Credentials are short-lived (~1 hour) and issued via OIDC
- name: Deploy
run: aws s3 sync dist/ s3://my-bucket/HashiCorp Vault for Application Secrets
# Vault dynamic database credentials: each app instance gets unique, short-lived creds
# Configure the database secrets engine
vault secrets enable database
vault write database/config/my-postgres-db \
plugin_name=postgresql-database-plugin \
allowed_roles="app-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/mydb" \
username="vault" \
password="vault-password"
# Define a role with a 1-hour TTL
vault write database/roles/app-role \
db_name=my-postgres-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# Application requests credentials at startup (not stored anywhere)
vault read database/creds/app-role
# Returns: username=v-app-abc123 password=A1b2C3d4E5f6 (expires in 1h)Implementation Roadmap
- 01Scan your repositories immediately with Gitleaks. Run against full git history. Rotate any secrets found, even if the commit was deleted.
- 02Install pre-commit hooks to prevent new secrets from being committed. This is a one-time investment that eliminates a persistent risk.
- 03Migrate CI/CD credentials to OIDC-based keyless authentication where supported (GitHub Actions to AWS/GCP/Azure all support this natively).
- 04Audit container images for baked-in secrets. Use multi-stage builds to ensure secrets used during build do not end up in final images.
- 05Deploy a secrets management solution (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager) for runtime application secrets.
- 06Set expiry policies on all API keys and credentials that do not yet support automatic rotation. A key with a 90-day expiry is substantially safer than one that never expires.
Many cloud providers and SaaS platforms now offer automatic rotation for credentials stored in their native secrets managers. AWS Secrets Manager can rotate RDS credentials, API Gateway keys, and Redshift credentials automatically. This is the lowest-friction path to short-lived credentials for common credential types.