Secrets Scanning
Secrets scanning is a checkbox for a pentest but it should also be part of a healthy security operations practice. Before I get to the review of the secrets scanning tools, I want to take a wee detour and share a useful Unix (yes, Unix) tool: script(1). The script command logs everything in a terminal session. Most of the tools I am reviewing are run on the command line in a shell and sometimes it is the case that not all of the work I am doing gets saved in a way that is useful for later. It is very useful at the start of a day to run the script command:
% script ~/Projects/2026/MyProject/logs/02032026.log
Usually – but not always – this will even capture what is echoed to the terminal over a network connection (e.g., ssh). I was reminded of this when I let trufflehog run against a remote repository, walked away while it ran and then didn’t save the output before letting Apple update my MacBook. Doh.
So why is secrets scanning important? Usable credentals is one of those findings often referred to as low-hanging fruit. As much as we all want to go for those elusive, rare and sexy buffer overflows, enterprises are far more likely to be compromised by leaked credentials. The inspiration for this post was the Change Healthcare Ransomware Incident, where attackers gained access to a Citrix portal using leaked credentials for an account that was not enrolled with multi-factor authentication. In the past, I have worked with my clients to detect account takeovers and mitigate credential stuffing attacks. Shit’s not fun. Best to get ahead of it whenever possible.
At some point in your pentest, you’re going to have a bunch of artifacts (e.g., code, JavaScript bundles, logs, configmaps for kubernetes pods) and you will want to inspect them for usable credentials. The tool I have used for this in the past is trufflehog (jump).
For this post, I also evaluated (or tried to)
If you want to follow along, you should have Go installed. If not, you can install Go. Or read the READMEs in the repos I’ve linked for alternative installation instructions. To test these tools, I cloned the OWASP wrongsecrets repo.
trufflehog
To install trufflehog, I chose the Compile from source option:
% git clone https://github.com/trufflesecurity/trufflehog.git
% cd trufflehog; go install
# [... wait ...]
NOTE: trufflehog will try to verify credentials by default. I don’t really care for this feature, so I always use the --no-verification flag.
% trufflehog filesystem /path/to/wrongsecrets --no-verification --json
{"level":"info-0","ts":"2026-03-02T15:29:37-06:00","logger":"trufflehog","msg":"running source","source_manager_worker_id":"gmdl4","with_units":true}
{"SourceMetadata":{"Data":{"Filesystem":{"file":"wrongsecrets/.ssh/wrongsecrets.keys","line":1}}},"SourceID":1,"SourceType":15,"SourceName":"trufflehog - filesystem","DetectorType":15,"DetectorName":"PrivateKey","DetectorDescription":"Private keys are used for securely connecting and authenticating to various systems and services. Exposure of private keys can lead to unauthorized access and data breaches.","DecoderName":"PLAIN","Verified":false,"VerificationFromCache":false,"Raw":"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACAA1sjJcm0xU4TxVyhUjoAxpUWjZlKneZcIoaKQSyFO7wAAAKAWqeghFqno\nIQAAAAtzc2gtZWQyNTUxOQAAACAA1sjJcm0xU4TxVyhUjoAxpUWjZlKneZcIoaKQSyFO7w\nAAAEBipwrG6wv7JN3oxHf0NmU96RXN+MzESqsPC7q+eiJ+CADWyMlybTFThPFXKFSOgDGl\nRaNmUqd5lwihopBLIU7vAAAAFndyb25nc2VjcmV0c0Bvd2FzcC5jb20BAgMEBQYH\n-----END OPENSSH PRIVATE KEY-----\n","RawV2":"","Redacted":"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5v","ExtraData":{},"StructuredData":null}
[... you get the idea ...]
You can also run trufflehog against a remote repository. This scan should return the entire git history, including commits, comments and branches (even deleted ones). As you can imagine, this can take a while; you’re going to want to automate it.
% export GH_TOKEN=ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
% script ~/tmp/02032026.log
Script started, output file is /Users/erin/tmp/02032026.log
% trufflehog github-experimental --repo=https://github.com/OWASP/wrongsecrets --token=${GH_TOKEN} --object-discovery --no-verification
# [... wait ...]
Of the tools evaluated, trufflehog appears to be the only one that does de-duplication. The report should contain the file path, commit SHA, line numbers, author and date.
gitleaks
The next tool I evaluated is gitleaks. I also opted for the clone and build option to install gitleaks, which meant I had to copy the resulting binary somewhere into my $PATH directory:
% git clone https://github.com/gitleaks/gitleaks.git
% cd gitleaks
% make build
% cp gitleaks ~/go/bin
Unlike trufflehog, gitleaks will only work on a local copy of a git repository. There are two options for gitleaks,
gitleaks dir– scans every file on disk, including files that are untracked or gitignoredgitleaks git– scans commits in git history.
To get the full report, you will either need to supply the -v flag or --report-path=/path/to/output.json (note that --report-path= does not seem to honor a tilde). Gitleaks does not do credential validation but it does produce the best report of all three tools, including info like file path, commit SHA, line number, author and date.
% gitleaks dir /path/to/wrongsecrets --no-banner --report-format=json --report-path=-
3:34PM INF scanned ~2172620 bytes (2.17 MB) in 794ms
3:34PM WRN leaks found: 56
[
{
"RuleID": "generic-api-key",
"Description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
"StartLine": 61,
"EndLine": 61,
"StartColumn": 35,
"EndColumn": 64,
"Match": "wrongsecrets:1.12.10-k8s-vault",
"Secret": "1.12.10-k8s-vault",
"File": "wrongsecrets/aws/k8s/secret-challenge-vault-deployment.yml",
"SymlinkFile": "",
"Commit": "",
"Entropy": 3.5724695,
"Author": "",
"Email": "",
"Date": "",
"Message": "",
"Tags": [],
"Fingerprint": "wrongsecrets/aws/k8s/secret-challenge-vault-deployment.yml:generic-api-key:61"
},
[... you get the idea ...]
SecretScanner
I had zero luck with SecretScanner. This one didn’t work out because it requires a license key — you have to curl their endpoint with a work email to generate one, even for the community edition; this failed (see below). The domain appears to be in CloudFlare jail.
curl 'https://license.deepfence.io/threatmapper/generate-license?first_name=Bobo&last_name=Jones&email=bobo@bobotjones.com&company=Mingus%20Pingus&resend_email=true'
curl: (6) Could not resolve host: license.deepfence.io
titus
The final tool I evaluated is Praetorian’s titus. I got stuck on the make build part of the instructions so I am including the brew install command that I ran to fix it.
% git clone https://github.com/praetorian-inc/titus
# [... wait ...]
% HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config vectorscan
# [... wait ...]
% cd titus
% make build
# [... wait ...]
% cp dist/titus ~/go/bin
First run, I got this
% titus scan /path/to/wrongsecrets
[vectorscan] 484/484 rules compiled for Hyperscan, 0 rules use regexp2 fallback
Scanned 2203824 B from 642 blobs in 2 second (1087542 B/s); 53/53 new matches
Rule Findings Matches
────────────────────────────────────────────────────────────────────────
Generic Password 4 4
Generic Secret 7 13
Slack Webhook 3 9
JDBC connection string with embedded credentials 4 4
PEM-Encoded Private Key 1 1
Contains Private Key 1 1
Password Hash (bcrypt) 20 20
Base64-PEM-Encoded Private Key 1 1
Run the `report` command next to show finding details.
Reviewing the documentation for titus, it looks pretty powerful. For example,
% export GH_TOKEN=ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
% script ~/tmp/02032026.log
% titus github --org owasp --token $GH_TOKEN
[vectorscan] 484/484 rules compiled for Hyperscan, 0 rules use regexp2 fallback
Enumerating repositories...
Found 1350 repositories to scan
Cloning OWASP/EnDe...
Cloning OWASP/OWASP-WebScarab...
Cloning OWASP/OWASP-Proxy...
Cloning OWASP/passfault...
Cloning OWASP/owasp-orizon...
Cloning OWASP/owasp-esapi-ruby...
Cloning OWASP/AppSensor-Handbook...
Cloning OWASP/O-Saft...
[... wait hold up wat ...]
^Cfetch-pack: unexpected disconnect while reading sideband packet
I didn’t let titus finish doing this even though I probably have enough disk space… it created an SQLite database named titus.db in my current working directory. I have an external 1TB hard drive that already has almost 2000 repos on it, so I did not wait for it to finish. Titus does not appear to validate credentials by default, but takes a flag --validate
a little help from Claude
Just for fun, I asked Claude (Sonnet 4.6) to run titus, trufflehog and gitleaks against the wrongsecrets repo and then compare. This is what came back:
% gitleaks git /path/to/wrongsecrets --no-banner --report-format=json --report-path=-
% trufflehog git file:///path/to/wrongsecrets --no-verification --json
% titus scan --git --format json --output :memory: /path/to/wrongsecrets
| Rule / Category | gitleaks | trufflehog | titus |
|---|---|---|---|
| Slack webhook | 12 | 9 | 9 |
| Generic API key | 23 | 0 | 0 |
| Private key (PEM) | 4 | 3 | 5 * |
| JDBC connection string | 0 | 4 | 4 |
| Postgres credentials | 0 | 2 | 0 |
| Kubernetes secret YAML | 6 | 0 | 0 |
| Vault service token | 3 | 0 | 0 |
| Generic password | 0 | 0 | 5 |
| Generic secret | 0 | 0 | 13 |
| Bcrypt password hashes | 0 | 0 | 20 |
| Total | 48 | 18 | 56 |
Claude found that titus double-counted a couple of keys, and that each tool found things that the others did not. It might be the case that donking around with flags improves coverage. Apparently trufflehog had the fewest false positives and titus had the most. No harm in running all three, I suppose. And there’s probably other fun things you could do with ripgrep and your own library of regular expressions.