Using AI

gitleaks Said "No Leaks" but Missed 2 Secrets: My Pre-Publish Secret Check Workflow

gitleaks Said "No Leaks" but Missed 2 Secrets: My Pre-Publish Secret Check Workflow

Photo: gitleaks(GitHub) (紹介目的で使用) via github.com

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.

POINT
  • 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:

TypeDoes gitleaks catch it?Why
API keys and tokens for major servicesUsuallyThey have fixed prefixes and formats
Long random strings after password=UsuallyThey hit both the keyword and the entropy check
Short numeric-only access codesOften notLittle that stands out in shape or randomness
Internal URLs, hostnames, file namesOften notIndistinguishable from ordinary strings
Hardcoded values with no key nameOften notNo 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:

CommandTargetWhen to use it
gitleaks gitgit history (the diffs from git log -p)Digging back through everything already committed
gitleaks dirDirectories and filesChecking files that aren’t in git yet
gitleaks stdinStandard inputScanning 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:

  1. Add a gitleaks:allow comment on that line
  2. Put the finding’s Fingerprint from the report into .gitleaksignore to ignore just that one result
  3. Exclude whole paths or regex patterns with an allowlist in the .gitleaks.toml config 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.

  1. Install pre-commit
  2. Add the following to .pre-commit-config.yaml at the root of the repository
  3. Run pre-commit autoupdate and pre-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:

FeatureFor public repositoriesWhat it does
Secret ScanningRuns automatically, freeScans git history on all branches and alerts you to findings
Push protection for usersOn by default at the account levelBlocks pushes containing secrets to public repositories
Push protection for repositoriesRequires GitHub Secret ProtectionBlocks 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:

FileCommitted to the repo?Contents
.envNoThe real values
.env.exampleYesKey names plus dummy values or descriptions
.gitignoreYesExclusion rules for .env and the credentials folder
credentials folderNoKey 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.

Try the fee simulator and get 100 points, no sign-up required Review your phone bill with Rakuten Mobile

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.

  1. Extract just the values from the old .env files into a file with one value per line
  2. Remove blank lines and values too short to be meaningful (like true or 1) that would cause false hits
  3. Keep the value list outside the repository
  4. Use grep -F to match them as fixed strings rather than regular expressions
  5. 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.

CHECK

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.

StepWhat to doWhy
1Revoke and reissue the key or passwordOnce the old value stops working, the damage stops there
2Change the code to read from .envSo you don’t hardcode the new value
3Rewrite history with git filter-repo if neededTo remove the old value from history
4Force push, and contact GitHub Support if neededTo 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
Warning

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

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

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

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.

Try the fee simulator and get 100 points, no sign-up required Check Rakuten Mobile plans and prices

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 .

ABOUT THE AUTHOR
まさきん

Rakuten Group employee · Digital Marketer (holds a Financial Planner qualification)

In his early 40s, part of a dual-income household with four kids. Works as a digital marketer at Rakuten Group, while also using his Financial Planner (FP) qualification to focus on household finances and building assets.

View Profile

Related Articles

RELATED
Gemini (Google AI Pro) Family Sharing Covers Up to 5 Family Members: Setup Steps and Scope Using AI

Gemini (Google AI Pro) Family Sharing Covers Up to 5 Family Members: Setup Steps and Scope

Google AI Pro can be shared with up to 5 family members besides the manager. The Gemini app and storage are shared, while YouTube Premium Lite is for the manager only. Flow limits are shared across the family. I went through the official help to sort out the setup steps and how child accounts are handled.

2026.10.13 · 11 min read
3 Reasons Your AI Subscription Billing Date Does Not Match Your Budget: Claude Upgrades Change It Using AI

3 Reasons Your AI Subscription Billing Date Does Not Match Your Budget: Claude Upgrades Change It

If you upgrade Claude to Max mid-month, your billing cycle resets and that day becomes your new billing date. The yen conversion of dollar charges is set when the data reaches your card company, and statements and budgeting apps lag by a few days. Here are 5 steps to decide the month using the receipt email as your source of truth.

2026.10.13 · 12 min read
Turning Unprocessed freee Accounting Transactions into a Monthly Reconciliation Run: 7:30 on the 5th, 3 Categories, and a Cap of 15 Items Handed to a Human Using AI

Turning Unprocessed freee Accounting Transactions into a Monthly Reconciliation Run: 7:30 on the 5th, 3 Categories, and a Cap of 15 Items Handed to a Human

About 18 unprocessed transactions sat untouched in freee Accounting across 6 weekly checks. Here is my design for a reconciliation run on my home Mac at 7:30 a.m. on the 5th of each month, and where I drew the line: one monthly task for the items that can be confirmed, and one task each, capped at 15, for the ones that need a human decision.

2026.10.12 · 13 min read
Switch to Rakuten Mobile, get up to 14,000 points Try the fee simulator and get 100 points, no sign-up required →
Apply Now →