Where secrets hide in a codebase
The most common places developers accidentally commit credentials:
- Configuration files:
.env,config.yml,settings.py,application.propertiesβ committed without realising they contain real credentials. - Test files: Developers hardcode real credentials in tests for convenience, intending to remove them later. They never do.
- Commented-out code:
// password = "old_prod_password"β removed from active code but still in the file. - Git history: A secret committed, then deleted in the next commit. The deletion doesn't remove it from history.
- CI configuration:
.travis.yml,Jenkinsfile, GitHub Actions workflow files with secrets inlined (not using encrypted secrets). - Jupyter notebooks:
.ipynbfiles with API calls in cells that include tokens in the request examples.
The rotation trap: Finding a secret in your repo and rotating it is not enough. The old secret still exists in every git clone, every CI cache, and every GitHub fork ever made of the repository. If the repo was ever public, assume the old secret is already in attacker tooling.
Quick grep scan for common secret patterns
# Common API key and token patterns $ grep -rEin "api[_-]?key\s*[=:]\s*['\"][a-z0-9]{20,}" . $ grep -rEin "password\s*[=:]\s*['\"][^'\"]{8,}" . $ grep -rEin "secret[_-]?key\s*[=:]\s*['\"]" . $ grep -rEin "access[_-]?token\s*[=:]\s*['\"]" . # AWS credentials $ grep -rEn "AKIA[0-9A-Z]{16}" . $ grep -rEn "aws[_-]?secret" -i . # Common service-specific patterns $ grep -rEn "sk_live_[0-9a-zA-Z]{24}" . # Stripe live key $ grep -rEn "ghp_[0-9a-zA-Z]{36}" . # GitHub PAT $ grep -rEn "xoxb-[0-9]+-[0-9a-zA-Z]+" . # Slack bot token $ grep -rEn "AIza[0-9A-Za-z_-]{35}" . # Google API key # Also check .env files specifically $ find . -name "*.env" -o -name ".env*" | xargs grep -l "." 2>/dev/null
Trufflehog β scan the entire git history
Trufflehog is the most thorough secrets scanner for Git repositories. It scans every commit in the full git history, detects secrets using both regex patterns and entropy analysis, and validates findings against real APIs to confirm they are active (not rotated or invalid).
# Install $ brew install trufflehog # or: curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh # Scan local git repo (entire history) $ trufflehog git file://. --only-verified # Scan a remote GitHub repo $ trufflehog github --repo https://github.com/yourorg/yourrepo --only-verified # Scan only recent commits (faster for CI) $ trufflehog git file://. --since-commit HEAD~50 --only-verified # Output as JSON for automation $ trufflehog git file://. --json 2>&1 | jq '.SourceMetadata'
The --only-verified flag makes Trufflehog check each found credential against the respective API (AWS, GitHub, Stripe, etc.) to confirm it's currently active. This dramatically reduces false positives β you only see secrets that are real and working.
Gitleaks β fast regex-based scanning
Gitleaks is faster than Trufflehog (no API verification) and better suited for CI pipeline gates where speed matters. It ships with 150+ pre-built rules covering all major secret types.
# Install $ brew install gitleaks # Scan entire repo history $ gitleaks detect --source . --verbose # Scan only staged files (pre-commit use) $ gitleaks protect --staged --verbose # Generate a report $ gitleaks detect --source . --report-path gitleaks-report.json # Exit code 1 if secrets found β blocks CI $ gitleaks detect --source . --exit-code 1
Pre-commit hook: block secrets before they're committed
The cheapest place to catch a secret is before it enters git history. A pre-commit hook runs on every git commit on the developer's machine.
repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.21.0 hooks: - id: gitleaks - repo: https://github.com/Yelp/detect-secrets rev: v1.5.0 hooks: - id: detect-secrets args: ['--baseline', '.secrets.baseline']
$ pip install pre-commit
$ pre-commit install
$ pre-commit run --all-files # run against existing codebase first
CI/CD gate: catch anything that slips through pre-commit
name: Secrets Scan on: [push, pull_request] jobs: gitleaks: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: fetch-depth: 0 # full history for git log scan - uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{{ secrets.GITHUB_TOKEN }}} GITLEAKS_LICENSE: ${{{ secrets.GITLEAKS_LICENSE }}}
What to do after finding a secret in your repository
- Rotate the credential immediately. Disable or revoke the compromised key/token/password before doing anything else. Don't wait until the cleanup is complete.
- Assume it has been used. Check the service's audit logs (AWS CloudTrail, GitHub audit log, Stripe logs) for any suspicious activity using that credential.
- Remove from current code and replace with an environment variable reference or secrets manager reference.
- Do NOT rewrite git history if the repo is shared β history rewrites force all collaborators to reset their local clones and can break CI. The rotated credential is worthless anyway.
- If the repo was ever public: Treat the credential as permanently compromised regardless of whether you can see it in the current default branch. GitHub forks and clones may have captured it.
Never try to hide the commit by force-pushing or rebasing. If the repository is public, GitHub retains objects for a period even after force-push. Rotation is the only reliable remediation.
Scan your codebase for hardcoded secrets automatically
AquilaX detects 800+ secret types across your entire codebase and git history β with verified-secret detection and automated CI blocking.
See secrets scanning β