case study
Finding a real timing bug in goreleaser — by measuring, not guessing.
We benchmarked one of our security detectors against ~503,000 lines of popular Go code. It produced exactly one hit. That hit was real, and it's now fixed upstream: goreleaser#6813.
the detector
What it looks for
Secrets and tokens must be compared in constant time.
A plain string equality check (==) against a secret leaks
information through timing — character by character, an attacker on
shared infrastructure can learn how much of the token matched.
Our detector walks the Go AST and only fires when a
value with a traceable credential source (request
headers, environment variables) is compared with plain equality — never
on identifiers that merely look like key or
token. Getting that wrong is how linters earn their mute
button.
the benchmark
~503k LOC, 8 repositories, zero tolerance for noise
| repo | domain | LOC | findings |
|---|---|---|---|
| caddy | web server / reverse proxy | 104,660 | 0 |
| lazygit | TUI git client | 141,754 | 0 |
| restic | backup / storage infra | 88,525 | 0 |
| goreleaser | release automation | 92,357 | 1 |
| fzf | CLI fuzzy finder | 33,306 | 0 |
| task | task runner | 23,162 | 0 |
| chi | HTTP router | 12,082 | 0 |
| viper | configuration | 7,194 | 0 |
Seven clean repos prove the detector isn't just pattern-matching the words "token" or "key". One hit means we could afford to look at it properly.
the finding
goreleaser, internal/client/gitlab.go
checkUseJobToken
compares a user-configured GitLab token against the
CI_JOB_TOKEN environment variable to decide which API
client to use. Both are credentials; only one side was compared in
constant time:
// before ciToken := os.Getenv("CI_JOB_TOKEN") ... return token == ciToken // after — goreleaser#6813 ciToken := os.Getenv("CI_JOB_TOKEN") ... return subtle.ConstantTimeCompare([]byte(token), []byte(ciToken)) == 1
Honest framing: this is hardening, not a zero-day. Exploitability depends on running goreleaser on shared infrastructure where a co-tenant can time your GitLab client. The maintainers merged it as a correctness improvement — which is exactly the right response to this class of finding.
why this page exists
This is what "rules backed by evidence" means
deslop ships rules that steer coding agents, and gates the patterns we can prove agents emit. Every gate earns its place the same way this one did: measured against half a million lines of real code before it's allowed to fail anyone's build.
The same discipline powers our mining service: we run detectors like this one against your repository history, and every rule that reaches your CI carries its verification with it.