- 01Two Secrets That Survived a “No Leaks” Result
- 02How gitleaks Finds Secrets
- 03Installation and Basic Commands
- 04Handling False Positives
- 05Blocking Commits with pre-commit
- 06GitHub Secret Scanning and Push Protection
- 07Moving Secrets into .env and Using .gitignore
- 08A Homemade Check That Matches Actual Values
- 09How My Replacement Script Replaced Things Twice
- 10What to Do, in Order, If a Secret Gets into History
- 11Common Pitfalls
- 12Things to Confirm Before You Start
- 13FAQ
- 14Official Sources I Referenced
- 15Summary
Good morning, this is まさきん.
I’m getting my home automation scripts ready to live in a public GitHub repository. While hunting down secrets, I found two that were still sitting in the code even after the scanner told me “no leaks.” Here’s the process I went through.
This information is current as of publication. Please check the official sites for the latest details.
- gitleaks uses regular expressions and entropy to find secrets that are “recognizable by their shape,” like API keys
- Hardcoded values with no distinctive shape can slip right past a scanner
- Matching the whole repository against the actual values from your old .env files catches what slipped through
- For public repositories, GitHub runs Secret Scanning automatically and for free
- If a secret makes it into your history, revoke and reissue the key before you rewrite history
Two Secrets That Survived a “No Leaks” Result
There are a lot of automation tools running in my house. Their settings were spread across 14 separate .env files, with key files scattered all over the place. I consolidated everything into a single config file (41 keys) and one credentials folder, then updated the references in roughly 70 code files.
Assuming everything I’d moved would be committed to git, I ran gitleaks. The result: “no leaks.”
Just to be safe, though, I also searched for the actual values from my old .env files. That turned up two hardcoded values gitleaks had missed. One was an access code for a device on my LAN. The other was a value tied to a personal file name.
Neither of them looked anything like an API key. Given how the scanner works, it’s probably not surprising that it missed them.
How gitleaks Finds Secrets
gitleaks is a tool for finding secrets such as passwords and API keys. It can scan git history, files on disk, and standard input.
Detection is built around “rules.” Each rule has a regular expression that matches the token format of a specific service. The default config in the official repository ships with more than 200 rules.
The other axis is entropy, a number that describes how random a string of characters is. The generic API key rule looks for words like “key” combined with a high-entropy value.
Here’s how that plays out in practice:
| Type | Does gitleaks catch it? | Why |
|---|---|---|
| API keys and tokens for major services | Usually | They have fixed prefixes and formats |
| Long random strings after password= | Usually | They hit both the keyword and the entropy check |
| Short numeric-only access codes | Often not | Little that stands out in shape or randomness |
| Internal URLs, hostnames, file names | Often not | Indistinguishable from ordinary strings |
| Hardcoded values with no key name | Often not | No keyword to latch onto |
Installation and Basic Commands
According to the official README, you can install gitleaks via Homebrew, Docker, or by building from source with Go. On a Mac, it’s just:
brew install gitleaks
One thing to watch out for is the command names. The detect and protect commands you’ll see in older articles were deprecated in v8.19.0. These three modes are now the basics:
| Command | Target | When to use it |
|---|---|---|
| gitleaks git | git history (the diffs from git log -p) | Digging back through everything already committed |
| gitleaks dir | Directories and files | Checking files that aren’t in git yet |
| gitleaks stdin | Standard input | Scanning the output of another command directly |
Here are the forms I use most:
# Scan the history of the current repository
gitleaks git -v
# Scan a folder you plan to publish, before adding it to git
gitleaks dir -v ./my-scripts
If it finds a leak, the exit code is 1; if not, it’s 0. That makes it easy to use as a gate in scripts.
Handling False Positives
You’ll sometimes get false positives, for example from dummy values used in tests. The official README offers three ways to deal with them:
- Add a
gitleaks:allowcomment on that line - Put the finding’s Fingerprint from the report into
.gitleaksignoreto ignore just that one result - Exclude whole paths or regex patterns with an allowlist in the
.gitleaks.tomlconfig file
In v8.25.0, the allowlist syntax changed to [[allowlists]]. Keep that in mind if you’re copying an older config example.
Personally, I think ignoring findings one at a time by Fingerprint is the safest option. If you exclude an entire path, you won’t notice when a real secret lands there later.
Blocking Commits with pre-commit
To catch things as part of your everyday workflow, gitleaks also works as a pre-commit hook.
- Install pre-commit
- Add the following to
.pre-commit-config.yamlat the root of the repository - Run
pre-commit autoupdateandpre-commit install
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
GitHub Secret Scanning and Push Protection
GitHub has its own secret detection, too. Here’s how it works for public repositories:
| Feature | For public repositories | What it does |
|---|---|---|
| Secret Scanning | Runs automatically, free | Scans git history on all branches and alerts you to findings |
| Push protection for users | On by default at the account level | Blocks pushes containing secrets to public repositories |
| Push protection for repositories | Requires GitHub Secret Protection | Blocks pushes at the repository level |
Secret Scanning also covers the text of Issues and Pull Requests. When it finds a key belonging to a partner service, that service gets notified.
That said, both features mainly target secrets that are recognizable by their shape. Relying on them to catch shapeless values might be a little risky.
Moving Secrets into .env and Using .gitignore
The most effective measure is simply not writing secrets into your code. GitHub’s official documentation also recommends using environment variables.
My consolidation followed that same idea. Here’s what the layout looks like when the repo goes public:
| File | Committed to the repo? | Contents |
|---|---|---|
| .env | No | The real values |
| .env.example | Yes | Key names plus dummy values or descriptions |
| .gitignore | Yes | Exclusion rules for .env and the credentials folder |
| credentials folder | No | Key files and the like |
Keeping a .env.example lets you show which keys are required while keeping the values hidden.
There’s a catch with .gitignore, though. As the official git documentation notes, it has no effect on files that are already tracked. Anything you’ve committed once has to be untracked with git rm --cached.
Just like taking inventory of your tools, taking stock of fixed costs like your phone bill once in a while can give you a clearer picture.
Clicking this opens the Rakuten login page. Once you log in, you’ll see the campaign details.
A Homemade Check That Matches Actual Values
This is the part I most want to get across. Scanners search by shape, so I added a method that searches by the values I already have: checking whether any value from my old .env files shows up anywhere in the repository.
- Extract just the values from the old .env files into a file with one value per line
- Remove blank lines and values too short to be meaningful (like true or 1) that would cause false hits
- Keep the value list outside the repository
- Use
grep -Fto match them as fixed strings rather than regular expressions - Run the same match against the git history
Here’s an example with the value list stored at ~/private/values.txt:
# Match against the entire working tree (excluding .git)
grep -rnF -f ~/private/values.txt --exclude-dir=.git .
# Match against every commit in history
git grep -nF -f ~/private/values.txt $(git rev-list --all)
With -F, values containing special characters are searched as literal strings. Watch out for blank lines in the value list, though: they’ll match every single line.
Keep the value list somewhere that will never be published. Once you’re done matching, it’s safest to delete it or move it back somewhere locked down.
How My Replacement Script Replaced Things Twice
I tripped over one more thing while doing the actual work, when I used a replacement script to rewrite the references in about 70 files.
In three cases, the new string contained the old string. Think of replacing “config” with “config_shared.” Run that replacement twice and you end up with “config_shared_shared.”
I rolled everything back and started over. The lesson is simple: before replacing, check whether the old string appears inside the new one. If it does, it’s better to replace it exactly once using word boundaries.
After redoing it, all 18 files I tested for loading worked correctly.
What to Do, in Order, If a Secret Gets into History
If you do commit a secret, the order of operations matters. GitHub’s official documentation says to revoke and reissue (rotate) the key first.
| Step | What to do | Why |
|---|---|---|
| 1 | Revoke and reissue the key or password | Once the old value stops working, the damage stops there |
| 2 | Change the code to read from .env | So you don’t hardcode the new value |
| 3 | Rewrite history with git filter-repo if needed | To remove the old value from history |
| 4 | Force push, and contact GitHub Support if needed | To clear cached views and Pull Request references |
The official documentation notes that rotation alone is sometimes enough. Rewriting history changes commit hashes, and it can’t remove copies that live on in forks.
The tool for rewriting is git filter-repo. You need a version that supports --sensitive-data-removal (2.47 or later). To replace values, it looks like this:
git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt
Assume that any secret that has ever appeared in a public repository has already been picked up by someone. The official order is to revoke the key and replace it with a new one before you worry about erasing history.
Common Pitfalls
- Treating gitleaks’ “no leaks” as proof that there are no secrets at all
- Only running
gitleaks gitand forgetting to scan files that haven’t been committed yet - Adding a file to .gitignore when it’s already being tracked
- Keeping your value list or replacement list inside the repository
- Having old and new strings overlap so replacements get applied twice
- Rewriting history but forgetting to rotate the key
The first one is exactly what happened to me. Scanners are extremely reliable tools, but they only look at shape. Combining them with value-based matching feels a lot safer to me.
I wrote about counting to verify instead of just setting rules in What I learned from counting 164 tasks. Secret checks might follow the same pattern.
Things to Confirm Before You Start
- Do you have a list of which files hold secrets, and how many?
- Have you gathered your .env and key files into one place?
- Does .gitignore include .env and the credentials folder?
- Have you untracked already-tracked secret files with git rm --cached?
- Have you run both gitleaks git and gitleaks dir?
- Have you matched the working tree and history against your old .env value list?
- Is your value list stored outside the repository?
- Is Push protection for users turned on in GitHub?
My own move to a public repository is still a work in progress.
FAQ
Q. If gitleaks says “no leaks,” is it safe to publish?
For secrets that are recognizable by their shape, you can feel fairly confident. But short codes and internal file names may slip through. I recommend pairing it with matching against a value list.
Q. What’s the difference between gitleaks detect and gitleaks git?
detect is an older command that was deprecated in v8.19.0. Today you use git to scan history and dir to scan folders. The old commands still run, but they no longer appear in the help output.
Q. Can I use Secret Scanning on a personal public repository?
Yes. According to the official documentation, it runs automatically and for free on public repositories. It covers history on all branches, plus the text of Issues and Pull Requests.
Q. Do I need to set up Push Protection myself?
Push protection for users, which works at the account level, is on by default. It blocks pushes containing secrets to public repositories. You can check its status under “Code security” in your settings.
Q. Is adding .env to .gitignore enough?
If you haven’t committed it yet, that will keep it from being tracked. But it has no effect on files that are already tracked. You need to untrack them with git rm --cached and also check whether they’re still in your history.
Q. What should I do first if I’ve committed a secret?
Revoke and reissue the key or password first. Decide whether you need to rewrite history after that.
Official Sources I Referenced
- gitleaks (GitHub repository, README)
- About secret scanning (GitHub Docs)
- About push protection (GitHub Docs)
- Push protection for users (GitHub Docs)
- Removing sensitive data from a repository (GitHub Docs)
- git-filter-repo (GitHub repository)
- gitignore (official Git documentation)
- pre-commit (official site)
Summary
gitleaks turned out to be a very reliable tool for finding secrets that are recognizable by their shape. On the other hand, values with no distinctive shape can hide behind a “no leaks” result.
That’s why combining a scanner with value-based matching seems like the way to go. Just like hunting for duplicate subscriptions, adding one more perspective changes what you can see. Before you hit the publish button, try opening up your local .env files one more time.
Clicking this opens the Rakuten login page. Once you log in, you’ll see the campaign details.
This article contains affiliate advertising. If you sign up for a product or service through a link on this site, we may receive compensation from the partner company. The site operator is also an employee of Rakuten Group and may receive compensation through an employee referral program. The content and opinions here are based on the operator’s own experience and research regardless of any advertising relationship, but please read with the above in mind. See the Disclaimer & Affiliate Disclosure for details. Disclaimer & Affiliate Disclosure
This article includes machine-translated content. Please check the official Rakuten Mobile multilingual page for exact terms.
If you have concerns about Rakuten Mobile coverage or signal quality, you can consult through the official signal improvement request form .