Back to Blog

Your Dependency Bot Is a Liability

Dependabot opened thousands of PRs for a vulnerability that affected nobody. The real fix isn't more automation - it's smarter automation.

EngineeringSecurityDevOpsDependency ManagementGo
February 21, 2026
4 min read

Filippo Valsorda published a security fix for filippo.io/edwards25519 this week. One method, MultiScalarMult, produced incorrect results when the receiver wasn't the identity point. The fix was a single line change.

Then Dependabot woke up and opened thousands of PRs across the Go ecosystem.

Here's the thing: almost nobody calls MultiScalarMult. Most projects depend on edwards25519 transitively through go-sql-driver/mysql, which has 228k GitHub dependents. None of them touch the affected code path. Dependabot doesn't know that. It can't know that. It operates at the module level, not the symbol level.

One of those PRs landed on the Wycheproof repository, which doesn't even import the affected package. It imports filippo.io/edwards25519/field, a completely separate sub-package. Dependabot saw the module in go.mod and panicked anyway.

The noise-to-signal problem is structural

This isn't a bug in Dependabot. It's the architecture. Dependabot checks whether your go.mod (or package-lock.json, or Gemfile.lock) pins a module version that has a known CVE. That's it. No static analysis. No call graph traversal. No reachability check.

The result: engineers spend time reviewing, testing, and merging PRs for vulnerabilities their code never reaches. Multiply that across a company with 200 repos and you're burning hundreds of engineering hours per quarter on phantom risk.

I've seen this pattern at every company I've worked at. A Dependabot PR arrives. Nobody knows if the vulnerability actually affects production. The safest-feeling move is to merge it. But "safe-feeling" and "safe" are different things. That merge triggers CI, which triggers deploy pipelines, which introduces actual change risk into a stable system. You've traded a theoretical vulnerability for a concrete deployment.

What reachability analysis actually looks like

Go's govulncheck does what Dependabot should do. It uses static analysis to build a call graph from your code, then checks whether the vulnerable symbol is actually reachable. For the edwards25519 case:

typescript
$ govulncheck ./...
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 1 vulnerability in packages you import and 2
vulnerabilities in modules you require, but your code doesn't appear
to call these vulnerabilities.

Zero noise. The tool found the module dependency, traced the call graph, and determined the vulnerable function is never called. Done in seconds. No PR. No review cycle. No deploy risk.

This is the difference between module-level scanning and symbol-level scanning. It's also why Go's vulnerability database includes package and symbol metadata, not just version ranges.

The broader lesson beyond Go

Most ecosystems don't have govulncheck. JavaScript's npm audit is notoriously noisy. Python's ecosystem is fragmented across pip-audit, Safety, and Snyk. But the principle holds everywhere: version-range scanning is the wrong abstraction for vulnerability detection.

If you're running a Node.js monorepo, here's what actually works:

  1. Drop Dependabot security alerts. Replace them with a scheduled CI job that runs your actual test suite against bumped dependencies. If tests pass, auto-merge. If they fail, file a ticket with context.
  2. Use a scanner that understands your code. Snyk's reachability analysis exists (behind a paywall). Socket.dev does supply chain analysis. Neither is perfect, but both are better than version-range matching.
  3. Separate security updates from version bumps. Dependabot conflates the two. A security patch and a major version upgrade require completely different review processes. Treating them identically means the critical one gets the same attention as the routine one, which means neither gets proper attention.

The cost nobody measures

Every false-positive security alert has a cost beyond the engineering time to review it. It trains your team to ignore alerts. When the real CVE hits, the one where your code actually calls the vulnerable function, it sits in the same queue as the last fifty phantom PRs. Alert fatigue is a vulnerability in itself.

I've watched teams go from "we review every Dependabot PR within 24 hours" to "someone batch-merges them on Friday" in about three months. That second state is worse than having no scanner at all, because it gives you the illusion of coverage.

The fix isn't more automation. It's automation that understands what your code actually does.

Share

Get new posts in your inbox

Architecture, performance, security. No spam.

Keep reading