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.

Pipeline: AST detector → benchmark over 8 OSS repos → one hit in goreleaser → verified → fixed upstream

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

repodomainLOCfindings
caddyweb server / reverse proxy104,6600
lazygitTUI git client141,7540
resticbackup / storage infra88,5250
goreleaserrelease automation92,3571
fzfCLI fuzzy finder33,3060
tasktask runner23,1620
chiHTTP router12,0820
viperconfiguration7,1940

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.