Why Go’s Direct VCS Fetch for Dependencies Beats Package Registries

Martin Tournoij argues Go's direct fetch from VCS offers superior security and auditability over registry-based systems like RubyGems and npm. Recent Go 1.24 tool directives and ongoing supply-chain threats highlight why transparency in dependencies remains essential. The model isn't perfect, yet it sets a high bar.
Why Go’s Direct VCS Fetch for Dependencies Beats Package Registries
Written by Emma Rogers

Martin Tournoij had seen enough. After years writing Go and now working in Ruby, one difference stood out. Go handled dependencies with a clarity and security edge that RubyGems lacked. No central publish step. No detached archives. Just URLs pointing straight to git repositories.

Go’s Model: URL, Tag, Commit

In Go, a dependency lives at a path like github.com/user/pkg. The go command figures out the version control system, pulls the exact tag or commit listed in go.mod, and verifies everything against checksums. That go.mod file doubles as specification and lockfile. It records precise versions for direct and indirect dependencies alike. The Go team designed it this way from the start.

“dependencies are identified by URL, the code is fetched directly from the VCS, and it does this for both direct and indirect dependencies,” Tournoij wrote in his analysis (arp242.net).

Auditing becomes straightforward. Run git log between old and new revisions. Read the commits. Look for anything suspicious. exec.Command in a logging library? It jumps out. For solo developers or small teams this takes minutes. Larger trees demand more time. The process stays transparent.

Contrast that with Ruby. Developers build a .gem archive and upload it to rubygems.org. The published artifact might differ from the source repo. Auditing requires downloading gems, extracting tarballs, and running diffs. Commit history disappears. Subtle changes hide easily. Tournoij tried it. The friction discouraged regular reviews.

But Go isn’t alone in its approach. The language’s module system, introduced in version 1.11 and refined since, draws from earlier lessons. Before modules, GOPATH and vendoring created headaches. Reproducible builds suffered. Security gaps widened. Modules fixed much of that by treating source repositories as the single source of truth.

Official documentation explains the setup. Modules provide a decentralized way to publish code. Developers tag versions in their own repos. Tools handle discovery, versioning, and verification. A proxy like proxy.golang.org speeds things up and adds protection against supply-chain tricks such as left-pad style removals. Yet it falls back to direct VCS access when needed. (go.dev).

Security researchers have documented the pattern. Most supply-chain attacks target the publish step. npm accounts get compromised. Malicious code slips into minified JavaScript or binary blobs. The xz utility attack hid exploits in release tarballs while the git repo looked clean. Event-stream pulled in a tainted dependency only on npm. These incidents share a common thread: the gap between repo and distributed package.

Tournoij calls them package publishing attacks more than pure side-channel exploits. Source repos stay visible. Commits get reviewed. Once code moves to a registry, visibility drops. Compiled or minified assets make inspection even tougher. Ruby faces the same issue, though less severely than JavaScript ecosystems.

Recent developments show the conversation continues. In February 2025 the Go project released version 1.24. It added first-class support for tool dependencies directly in go.mod. No more tools.go workarounds with blank imports. A simple “tool” directive tracks executables. The go get -tool flag adds them. go tool runs them. This keeps build tools versioned alongside application code without mixing them into the main dependency graph. (go.dev/doc/go1.24).

That change addresses a long-standing pain point. Developers had mixed tool versions with app dependencies, risking conflicts. Separation brings clarity. It also reinforces the philosophy: declare exactly what you need, pull it from a trusted source, verify it.

Yet challenges remain. Minimal version selection, Go’s strategy for picking module versions, draws criticism in some circles. It chooses the highest minimum version required anywhere in the graph rather than the latest compatible release. Predictable, yes. But projects can lag on updates if maintainers don’t bump requirements.

Automation tools have stepped in. Renovate and similar services scan for updates and open pull requests. They reduce manual work. Still, the core audit responsibility sits with developers. Go makes that audit practical. Other languages often don’t.

RubyGems tried incremental fixes. In 2024 it introduced cooldown periods for new gems and AI-assisted scanning for critical packages. Helpful in the short term. Tournoij sees them as bandages. The publish model itself, born in an era of FTP tarballs and sparse public repositories, no longer fits. “I probably would have created something similar to RubyGems myself twenty years ago,” he noted. “RubyGems is just designed in a different world.”

He floated ideas for Bundler. A directive to force git sourcing for all gems. Syntax that lists dependencies by repository URL and tag. Indirect dependencies declared explicitly in a block. Gemfile.lock could hold hashes like go.sum. Details would need work. The direction feels promising.

Other ecosystems show movement too. A January 2026 post outlined Go modules for newcomers, stressing reproducible builds and explicit version control. (oneuptime.com). An August 2025 guide highlighted how modules escaped GOPATH limitations and brought clear versioning. (namastedev.com).

Supply-chain worries haven’t faded. Recent X discussions flagged North Korean actors expanding from npm into Go modules and Packagist, even rewriting git history. Defenses must evolve. Direct VCS ties make history tampering more visible, though not impossible.

Go’s proxy adds another layer. It caches and serves modules. Organizations run private proxies for internal code. The GOAUTH environment variable, introduced in later releases, simplifies authentication for private repositories. Everything stays under version control. go.sum ensures tampering breaks the build.

Critics point out imperfections. Not every project tags semantic versions properly. Pseudo-versions based on commit timestamps fill gaps. Large dependency graphs still overwhelm reviewers. Yet the baseline stays higher than registry-only systems.

Consider a typical workflow. A developer adds a package with go get. The tool updates go.mod and go.sum. On CI, go mod download or go build fetches and verifies. No surprise updates. No mysterious binaries. When a vulnerability appears, teams scan the exact commits that introduced it.

Other languages could adapt pieces. Some already support git-based gems or pip installs from repositories. Making it the default, frictionless path demands deeper changes. Registries provide discovery and convenience. They also create single points of failure.

Tournoij doesn’t demand exact copies of Go’s system. He wants auditability. “I want to reliably audit my dependencies like a responsible developer and RubyGems makes it too hard, as do other package managers.”

That sentiment resonates across the industry. As attacks grow more sophisticated, transparency matters more than ever. Fetching from version control won’t stop every threat. It does make many of them far easier to spot before they reach production.

Teams that adopt the practice report fewer incidents. Builds stay consistent across machines. Updates require deliberate review. The overhead exists but feels manageable compared with the alternative: trusting that a uploaded archive matches the advertised source.

Go didn’t invent this pattern. It refined and standardized it at a time when the community needed solutions. The result influences how newer projects think about packaging. Future language designers would do well to study both the successes and the remaining rough edges.

Because in the end, dependencies are code. They deserve the same scrutiny as the lines we write ourselves. When the system makes that scrutiny easy, developers actually do it. When it doesn’t, shortcuts happen. And shortcuts, as recent history shows, carry real costs.

Subscribe for Updates

DevNews Newsletter

The DevNews Email Newsletter is essential for software developers, web developers, programmers, and tech decision-makers. Perfect for professionals driving innovation and building the future of tech.

By signing up for our newsletter you agree to receive content related to ientry.com / webpronews.com and our affiliate partners. For additional information refer to our terms of service.

Notice an error?

Help us improve our content by reporting any issues you find.

Get the WebProNews newsletter delivered to your inbox

Get the free daily newsletter read by decision makers

Subscribe
Advertise with Us

Ready to get started?

Get our media kit

Advertise with Us