We've all done it. You commit a .env file by accident, catch it a few commits later, delete it, and breathe a sigh of relief. Except... it's not gone. It's still sitting in your Git history, one git log -p away from anyone who clones the repo.
That's the problem GitHub Secrets Watcher is built to catch.
What it does
GitHub Secrets Watcher is a command-line tool written in modern C++20 that scans your GitHub repositories - public or private (with a token) - and walks back through commit history looking for accidentally committed environment and config files: .env, config.js, and similar patterns that often carry API keys, database credentials, or other secrets.
For every hit, it gives you a direct link to the exact commit where the file appears, so you're not stuck digging through history manually to remediate it.
Importantly, it's strictly read-only. It never modifies, deletes, or rewrites anything in your repositories - it just reports what it finds so you can decide what to do next (rotate the secret, scrub the history, etc.).
Key features
- Scans all repos for a given GitHub username, public by default, private too if you pass a token
- Walks a configurable depth of commit history across branches
- Skips noisy directories like
node_modules,.git, anddist - Validates commit hashes before generating links, so you never get a broken URL
- Multi-threaded scanning - set the thread count to match your hardware
- Three output formats: human-readable text (with color-coded terminal output), JSON, or CSV
- Stream-based processing to keep memory usage low even on large histories
- Modern, safe filesystem operations: Uses C++20 std::filesystem for all path handling, eliminating platform-specific path issues and improving security
- Direct Git integration: Leverages libgit2 for efficient, safe repository operations without spawning subprocesses
Example output
[1/7] Scanning: ccas-website
[INFO] Cloning repository (depth=100)...
[INFO] Scanning history...
[WARN] Found 5 potential environment/configuration files:
- client/assets/js/config.js
[LINK] https://github.com/.../blob/c7e0fba.../client/assets/js/config.js
Each finding links straight to the offending commit, so remediation is one click away instead of a history-diving expedition.
Getting started
git clone https://github.com/Gerald-1234/github-secrets-watcher
cd github-secrets-watcher
mkdir build && cd build
cmake ..
make
Then run a scan:
./github_secrets_watcher scan --username YOUR_USERNAME
Add a token for private repos and higher API rate limits:
./github_secrets_watcher scan --username YOUR_USERNAME \
--token YOUR_PERSONAL_ACCESS_TOKEN --include-private
You can tune the scan with --depth, --max-repos, --threads, --format, --verbose, repo(s) <repo name(s)> and --output.
Why C++20
This isn't a wrapper around git log piped through grep — it uses libcurl for GitHub API calls, libgit2 for direct Git repository access, nlohmann/json for parsing, and C++17 std::filesystem for safe, portable path operations, with modern C++20 features throughout. It builds cleanly with CMake or a provided build script on Linux, macOS, and Windows (via MSYS2).
Try it out
It's MIT-licensed and open source. If you've got repos going back a few years, there's a decent chance something's buried in there that shouldn't be — I'd genuinely be curious what people find.
🔗 github.com/Gerald-1234/github-secrets-watcher
Feedback, issues, and PRs welcome.
Update - Sep 3:
Based on feedback from the community, I’ve replaced the filesystem handling with std::filesystem and am now using libgit2 for Git operations instead of popen. Thanks to @hailiang194 for the suggestions!
Top comments (4)
That's great idea.
I have some suggestion for your code
filesystem, it helps you perform the operations of file system and of course C++20 has included itThanks for the suggestions.
I’ll definitely look into using
std::filesystemand especially learn how to uselibgit2to replace thepopenapproach. That sounds like a much cleaner and more interesting way to handle the Git operations. 🙌Nice, and read-only is the right default for a scanner. The thing that keeps this tool necessary is that
.envexists in the working tree at all; onegit add .from a new contributor and it's in history forever.The complement to detection is removing the file class: commit an
.env.schema(names + types, no values), gitignore is no longer load-bearing because there's nothing to ignore, and values get injected at run time. That's the model I built penv around. A scanner like yours plus a no-file-on-disk workflow covers both directions: past leaks and future ones.Some comments may only be visible to logged-in visitors. Sign in to view all comments.