← All guides

Secret scanning

Code & dependencies
On this page

In plain terms

Secret scanning looks through your code, and its git history, for things that should never be there: API keys, database passwords, private keys, cloud credentials, tokens. These get committed by accident constantly (a quick test, a copy-paste, a config file that wasn't .gitignored), and once they're in git history, they're effectively permanent.

Why it matters

A leaked cloud key can become someone else's crypto-mining bill, or a full breach, within minutes; bots scan public commits continuously. And "just delete it" doesn't work: git keeps every version, so a secret committed and then removed is still sitting in the history for anyone who clones the repo. Leaked credentials are one of the most common initial-access vectors in real breaches.

Where it fits

  • Artifact: the repository. Both the current tree and its full history.
  • Stage: ideally a pre-commit hook (stop it before it's ever committed), plus a CI gate and periodic history scans as a backstop.

How it works

Secret scanners combine two strategies: high-signal regular expressions for known token formats (AWS keys, GitHub tokens, Stripe keys all have recognizable shapes) and entropy analysis to catch high-randomness strings that look like generated secrets. Better tools reduce noise by validating format checksums and understanding common false positives (example keys in docs, test fixtures).

Deleting a committed secret changes only what HEAD contains A commit history along a line. At one commit a credentials file is added. Several commits later it is removed. Every clone taken after the first commit still contains the key, because git keeps every version. A scan reading the tree at the latest revision reports nothing. A scan reading the history reports the key at the commit that introduced it. ONE BRANCH, SIX COMMITS add config/prod.env git rm config/prod.env HEAD every clone taken from here on carries the key draugr scan reads the tree at the scanned revision, the state that deploys nothing to report history: true reads every commit that ever existed on the branch found, with the commit Either way the fix is rotation. Removing the line changes what HEAD holds and nothing about who already has the key.
The first row is the gate you want on every change, and it is the one that says nothing here. The second is why the two run from one descriptor.
ToolNotes
GitleaksOSS, fast, scans tree + history; easy pre-commit hook
TruffleHogOSS; notable for verifying found secrets against the live service
git-secretsOSS, from AWS; lightweight pattern-based hook
GitHub Secret ScanningBuilt into GitHub; partners auto-revoke leaked tokens

Common pitfalls

  • Finding it isn't fixing it. A detected secret must be rotated (revoked and reissued), not just deleted. Assume anything committed is already compromised.
  • History is the hard part. Purging a secret from git history means rewriting it (git filter-repo) and force-pushing, disruptive, so prevention beats cleanup.
  • False positives from example values and test data need allow-listing to keep signal high.
How Draugr fits. Draugr's secrets control runs Gitleaks over each component's repository and reports findings as SARIF. The two rows in the figure are the same descriptor with one line different, rather than two tools: history: true under gitleaks turns the per-change gate into the periodic deep sweep. Leaked credentials are treated as top-priority findings whatever the severity math says, because a live key is a live key.

History findings are marked as such and carry the commit. A secret that has moved since it was committed is reported at the path it lives at now and the one it was introduced at, because those two call for different work: one is a file to change, the other is a history to rewrite.

Gitleaks does not read a file whose name contains an archive extension, such as notes.zip.txt, so each one is listed under Caveats as unread, with that reason, rather than counted as clean. Rename the file, or fail the run on it with failOnCaveats: [unread-files] in the descriptor's gate.

See the gitleaks options in the Saga reference, and pair the control with a pre-commit hook to stop leaks before either row sees them.

Keep learning