Remix.run Logo
jchw 4 days ago

Go MVS ought to be deterministic, but it still benefits from modules having lockfiles as it allows one to guarantee that the resolution of modules is consistent without needing to trust a central authority.

Go's system may be worth emulating in future designs. It's not perfect (still requires some centralized elements, module identities for versions ≥2 are confusing, etc.) but it does present a way to both not depend strongly on specific centralized authorities without also making any random VCS server on the Internet a potential SPoF for compiling software. On the other hand, it only really works well for module systems that purely deal with source code and not binary artifacts, and it also is going to be the least hazardous when fetching and compiling modules is defined to not allow arbitrary code execution. Those constraints together make this system pretty much uniquely suited to Go for now, which is a bit of a shame, because it has some cool knock-on effects.

(Regarding deterministic MVS resolution: imagine a@1.0 depending on b@1.0, and c@1.0 depending on a@1.1. What if a@1.1 no longer depends on b? You can construct trickier versions of this possibly using loops, but the basic idea is that it might be tricky to give a stable resolution to version constraints when the set of constraints that are applied depends on the set of constraints that are applied. There are possible deterministic ways to resolve this of course, it's just that a lot of these edge cases are pretty hard to reason about and I think Go MVS had a lot of bugs early on.)

arccy 3 days ago | parent [-]

I don't think anything about Go requires a central authority. You only need access to the sources of all your dependencies.

Go by default uses proxy.golang.org for speed/security, and sum.golang.org for sharing verification, but it works just fine without them.

I think trust in binary packages / no remote code execution is orthogonal to dependency selection.

jchw 2 days ago | parent [-]

Consider a project like Kubernetes. Find a dependency whose domain name has lapsed, or might soon. Snipe it, replicate the original VCS, add a new malicious version under a new tag. Wait for someone to go get -u it.

This is pretty bad even today: If it goes unnoticed, it could lead to compromised binaries being released. However, it underscores design decisions related to the module system that helps to reduce the impact:

- Use of MVS means that even without a sum file, you still won't automatically download malware published in new versions.

- Use of a centralized proxy and sumdb by most of the ecosystem greatly reduces the likelihood that an attacker will attempt to change an existing version as it likely won't work and might result in detection instead.

- Lack of code execution in dependency fetching substantially reduces the odds of being able to compromise a developer machine. It doesn't make the problem a non-issue, but the prognosis is a ton better.

In NPM, if you don't have a lockfile, the version constraints will be resolved as you install dependencies. These version constraints are not necessarily deterministic, and installation can execute arbitrary code as the user. This is a pretty bad combination but there is a bit of relief because at least malware can be pulled from NPM when it is discovered... Which doesn't solve everything, but it's a lot better than nothing.

Anyway, that's why I don't consider these topics to be fully orthogonal. They do have some interplay.