We Deleted the Key. Gitleaks Found It Anyway. Here's Why.

· SecurityClaw Demo D13 · Supply Chain Security

A developer panics. They committed a private key by mistake. They delete the file, push the commit, breathe a sigh of relief. They were never safe. We built exactly this scenario — 10 commits, 8 planted secrets, one RSA key deleted in the next commit after it was added — and ran SecurityClaw + Gitleaks against it. 13.2 milliseconds. 9 findings. 6 secrets found — including the one that "didn't exist anymore."

Date:March 12, 2026
Tool:gitleaks v8.18.0
Category:Supply Chain Security
Result:✅ Pass — 6/8 unique secrets
Scan time:13.2 milliseconds
Methodology note: We created a controlled git repo simulating a real developer workflow — 10 commits, 8 secrets planted across different files and scenarios. The repo includes realistic commit messages and file structures. All secrets were planted by us; all scan results are real. This is Demo D13 in the SecurityClaw campaign series. See also Demo D1 (TruffleHog) — gitleaks and TruffleHog are complementary tools, not alternatives.

The Setup

The demo repo had 10 commits simulating a real small-team development workflow: initial setup, adding configuration files, committing credentials "temporarily", trying to clean up, and pushing code that still carried secrets in its history.

We planted 8 secrets covering the most common real-world exposure categories:

Secret TypeFileSpecial Factor
AWS Access Key IDconfig/aws.ymlClassic credential exposure
AWS Secret Access Keyconfig/aws.ymlPaired with Access Key ID above
PostgreSQL passwordconfig/database.ymlYAML config pattern
GitHub PAT / JWT secretsrc/auth/config.pyDual-use credential
Slack Webhook URLconfig/notifications.jsonDedicated detection rule
RSA Private Keykeys/deploy_key.pemDeleted in the next commit
Stripe Secret Keysrc/payments.pyMulti-rule match
Base64-encoded API key.envObfuscated — entropy catch

The scan command:

gitleaks detect --source . --report-format json --report-path results.json --verbose

Runtime: 13.2 milliseconds. 9 raw findings. 6 unique secrets identified.

What Gitleaks Found

#Secret TypeFound?RuleWhy Notable
1AWS Access Key ID✅ YESaws-access-tokenAKIA... prefix triggers dedicated rule
2AWS Secret Access Key❌ MISSEDNo rule for random 40-char strings
3PostgreSQL password (YAML)❌ MISSEDpassword: in YAML not in default rules
4GitHub PAT / JWT secret✅ YESgeneric-api-keyEntropy + variable name context
5Slack Webhook URL✅ YESslack-webhook-urlDedicated Slack rule fires immediately
6RSA Private Key (DELETED)YESprivate-keyFound in git history — file gone from working tree
7Stripe Secret Key✅ YESstripe-access-token3 raw findings = 1 secret (multi-rule match)
8Base64-encoded API keyYESgeneric-api-keyEntropy analysis saw through the obfuscation

Score: 6/8 unique secrets. 0 false positives (1 low-risk OAuth client ID flagged for triage).

The Two Moments That Matter

Moment 1: The Deleted Key Was Not Gone

The RSA private key was committed in commit 8172da4 — the developer added it to the deploy configuration. In the very next commit, a182f88, they deleted it with the message: "Remove deploy key from repo (oops, was committed by mistake)."

The file does not exist in the current working tree. ls keys/ returns nothing. git status shows a clean repository.

Gitleaks found the key in commit 8172da4's diff. It's in the history. It will always be in the history. Every past and future clone of this repo has it.

The lesson is not subtle: git rm does not remove secrets. git push already made them permanent.

The only remediation is to do two things simultaneously:

  1. Rotate the key immediately — assume it's compromised. It has been accessible to anyone with repo access since the original push.
  2. Rewrite git history — using git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch keys/deploy_key.pem' or the faster BFG Repo-Cleaner. This requires a force-push and coordination with every team member who has a clone.

Most teams only do step 1. Step 2 is operationally painful. Which means the commit history stays poisoned, and any future clone pulls the deleted-but-present secret along with it.

Moment 2: Base64 Is Not Encryption

One .env entry was encoded:

INTERNAL_API_KEY=aW50ZXJuYWwtc2VjcmV0LWFwaS1rZXktMTIzNDU2Nzg5MA==

