Rendered at 23:50:35 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
jchw 1 days ago [-]
People keep replying seeming to miss what makes Go's solution so good. I think it deserves more attention.
In many cases I think it's impossible to avoid the pain that comes with decentralization without defeating the point. It's challenging because certain things are hard to do decentralized so it's easier to just give up and rely on central authorities and components.
When you install Go, it does just that, to some approximation; it will try to fetch from the module proxy first and fall back after that fails. But what makes Go different and more interesting is that you can just turn that off with no consequence. As long as all of the Git hosts are up, you can still use Go just fine with no proxy. I'm pretty sure Nix builds do this by default with Go so I believe most dependencies are usually online. That means the proxy approach and the decentralized approach are both fully redundant with eachother, while allowing the decentralized VCS hosts to remain the source of truth.
Go also separately has the sumdb to enforce immutability, so you can know that when you go grab a specific package that the tag name corresponds to one and only one exact source code. This is also optional, and even without it you get the benefit of local sum checking via your own cache and go.sum files in source checkouts. But having this be separate is great because it also means you could use another third party module proxy or a third party sumdb, or use just the sumdb and no proxy. It gives you many options to not have to rely on a single party while still getting some of the benefits of centralization.
Systems like this are rare. Many systems try very hard to have the cake and eat it too, but it usually fails somewhere. For me Go's approach works, and that is worth some attention.
throwaway27448 23 hours ago [-]
All package managers I've used allow specifying where the package is pulled from. What am I missing? What ecosystems are you comparing go to?
jchw 22 hours ago [-]
Sure, you can tell your other package managers to pull a module from VCS, usually with some limitations and negative side effects. The Go module syste is not the same model. You can, for example, run an NPM proxy that caches locally, but what you're missing is that with Go, the proxy part is the centralized part. There is no NPM. There is no crates.io, no PyPI, no RubyGems, no Packigist, no PECL, no CPAN. The proxy's entire job is just to stand in for requests that would be made to individual VCSes using their original source repositories. The sumdb's entire job is to ensure that whether proxied or not, the source code you get isn't tampered with. The source of truth remains decentralized, in the individual VCSes.
With NPM, uv, composer, Cargo, etc. you can indeed choose to make your dependencies pull from VCS, but:
- Not having universal module proxies with caching means you will run into the usual problems. Repos can disappear, history can be rewritten, hosts can go down. Even if you invented an equivalent caching service, without making it an ecosystem default it won't achieve the same effectiveness as Go because less would be in the cache.
- Because it isn't an ecosystem standard, dependencies you pull would then still reference dependencies from centralized repos anyways, more or less defeating the point. You'd have to go and override every recursive dependency to really make it decentralized.
(And again, it usually comes with other downsides depending on the specific ecosystem. Performance is a big one, the module cache definitely helps make fetching dependencies in Go faster.)
Not having centralized package repos comes with its ups and downs, but the lack of one existing means there are a limited number of interesting attacks you can really pull in the Go ecosystem and they are heavily tamper-evident. You can try to poison the module cache with a given revision for a dependency with malicious code then overwrite it with an inoccuous commit, but this will probably be detected when someone (possibly in Nixpkgs or another packaging system) runs GOPROXY=direct and the sumdb doesn't line up. So the least noticeable thing you can really do is just leave your malicious code in the repo. Go also has the kind of nice property that fetching and building modules is supposed to be safe with untrusted code. That's pretty good, it would be hard to do better than that.
It also means there really isn't any central namespace to poison. You can still trick someone into downloading and using malicious software but you're mostly limited to doing it the old fashioned way. Again, hard to do much better than that.
While everyone has been losing their minds over supply chain issues, the Go ecosystem has been concerned but less so. It's still a huge risk still, dependencies can get compromised still, but attackers have no real central target to go after and get a bunch of damage at once. Nothing realistical, anyway. So, it's back to just trying to pwn individual GitHub repos.
throwaway27448 17 hours ago [-]
> There is no NPM.
any package manager can be redirected towards particular sources. What is unique about go? I sense, though this is not articulated through your communication, that the source of the package is directly specified in the source code requiring the package. Is that what you're trying to say—source pinning is evident in the source?
jchw 10 hours ago [-]
What's unique about Go is the lack of a centralized package repo. You are saying "I could point NPM to another registry". I am saying, Go doesn't have a registry and you can't really point it to one. I hope it is apparent that these are two extremely different statements. The fact that it is pinning source to source is just the approach that they take to get there.
And again, if you just bolted a solution onto the side of an existing ecosystem it wouldn't accomplish as much. "Technically this could be done elsewhere" is true, but I hope you know I know that. The point is that Go deployed a mostly decentralized package management solution to the entire ecosystem as the primary and essentially only way to manage packages with Go. You actually could approximate what Go does using NPM, sans the caching module proxy part, using VCS URLs as dependencies, but the "approximate" and "sans caching module proxy part" is doing some heavy lifting.
There isn't really anything that does what Go does exactly. The closest example to me is OCI images. Like Go modules, their identity is essentially a sort of URI. However, it isn't a language package manager and has fairly different challenges, and doesn't really seem to need the caching proxy stuff due to the relatively few registries (and running it would be very expensive due to image sizes.) So I stand by with my belief that what Go does here is pretty unique.
SkiFire13 18 hours ago [-]
> The source of truth remains decentralized, in the individual VCSes.
That's not entirely true, if the VCS's tag changes the proxy might not pick it up.
zer00eyz 23 hours ago [-]
> When you install Go, it does just that, to some approximation; it will try to fetch from the module proxy first and fall back after that fails.
Go is telling you that your VCS has a deficiency, without saying it out loud.
That proxy should be your own repo... but git sub modules, sub trees, sub directories are non starters for 99 percent of cases...
Git is an amazing tool, and if you want to manage your development like the linux kernel its dam near perfect. Most orgs dont work that way, and the tools and machinations that we have built around these shortcomings are rather burdensome.
I long for google to productize piper for the rest of us.
jchw 22 hours ago [-]
To be honest with you, I don't know what you are suggesting. Piper is a more scalable Perforce clone. That's cool and all, but I don't see how it solves the problem that hosts can be unreliable and that data fetched from the internet can be changed later. Doesn't seem to me like there's anything a VCS itself can do to change this.
You maybe could invent a fully decentralized peer-to-peer VCS hosting system that uses Blockchain(TM) technology to ensure integrity, or something. But... You'd probably still want a cache, perhaps even moreso than before, for performance reasons, even if you really truly did solve the availability and integrity problems.
SkiFire13 18 hours ago [-]
> and that data fetched from the internet can be changed later
So do you want the VCS to be the source of truth or not?
jchw 8 hours ago [-]
First of all, can you just simply not speak in gotchas? I don't even like engaging with this sort of thing. It's a valid question to ask how VCS is the source of truth in this case, but the way you're responding suggests you're not interested in discussing it productively, just a shit-flinging where we argue semantics about what it means for something to be a source of truth, without regards to what the actual important implications are supposed to be. I'm going to try to interpret this in good faith but I feel like I'm probably wasting my time doing so.
Yes, the VCS is still the source of truth, ultimately nothing in the cache or sumdb exists without having existed in the VCS source first. The sumdb is an integrity mechanism bolted on top, and then the module proxy cache is an availability mechanism bolted on top.
Consider the following: let's say Kubernetes depends on some library you have Git access to. So you, being evil, rewrite the tag Kubernetes currently uses to contain malware. There are two possible scenarios:
- By default: the user is using a module cache, so changing a tag that already existed is no different than a Git host being pulled offline from the perspective of the user. The cache continues to serve the original source code, no malware delivered.
- The decentralized case: a user disables both sumdb and the module cache. Guess what? The pull fails. The pull fails because the project has a go.sum file and your tag doesn't match it.
Let's say someone compromised Google's sumdb or module proxy cache. Same basic thing: the go get will fail, because of the local integrity checks.
See, it really changes nothing. The module proxy cache essentially just ensures that a module source code that was once available remains available indefinitely so that you can continue to build old software exactly and securely.
The only place where behavior ever changes meaningfully (as in, not just "available or unavailable") is when go getting a new package or package update. If you have GOPROXY=direct, the poisoned fetch will fail because of global sumdb. If you disable that too, it may still fail because of local sumdb. But if not, you may get someone to pull your mutated tag. (And then the resulting go.sum would fail for other people.)
If you knew for absolute certain that nobody had ever pulled the package before, then theoretically mutating an existing tag would be safe. However, I call shenanigans on this: if you knew for sure nobody had ever pulled it, how in the world did it end up in the sumdb and module proxy cache? Granted, it may have been because of your own CI or something, but still, it stands to reason that this mechanism is introducing a highly desirable property. If a given tag was available once with a given source code I would want to be able to pull it again later and know it isn't tampered with.
In general, I have never seen it interpreted that the possibility of a stale cache changes what the canonical source of truth is. I've also never seen anyone argue that a lockfile changes the canonical source of truth. The Go sumdb being a global integrity mechanism does make it fairly unique, but I don't see it as being categorically different than the integrity checks from lockfiles, it's just that but across multiple users.
Even if you don't like Go's implementation particularly, I still think it is highly worth copying. You can always change a few things. Like for example, you could make module fetch operations not backed by a local checksum skip the global module proxy cache as a safeguard against the proxy itself, only allowing it to be trusted once you already have evidence that the sumdb and canonical source currently agree by virtue of already having a sum.
Sorry I have dropped another text wall, I don't have time to make it shorter. It will happen again.
(I realize that I have ranted in two directions here, but my intention was to 1. Argue that the properties introduced by the design are actually important and desirable, 2. Then argue semantics about whether it changes what one would consider to be the canonical source of truth, second mainly because I consider this to be unimportant. I mean it could be discussed further but, there really should be a point to it other than just semantics.)
Cedricgc 24 hours ago [-]
IIRC Go had to deal with a bunch of issue related to their initial source based package management system. The URL imports remain, but I believe fetches are served through the pkg.go.dev proxy now.
Andrew Nesbitt has a good write [0] up on why using Git as a database is a bad idea
Most of those deal with downloading large centralized indexes using git, not individual packages.
> The problem was that go get needed to fetch each dependency’s source code just to read its go.mod file and resolve transitive dependencies. Cloning entire repositories to get a single file.
Then it needs to compile the module, right? It's not downloading it twice.
> Grab’s engineering team went from 18 minutes for go get to 12 seconds after deploying a module proxy.
> ...
> GOPROXY became the default in Go 1.13.
The way this is written it sounds like go builds were extremely slow (18 minutes) so go adopted a proxy to speed things up. But Go 1.13 was released in 2020, and the blog post about 18 minutes is from 2023. There's no link to anything about GOPROXY.
The vcpkg example also isn't a git issue, it sounds like just bad engineering from Microsoft.
I'm willing to believe there are issues with using git, but that article is a letdown.
It seems to imply the reasons were optimization and to protect from disappearing dependencies. It doesn't imply that there were any hidden dangers to git.
I think it's very possible that google simply thinks speed is more important than decentralization. And for google's purposes, a google-owned cache is probably a safe enough hedge against disappearing repos (read: if you're not google you'd need to evaluate this risk yourself).
Edit: Discussion from the time here: https://news.ycombinator.com/item?id=20837753 - in that thread too people don't really know why the proxy was needed (or like, "is it really just for speed? but it isn't particularly slow right now"). One additional reason is analytics collection.
cygx 1 days ago [-]
Not sure this is the right solution for 2 reasons:
First, I'm uncomfortable with making a package creator's VCS provider part of the language's module infrastructure.
Second, not all files under version control necessarily belong in a tarball, and not all files in a tarball necessarily need to be under version control.
However, the point that there should be an easy way to track changes in your dependencies is well taken. One possible approach would be that publishing a packge boils down to importing the files that normally would go in your tarball into a community-controlled VCS.
Charon77 21 hours ago [-]
Archlinux has this separation of official packages and the user submitted AUR, which has never been something that is officially supported by design.
Having big names maintain their own VCS is a good thing. There can be a centralized system to index / search packages just for findability. And this centralized system doesn't have to be anything special just like how anyone could use any search engine
g-b-r 24 hours ago [-]
> First, I'm uncomfortable with making a package creator's VCS provider part of the language's module infrastructure.
Why can't you store a (possibly shallow) clone of the repository in the "package" registry?
> Second, not all files under version control necessarily belong in a tarball
How likely is it that files under version control but "not belonging in a tarball" (not needed to compile the current version) take so much space to justify making a tarball?
At least if you make a shallow clone.
> not all files in a tarball necessarily need to be under version control
Which wouldn't (besides huge files that can be handled with git-lfs and similar)?
If you have configure in mind, let's not do it please.
The risks introduced by including them vastly overcome their convenience, in my opinion.
And maybe let's move towards getting rid of autotools
cygx 23 hours ago [-]
Why can't you store a (possibly shallow) clone of the repository in the "package" registry?
Yes, that would be an option.
As to your question, you generally want to version control input files, but distribute the generated artifacts. Some possible scenarios would be documentation, data tables in source or binary form, generated code including maybe even configure scripts if you cannot avoid it (though if I had to 'vendor' 3rdparty code that uses autotools, I think I probably would just unpack a tarball and commit the relevant artifacts).
g-b-r 22 hours ago [-]
If you distribute generated artifacts you're not really distributing the source (and thus xz can happen)
Even for documentation, you'd think it would be harmless to generate it, but it's far from a given that the tools (e.g. browsers) used to read it are safe from malicious documentation files
cygx 22 hours ago [-]
it's far from a given that the tools (e.g. browsers) used to read it are safe from malicious documentation files
Generated HTML files are potentially easier to audit than the scripts/toolchains used to generate them on an end user's machine if you do not pre-generate them.
Off the top of my head, other things I've done is committing RELAX NG *.rnc files, but shipping *.rng files, or generating C header files for various types of data (think `xxd -i` in case of binary files, but also just large chunks of plain text that gets wrapped into a C string).
g-b-r 22 hours ago [-]
Hmm, potentially, the problem is that hardly anyone ever audits packages, while at least someone occasionally gives a look at a repository history
Can't the things you list be part of the build scripts?
cygx 21 hours ago [-]
Sure - but running build scripts on an end-user's machine requires the user to have all relevant tools installed, and isn't exactly reducing the attack surface...
I still like the idea of shipping tarballs that include generated files instead of pulling input files from source control. As mentioned, the first thing that came to mind to make things easier to audit is to stick their contents into a community-controlled VCS.
g-b-r 20 hours ago [-]
> Sure - but running build scripts on an end-user's machine requires the user to have all relevant tools installed, and isn't exactly reducing the attack surface...
Well, that's what building from source means; you could maybe distribute some compiled files for those who prefer them, and are willing to take the risk.
But you seem to be arguing that having users compile their software themselves doesn't increase their security?
> As mentioned, the first thing that came to mind to make things easier to audit is to stick their contents into a community-controlled VCS.
True
cygx 15 hours ago [-]
But you seem to be arguing that having users compile their software themselves doesn't increase their security?
On the contrary! I gave examples where source files get generated. This happens in one of two places: Either when the maintainer publishes a new version, or every time an end user builds the package.
I'm arguing for the former, you're arguing for the latter. There are pros and cons to either approach. Some cons for the latter:
First, the build process becomes more brittle, as every end user now has to install the necessary tools. In case of the project containing RELAX NG schemas I alluded to, this would require a recent version of Perl, a Java runtime, and the Trang utility written in Java. The alternative? Just shipping a single XML file.
Second, the build process often doesn't become more, but less auditable: Instead of just reviewing the source files that actually get compiled, you now have to track down how they get generated, and review all the scripts that do so.
tikkabhuna 1 days ago [-]
There’s no perfect solution here. Publishing to a separate registry can survive a Git repo rename, migration or deletion. Locking into a Git host seems undesirable. By separating VCS and registry they can offer different feature sets. There’s also nothing stopping someone from publishing to multiple registries.
klntsky 1 days ago [-]
The almost perfect solution is Nix
NewJazz 1 days ago [-]
Why almost?
iririririr 1 days ago [-]
if you mean nix the state declaration, it is a ilusion. when you have packge for debian 12... you just install it. when you have debian 13, you need the package for debian 13 and there's no way around it. nix lies that you don't have to worry about all that. which is only as true as is true with packages. if you can replace -12 with -13 in a non nix setup, your nix "package x" will still work. otherwise you will have to deal with it, just with more layers.
if you mean nixos, that's just starting yet another distro maintenance issue from scratch
tiffanyh 1 days ago [-]
I’m super interested in your comment about nix but am not following your example.
Would you mind elaborating why (presumably) nixos (since you gave a Debian example), doesn’t help with this.
iririririr 10 hours ago [-]
it is a distro. it packages code from upstream. it have the same problems as other distros when it came from depencies and managing breaking changes.
chickensong 1 days ago [-]
FWIW, Go provides a mechanism to abstract the import source, so you can use a vanity domain under your control that resolves to your host of choice.
nijave 1 days ago [-]
Not sure relying on a bunch of various VCS to stay online is necessarily a great approach either.
I think go is also a little more amenable to source library distribution since there's a pretty broad pure go ecosystem. For interpreted languages, a lot of performance sensitive stuff tends to be offloaded to arbitrary compiled languages so you end up needing a bunch of different toolchains to get everything working. A statically linked binary library is a useful abstraction layer.
theamk 1 days ago [-]
2nd paragraph of blogpost:
> A hash of all files is checked against known hashes on sum.golang.org to prevent tags from being replaced, and it uses a proxy to prevent repos from being left-pad’d.
there is no need for 3rd party hosts to stay online. It's the best combination of de-centralized and centralized approaches.
Re pre-buiild packages: Python makes a difference between source and binary wheels. This approach is obviously for source wheels only.
arcatek 1 days ago [-]
Packages are typically different once published than they were inside their original repositories. Call it transpilation, build, compilation, packaging, etc, most popular projects require some level of support for dynamic code execution before reaching their usable state.
As much as I'd have liked Git to be a viable option compared to centralized registries, last couple of years demonstrated running arbitrary commands during install is too much of a risk for it to work at scale.
AlotOfReading 1 days ago [-]
...most popular projects require some level of support for dynamic code execution before reaching their usable state.
None of your examples require arbitrary script execution. You can specify them all declaratively, like Bazel forces you to do. I don't think that package managers should be doing the job of a build system though.
brunoarueira 1 days ago [-]
Multiple times the VCS was poisoned, we'll need to improve a lot the security around them to be able to go that route!
kibwen 1 days ago [-]
Maybe everyone else is too young to remember left-pad, but in the wake of left-pad everyone learned that one of the primary selling points of dedicated dependency repositories is that they can refuse to support "unpublishing" a dependency, which is not a guarantee that Github (or any other popular forge) makes.
inigyou 1 days ago [-]
There's no such thing as refusing to unpublish, not if you want to avoid prison time.
Package contains child porn, what now?
mook 20 hours ago [-]
I think it's implied to be refusing to unpublish at the request of the author; unpublishing due to outside forces is probably inevitable. If some unsupportable content got introduced in a new version (e.g. xz), at least the historical versions would be kept.
That of course brings in questions of cost, trust, and governance, so there's definitely a trade-off…
chickensong 1 days ago [-]
> Maybe everyone else is too young to remember left-pad
It wasn't that long ago...
SoftTalker 22 hours ago [-]
In the AI age, 6 months seems to be percieved as a long time ago. 6 months feels like yesterday to me.
In many cases I think it's impossible to avoid the pain that comes with decentralization without defeating the point. It's challenging because certain things are hard to do decentralized so it's easier to just give up and rely on central authorities and components.
When you install Go, it does just that, to some approximation; it will try to fetch from the module proxy first and fall back after that fails. But what makes Go different and more interesting is that you can just turn that off with no consequence. As long as all of the Git hosts are up, you can still use Go just fine with no proxy. I'm pretty sure Nix builds do this by default with Go so I believe most dependencies are usually online. That means the proxy approach and the decentralized approach are both fully redundant with eachother, while allowing the decentralized VCS hosts to remain the source of truth.
Go also separately has the sumdb to enforce immutability, so you can know that when you go grab a specific package that the tag name corresponds to one and only one exact source code. This is also optional, and even without it you get the benefit of local sum checking via your own cache and go.sum files in source checkouts. But having this be separate is great because it also means you could use another third party module proxy or a third party sumdb, or use just the sumdb and no proxy. It gives you many options to not have to rely on a single party while still getting some of the benefits of centralization.
Systems like this are rare. Many systems try very hard to have the cake and eat it too, but it usually fails somewhere. For me Go's approach works, and that is worth some attention.
With NPM, uv, composer, Cargo, etc. you can indeed choose to make your dependencies pull from VCS, but:
- Not having universal module proxies with caching means you will run into the usual problems. Repos can disappear, history can be rewritten, hosts can go down. Even if you invented an equivalent caching service, without making it an ecosystem default it won't achieve the same effectiveness as Go because less would be in the cache.
- Because it isn't an ecosystem standard, dependencies you pull would then still reference dependencies from centralized repos anyways, more or less defeating the point. You'd have to go and override every recursive dependency to really make it decentralized.
(And again, it usually comes with other downsides depending on the specific ecosystem. Performance is a big one, the module cache definitely helps make fetching dependencies in Go faster.)
Not having centralized package repos comes with its ups and downs, but the lack of one existing means there are a limited number of interesting attacks you can really pull in the Go ecosystem and they are heavily tamper-evident. You can try to poison the module cache with a given revision for a dependency with malicious code then overwrite it with an inoccuous commit, but this will probably be detected when someone (possibly in Nixpkgs or another packaging system) runs GOPROXY=direct and the sumdb doesn't line up. So the least noticeable thing you can really do is just leave your malicious code in the repo. Go also has the kind of nice property that fetching and building modules is supposed to be safe with untrusted code. That's pretty good, it would be hard to do better than that.
It also means there really isn't any central namespace to poison. You can still trick someone into downloading and using malicious software but you're mostly limited to doing it the old fashioned way. Again, hard to do much better than that.
While everyone has been losing their minds over supply chain issues, the Go ecosystem has been concerned but less so. It's still a huge risk still, dependencies can get compromised still, but attackers have no real central target to go after and get a bunch of damage at once. Nothing realistical, anyway. So, it's back to just trying to pwn individual GitHub repos.
any package manager can be redirected towards particular sources. What is unique about go? I sense, though this is not articulated through your communication, that the source of the package is directly specified in the source code requiring the package. Is that what you're trying to say—source pinning is evident in the source?
And again, if you just bolted a solution onto the side of an existing ecosystem it wouldn't accomplish as much. "Technically this could be done elsewhere" is true, but I hope you know I know that. The point is that Go deployed a mostly decentralized package management solution to the entire ecosystem as the primary and essentially only way to manage packages with Go. You actually could approximate what Go does using NPM, sans the caching module proxy part, using VCS URLs as dependencies, but the "approximate" and "sans caching module proxy part" is doing some heavy lifting.
There isn't really anything that does what Go does exactly. The closest example to me is OCI images. Like Go modules, their identity is essentially a sort of URI. However, it isn't a language package manager and has fairly different challenges, and doesn't really seem to need the caching proxy stuff due to the relatively few registries (and running it would be very expensive due to image sizes.) So I stand by with my belief that what Go does here is pretty unique.
That's not entirely true, if the VCS's tag changes the proxy might not pick it up.
Go is telling you that your VCS has a deficiency, without saying it out loud.
That proxy should be your own repo... but git sub modules, sub trees, sub directories are non starters for 99 percent of cases...
Git is an amazing tool, and if you want to manage your development like the linux kernel its dam near perfect. Most orgs dont work that way, and the tools and machinations that we have built around these shortcomings are rather burdensome.
I long for google to productize piper for the rest of us.
You maybe could invent a fully decentralized peer-to-peer VCS hosting system that uses Blockchain(TM) technology to ensure integrity, or something. But... You'd probably still want a cache, perhaps even moreso than before, for performance reasons, even if you really truly did solve the availability and integrity problems.
So do you want the VCS to be the source of truth or not?
Yes, the VCS is still the source of truth, ultimately nothing in the cache or sumdb exists without having existed in the VCS source first. The sumdb is an integrity mechanism bolted on top, and then the module proxy cache is an availability mechanism bolted on top.
Consider the following: let's say Kubernetes depends on some library you have Git access to. So you, being evil, rewrite the tag Kubernetes currently uses to contain malware. There are two possible scenarios:
- By default: the user is using a module cache, so changing a tag that already existed is no different than a Git host being pulled offline from the perspective of the user. The cache continues to serve the original source code, no malware delivered.
- The decentralized case: a user disables both sumdb and the module cache. Guess what? The pull fails. The pull fails because the project has a go.sum file and your tag doesn't match it.
Let's say someone compromised Google's sumdb or module proxy cache. Same basic thing: the go get will fail, because of the local integrity checks.
See, it really changes nothing. The module proxy cache essentially just ensures that a module source code that was once available remains available indefinitely so that you can continue to build old software exactly and securely.
The only place where behavior ever changes meaningfully (as in, not just "available or unavailable") is when go getting a new package or package update. If you have GOPROXY=direct, the poisoned fetch will fail because of global sumdb. If you disable that too, it may still fail because of local sumdb. But if not, you may get someone to pull your mutated tag. (And then the resulting go.sum would fail for other people.)
If you knew for absolute certain that nobody had ever pulled the package before, then theoretically mutating an existing tag would be safe. However, I call shenanigans on this: if you knew for sure nobody had ever pulled it, how in the world did it end up in the sumdb and module proxy cache? Granted, it may have been because of your own CI or something, but still, it stands to reason that this mechanism is introducing a highly desirable property. If a given tag was available once with a given source code I would want to be able to pull it again later and know it isn't tampered with.
In general, I have never seen it interpreted that the possibility of a stale cache changes what the canonical source of truth is. I've also never seen anyone argue that a lockfile changes the canonical source of truth. The Go sumdb being a global integrity mechanism does make it fairly unique, but I don't see it as being categorically different than the integrity checks from lockfiles, it's just that but across multiple users.
Even if you don't like Go's implementation particularly, I still think it is highly worth copying. You can always change a few things. Like for example, you could make module fetch operations not backed by a local checksum skip the global module proxy cache as a safeguard against the proxy itself, only allowing it to be trusted once you already have evidence that the sumdb and canonical source currently agree by virtue of already having a sum.
Sorry I have dropped another text wall, I don't have time to make it shorter. It will happen again.
(I realize that I have ranted in two directions here, but my intention was to 1. Argue that the properties introduced by the design are actually important and desirable, 2. Then argue semantics about whether it changes what one would consider to be the canonical source of truth, second mainly because I consider this to be unimportant. I mean it could be discussed further but, there really should be a point to it other than just semantics.)
Andrew Nesbitt has a good write [0] up on why using Git as a database is a bad idea
[0] https://nesbitt.io/2025/12/24/package-managers-keep-using-gi...
> The problem was that go get needed to fetch each dependency’s source code just to read its go.mod file and resolve transitive dependencies. Cloning entire repositories to get a single file.
Then it needs to compile the module, right? It's not downloading it twice.
> Grab’s engineering team went from 18 minutes for go get to 12 seconds after deploying a module proxy.
> ...
> GOPROXY became the default in Go 1.13.
The way this is written it sounds like go builds were extremely slow (18 minutes) so go adopted a proxy to speed things up. But Go 1.13 was released in 2020, and the blog post about 18 minutes is from 2023. There's no link to anything about GOPROXY.
The vcpkg example also isn't a git issue, it sounds like just bad engineering from Microsoft.
I'm willing to believe there are issues with using git, but that article is a letdown.
It seems to imply the reasons were optimization and to protect from disappearing dependencies. It doesn't imply that there were any hidden dangers to git.
I think it's very possible that google simply thinks speed is more important than decentralization. And for google's purposes, a google-owned cache is probably a safe enough hedge against disappearing repos (read: if you're not google you'd need to evaluate this risk yourself).
Edit: Discussion from the time here: https://news.ycombinator.com/item?id=20837753 - in that thread too people don't really know why the proxy was needed (or like, "is it really just for speed? but it isn't particularly slow right now"). One additional reason is analytics collection.
First, I'm uncomfortable with making a package creator's VCS provider part of the language's module infrastructure.
Second, not all files under version control necessarily belong in a tarball, and not all files in a tarball necessarily need to be under version control.
However, the point that there should be an easy way to track changes in your dependencies is well taken. One possible approach would be that publishing a packge boils down to importing the files that normally would go in your tarball into a community-controlled VCS.
Having big names maintain their own VCS is a good thing. There can be a centralized system to index / search packages just for findability. And this centralized system doesn't have to be anything special just like how anyone could use any search engine
Why can't you store a (possibly shallow) clone of the repository in the "package" registry?
> Second, not all files under version control necessarily belong in a tarball
How likely is it that files under version control but "not belonging in a tarball" (not needed to compile the current version) take so much space to justify making a tarball? At least if you make a shallow clone.
> not all files in a tarball necessarily need to be under version control
Which wouldn't (besides huge files that can be handled with git-lfs and similar)?
If you have configure in mind, let's not do it please.
The risks introduced by including them vastly overcome their convenience, in my opinion.
And maybe let's move towards getting rid of autotools
Yes, that would be an option.
As to your question, you generally want to version control input files, but distribute the generated artifacts. Some possible scenarios would be documentation, data tables in source or binary form, generated code including maybe even configure scripts if you cannot avoid it (though if I had to 'vendor' 3rdparty code that uses autotools, I think I probably would just unpack a tarball and commit the relevant artifacts).
Even for documentation, you'd think it would be harmless to generate it, but it's far from a given that the tools (e.g. browsers) used to read it are safe from malicious documentation files
Generated HTML files are potentially easier to audit than the scripts/toolchains used to generate them on an end user's machine if you do not pre-generate them.
Off the top of my head, other things I've done is committing RELAX NG *.rnc files, but shipping *.rng files, or generating C header files for various types of data (think `xxd -i` in case of binary files, but also just large chunks of plain text that gets wrapped into a C string).
Can't the things you list be part of the build scripts?
I still like the idea of shipping tarballs that include generated files instead of pulling input files from source control. As mentioned, the first thing that came to mind to make things easier to audit is to stick their contents into a community-controlled VCS.
Well, that's what building from source means; you could maybe distribute some compiled files for those who prefer them, and are willing to take the risk.
But you seem to be arguing that having users compile their software themselves doesn't increase their security?
> As mentioned, the first thing that came to mind to make things easier to audit is to stick their contents into a community-controlled VCS.
True
On the contrary! I gave examples where source files get generated. This happens in one of two places: Either when the maintainer publishes a new version, or every time an end user builds the package.
I'm arguing for the former, you're arguing for the latter. There are pros and cons to either approach. Some cons for the latter:
First, the build process becomes more brittle, as every end user now has to install the necessary tools. In case of the project containing RELAX NG schemas I alluded to, this would require a recent version of Perl, a Java runtime, and the Trang utility written in Java. The alternative? Just shipping a single XML file.
Second, the build process often doesn't become more, but less auditable: Instead of just reviewing the source files that actually get compiled, you now have to track down how they get generated, and review all the scripts that do so.
if you mean nixos, that's just starting yet another distro maintenance issue from scratch
Would you mind elaborating why (presumably) nixos (since you gave a Debian example), doesn’t help with this.
I think go is also a little more amenable to source library distribution since there's a pretty broad pure go ecosystem. For interpreted languages, a lot of performance sensitive stuff tends to be offloaded to arbitrary compiled languages so you end up needing a bunch of different toolchains to get everything working. A statically linked binary library is a useful abstraction layer.
> A hash of all files is checked against known hashes on sum.golang.org to prevent tags from being replaced, and it uses a proxy to prevent repos from being left-pad’d.
there is no need for 3rd party hosts to stay online. It's the best combination of de-centralized and centralized approaches.
Re pre-buiild packages: Python makes a difference between source and binary wheels. This approach is obviously for source wheels only.
As much as I'd have liked Git to be a viable option compared to centralized registries, last couple of years demonstrated running arbitrary commands during install is too much of a risk for it to work at scale.
Package contains child porn, what now?
That of course brings in questions of cost, trust, and governance, so there's definitely a trade-off…
It wasn't that long ago...