Betterleaks: a Go secrets scanner with Expr rules and remote sources
Scan the world (for secrets). | New Sources | Support for sources like GitHub, GitLab, Hugging Face, S3, and more.
At a glance
- What is it?
- Betterleaks scans git history, local directories and hosted platforms for secrets, using Expr filters and BPE tokenization to cut false positives. It is maintained by the original Gitleaks author, and this article covers installation, configuration and where it stops being the right tool.
- Who is it for?
- Adopt Betterleaks if you already run Gitleaks and want Expr-shaped filtering, token-rarity scoring and remote sources such as GitHub, GitLab, Hugging Face and S3 in one binary. Do not adopt it if you need a hosted dashboard, scheduled re-scanning or a managed service; this is a CLI you have to wire into your own CI.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Betterleaks scans that a plain git hook does not
Most secret scanners assume one shape of input: a git repository on disk. Betterleaks treats the repository as one source among several. The README lists GitHub, GitLab, Hugging Face and S3 alongside a local filesystem scan, and the repository layout carries a top-level sources/ directory next to cmd/, detect/ and regexp/, which is where those source implementations live. The README also notes it is easy to add new sources.
The second problem it targets is noise. A regex that matches a 32-character hex string will match build artifacts, minified bundles and test fixtures. Betterleaks attacks that in two places: a prefilter that runs before any regex matching and can bail out on file path or git author, and a post-match filter that sees the candidate secret itself. The README frames the whole feature as a more expressive replacement for Gitleaks' [[allowlist]] system, which is a fair description of the direction even if the two are not drop-in identical.
Who is this for? Platform and security engineers who already accept that scanning is a CI job, not a product. The README names Aikido Security, MegaLinter, Moyai, Entire.io and LeakTK as users, and MegaLinter shipping it out of the box is the clearest signal of the intended integration pattern: a binary invoked by a pipeline, not a service you log into.
How the detection pipeline is wired
The scan has an order, and the order is the point. A prefilter runs first and only sees the attributes map, which describes the resource being scanned (a git patch, for example). Because it runs before regex matching, it is the cheap gate: skip node_modules, skip vendor, skip commits from renovate[bot]. Anything that survives goes to the regex engine, which the README says is backed by ahocorasick keyword filters and re2. The keyword filter matters because it avoids running every rule against every fragment.
Once a rule matches, the filter expression runs with access to both attributes and the finding. The README gives finding["secret"] and finding["match"] as the fields available there. A global filter applies to every candidate; per-rule filters apply to their own rule. Multipart rules add a third layer: they declare nearby component rules under components and read them with expressions like components["rule-id"]?.secret ?? "" or components["rule-id"]?.captures?.group ?? "". Named capture groups from the primary rule surface as finding["captures"].
Two more mechanisms sit on top. Token efficiency filtering uses BPE tokenization, via the tiktoken-go dependency in go.mod, to measure how rare a string is and drop natural-language false positives. Secrets validation makes asynchronous HTTP requests from inside the rule definition using Expr, so a rule can confirm a credential is live rather than merely well-formed. That last one is genuinely useful and also the feature most likely to surprise you in a regulated environment, since it means the scanner touches third-party endpoints.
One compatibility note from the README: filtering and validation logic were previously implemented in CEL. Existing CEL-shaped configs are still accepted, but new configs should use Expr.
Install Betterleaks and run a first scan
The README lists package managers, containers, Go and source builds. Homebrew is the shortest path on macOS or Linux:
brew install betterleaksFedora users get a native package. The README does not mention a Windows package manager, so on Windows the container or the Go install is the route the documentation actually supports:
sudo dnf install betterleaksIf you want it inside a pipeline image, the published container is pulled from GitHub's registry. The Dockerfile shows the entrypoint is the betterleaks binary itself, so any subcommand can follow the image name:
docker pull ghcr.io/betterleaks/betterleaks:latestWith a Go 1.25 toolchain, the module installs directly. The go.mod file declares go 1.25.0 and toolchain go1.25.12, so an older toolchain will refuse the build:
go install github.com/betterleaks/betterleaks@latestNow a first real scan. Point it at a repository you own and raise verbosity so you can see what matched:
betterleaks git /path/to/repo -v --source-workers=16The --source-workers flag is the parallelization knob the README shows; it appears on the git subcommand. For a directory that is not a repository, the dir subcommand takes the same shape:
betterleaks dir /path/to/file/or/dir -vRepository scans are not the only target. A GitHub organization or user is a first-class argument, and --include narrows which resource types are fetched:
betterleaks github https://github.com/betterleaks
betterleaks github https://github.com/cooluser123456789 --include issues,prs,actions,releases,gistsYou can also aim at a single resource. The README's example scans one pull request; the note next to it says the description is excluded by default and only comments are scanned unless you change that.
Writing prefilters and filters in betterleaks.toml
Configuration is TOML, and the top-level keys are prefilter and filter. The README's example global prefilter skips binary and image extensions, node_modules and vendor directories, and any commit authored by renovate[bot]. Note the syntax: filter.matchesAny and filter.containsAny are helper functions, and the path patterns are backtick-quoted regexes inside a list.
prefilter = '''
filter.matchesAny(attributes["path"], [
`(?i)\.(?:bmp|gif|jpe?g|png|svg|tiff|pdf|exe)$`,
`(?:^|/)node_modules(?:/.*)?$`,
`(?:^|/)vendor(?:/.*)?$`
])
|| attributes["git.author_name"] == "renovate[bot]"
'''The global filter below runs for every candidate secret and drops the placeholder strings that make up most of the noise in a first run. It reads finding["secret"], which is the post-match value rather than the raw fragment:
filter = '''
filter.containsAny(finding["secret"], [
"EXAMPLE",
"CHANGEME",
"YOUR_API_KEY_HERE",
"0000000000000000"
])
'''Rules themselves live in an array of tables under [[rules]], each with an id and description. The README points at the default config in config/betterleaks.toml for worked examples and at docs/config.md for the full key list. There is also a hosted playground at betterleaks.com/playground for testing rules before committing them.
One operational warning comes straight from the README: if you run Betterleaks in production, maintain your own config instead of extending the upstream default. The stated reason is that a pinned rule set stays stable across upgrades and lets you review new upstream rules before adopting them. Take that seriously. The repository's own Makefile regenerates config/betterleaks.toml from cmd/generate/config, so the default config is generated output that will move.
Where Betterleaks is the wrong tool
The README recommends pinning your own config, and that recommendation is also the project's biggest operational cost. A scanner whose value comes from its rule set requires someone to review rule changes. If nobody owns that, you get one of two failure modes: a config frozen so long it misses new credential formats, or an unpinned config that starts flagging new patterns after an upgrade and breaks a build.
Validation is the second sharp edge. Rules can make asynchronous HTTP requests to check whether a detected secret is active. That is a real capability, and it also means the scanner sends data derived from your repositories to third parties. In an air-gapped environment, or one where egress is audited, those rules have to be identified and disabled. The README documents the mechanism; it does not document a global kill switch for it.
Then there is scope. Betterleaks has no server, no scheduled rescans and no dashboard. The repository is a Go module with a cmd/ tree and a single binary entrypoint, and the Dockerfile sets that binary as the entrypoint. Everything about scheduling, alerting, deduplication across runs and retention is left to whatever invokes it. If what you actually need is continuous monitoring of a large organization with a triage UI, this is a component of that system, not the system.
Finally, the remote sources are only as complete as their implementations. The README advertises GitHub, GitLab, Hugging Face and S3, and the GitHub examples show an --include flag for narrowing resource types. The README does not document rate-limit handling or pagination behaviour for those sources, so a very large organization scan is something to measure on your own account before you commit to it in a pipeline.
Betterleaks versus Gitleaks: the actual difference
The obvious comparison is Gitleaks, and the README makes it directly: Betterleaks is maintained by the people who made Gitleaks, including the original author. That lineage explains why the config format will look familiar and why the README translates one feature into Gitleaks terms, calling Expr filtering a more expressive [[allowlist]] system.
The difference in approach is where the filtering happens and what it can see. Gitleaks' allowlist model is largely declarative: you enumerate paths, regexes and stopwords. Betterleaks evaluates expressions, and the expressions run at two distinct stages with different inputs. The prefilter sees only attributes and runs before regex matching, so it can skip work entirely. The filter sees attributes plus the finding, so it can reason about the matched value. Splitting the two stages is the design choice that matters, because it means an expensive decision never has to be made in the cheap stage.
The second difference is the source model. Gitleaks scans git repositories and directories. Betterleaks adds hosted sources, with the sources/ directory holding the implementations, and the README explicitly invites new ones. If your exposure is mostly in git history, that difference buys you little. If secrets leak into issues, PR comments, releases, gists or action logs, it buys you coverage that a repository scanner cannot reach.
The third is token efficiency filtering. BPE tokenization, via tiktoken-go, is used to score how non-human a candidate string is. It is a heuristic layered on top of regex, not a replacement for it, and the README links a blog post on generic secrets detection for the reasoning. Treat it as a noise reducer you tune, not a correctness guarantee.
Licence, maintenance and upgrade cost
Betterleaks is MIT licensed. In practice that means you can embed the binary in commercial pipelines and modify it, provided the licence text and copyright notice travel with redistributions. This is not legal advice; if you vendor the code into a product, have counsel read the LICENSE file at the repository root rather than relying on the SPDX identifier alone.
The repository is not archived and the last push was on 2026-08-18, the same day as the v1.8.1 release. Releases v1.8.0 and v1.7.4 landed earlier in August 2026, so the release cadence in the weeks before that date was close. Judge the project on that cadence rather than on any claim about how actively it is developed.
Upgrade cost concentrates in two files. The first is your own betterleaks.toml, which the README tells you to maintain separately from the upstream default precisely so upgrades do not change your findings. The second is the generated config: the Makefile has a target that runs go generate ./... to rebuild config/betterleaks.toml from cmd/generate/config, so the shipped default is a build artifact. If you have copied rules out of it, diff them against the new generated file after each release rather than assuming the rule semantics held.
There is one more upgrade consideration worth naming. CEL-shaped configs are still accepted for compatibility, but new configs should use Expr. If you migrated a CEL config, the compatibility path is a bridge, not the destination, and it is the part of the config most likely to be dropped in a future release.
Editorial conclusion
Adopt Betterleaks if you already run Gitleaks and want Expr-shaped filtering, token-rarity scoring and remote sources such as GitHub, GitLab, Hugging Face and S3 in one binary. Do not adopt it if you need a hosted dashboard, scheduled re-scanning or a managed service; this is a CLI you have to wire into your own CI. Before rolling it out, verify three things: that your pinned config still passes against your own repositories, that the source you want to scan is listed in the scanning doc, and that the post-regex validation rules you write do not fire HTTP requests against production credentials you would rather not touch. The README's own advice applies here: keep your own betterleaks.toml rather than extending the upstream default, so upstream rule changes do not silently alter what your pipeline reports.
Frequently asked questions
How do I install Betterleaks?
The README lists Homebrew (brew install betterleaks), Fedora (sudo dnf install betterleaks), the container image ghcr.io/betterleaks/betterleaks:latest, go install github.com/betterleaks/betterleaks@latest, and a source build via git clone followed by make build. The go.mod file requires Go 1.25.0 with toolchain go1.25.12.
Can Betterleaks scan GitHub organizations and GitLab groups?
Yes. The README shows betterleaks github https://github.com/betterleaks for an organization and betterleaks gitlab https://gitlab.com/mygroup for a GitLab group, and an --include flag such as --include issues,prs,actions,releases,gists to narrow which resource types are fetched.
How does Betterleaks reduce false positives compared with Gitleaks?
It splits filtering into a prefilter that runs before regex matching with access only to attributes, and a filter that runs after a match with access to the finding, such as finding["secret"] and finding["match"]. The README describes this as a more expressive replacement for Gitleaks' [[allowlist]] system, and adds token efficiency filtering using BPE tokenization to drop natural-language matches.
Does Betterleaks need a config file?
The README points to the default config at config/betterleaks.toml and to docs/config.md for the full key list, and recommends that production users maintain their own config rather than extending the upstream default so the rule set stays stable across upgrades. Prefilters and filters are written as Expr, with CEL-shaped configs still accepted for compatibility.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/betterleaks-betterleaks)