The developer had added a comment: "This is base64-encoded but still a secret (obfuscation != encryption)" — demonstrating self-awareness that the encoding was cosmetic. Gitleaks caught it anyway via entropy analysis. Base64 has characteristic entropy around 5.7 bits per character — clearly distinct from natural language (3.5–4.5 bits/char) or even hex strings (4.0 bits/char). Combined with the variable name INTERNAL_API_KEY, the rule fired.

No pattern match needed. The obfuscation was transparent to entropy analysis. This matters because developers sometimes think encoding a secret reduces detection risk. It doesn't. It often increases it, because encoded secrets have higher entropy than plaintext ones.

The Honest Gaps

Two secrets were missed. Both are genuine limitations of gitleaks's default ruleset:

AWS Secret Access Key — No Pattern Rule

The AWS Key ID (AKIA... prefix) was caught by a dedicated rule. The AWS Secret Access Key — a random 40-character alphanumeric string — was not. There's no distinctive pattern in the secret itself. Gitleaks has no default rule for it because random 40-character strings appear throughout codebases as hashes, tokens, and other non-secret identifiers — a rule would generate enormous false positives.

TruffleHog's verified AWS detector handles this by checking both the key ID and secret together and verifying them against AWS's API. It's slower but catches what gitleaks misses here.

PostgreSQL Password in YAML — Not in Default Rules

A plaintext password in a YAML config file (password: Sup3rS3cr3t123) was not flagged. Gitleaks's default rules don't cover the password: pattern in YAML because it generates false positives on placeholder values and documentation examples.

The fix: add a custom rule to a .gitleaks.toml committed to your repo:

[[rules]]
id = "yaml-password"
description = "Password in YAML config file"
regex = '''(?i)password\s*:\s*.{8,}'''
path = '''.*\.(yml|yaml)$'''
entropy = 3.5

This rule combines pattern matching with a minimum entropy threshold to reduce false positives on placeholder text like "changeme" or "your_password_here".

Gitleaks vs. TruffleHog: When to Use Each

D1 was TruffleHog. D13 is gitleaks. They're not competing — they're complementary. SecurityClaw runs both because they catch different secrets in different ways.

FeatureTruffleHog (D1)Gitleaks (D13)
Speed (same repo)~2 seconds13.2ms
Detectors700+ specialized~150 rules
Live secret verificationYes — checks if key still activeNo
Custom rulesConfig-based.toml file
Git history scanYesYes
Entropy detectionLimitedStrong
CI/CD pipeline fitRetrospective auditsPre-commit hooks
D1/D13 result4/5 planted secrets6/8 planted secrets

SecurityClaw's dual-scanner workflow: run gitleaks first (13ms, catches high-entropy and pattern-based secrets, perfect for CI gates). Run TruffleHog for deeper retrospective audits where live verification matters. Add custom rules to your .gitleaks.toml for organisation-specific credential patterns.

For teams building security into their development workflow, the concepts underlying these tools — entropy analysis, regex-based detection, git internals — are covered well in Black Hat Python. And The Web Application Hacker's Handbook covers credential exposure in the broader context of web application assessment — where git history secrets often lead directly to application-layer compromise.

SecurityClaw Scorecard: D13

D13 adds a second campaign to SecurityClaw's supply-chain-security category. Together with D1 (TruffleHog) and D9 (supply-chain-scanner), the supply chain coverage now spans three complementary approaches: package integrity, malicious behaviour detection, and secrets in version control history.

MetricValue
Toolgitleaks v8.18.0
Target10-commit controlled git repo
Secrets planted8 (across 6 file types)
Secrets found6/8 unique (75%)
Scan time13.2ms
False positives0 (1 low-risk OAuth ID flagged for triage)
Campaign resultPASS

If your git history has never been scanned for secrets, the question isn't whether there are exposed credentials — it's how many and how old the exposure is. gitleaks detect --source . --verbose answers that question in under 30 seconds for most repositories. It's free, it's fast, and it reads history that your developers thought was safely deleted.

Also worth reading alongside this demo: Penetration Testing by Georgia Weidman, which covers credential discovery as part of a complete assessment methodology — context for understanding where git history scanning fits in a real engagement.

Advertisement