Rust: Does the published crate match the upstream source?
codeandbitters.com
codeandbitters.com
As for supply-chain issues, I just vendor my dependencies. Problem solved. Just run cargo vendor and copy stdout to .cargo/config.toml in your project. Done. Now not only you're not vulnerable to someone pushing malicious code to crates.io, but also have one place where all the dependencies of your project have their source code available to read for you. If you archive your project and then want to get back to it after years on another computer which did not have the dependencies installed, you don't need to hunt on the internet for dependencies that don't exist anymore[1]. If you want to work on a project without an Internet connection, all you need is the repo, you don't need to worry if a given computer has all the dependencies installed in the right versions. If crates.io ever goes down, or they take down a crate, you're safe as well. Although it's not the default in Rust, it's very well supported by cargo, if you want to develop in this model.
[1]: Yes, those are my bad experiences with Python dependencies.
Obviously you're only protected against new compromises. If the code is already compromised you're screwed (but not worse than using normal downloaded dependencies). This does make it possible that you miss security updates and other important features.
> Obviously you're only protected against new compromises.
Same as in the case of default Rust workflow. Cargo.lock ensures that versions of your dependencies are the same across different machines/devs.
> If the code is already compromised you're screwed (but not worse than using normal downloaded dependencies).
Yes, I generally read the code of my dependencies, so that's fine. Checking all the digests and crypto ever won't protect me from having to check what I use.
> This does make it possible that you miss security updates and other important features.
Again. Same as in normal Rust, thanks to Cargo.lock. But I think that's great, because the flip side of it is: I'm also going to miss all the new bugs. I don't want updates to be something that's happening behind my back. I want to check them and do them at the time of my choosing.
What vendoring brings to the table is the simplest solution to "is the code on crates.io the same as in the upstream repo", because with vendoring I don't care about the upstream repo at all. I only care about the copy of code that I have in my own repo. Going to GitHub and then checking if digests match would be a very roundabout way to do that, and then over the years people discover flaws in the hash functions etc. etc. I don't bother, I choose not to have to wonder about it at all. I don't care about what's on GitHub, I only care about what's in my own repo. And then again, there are issues of disappearing dependencies, which digests do not solve, but vendoring solves in the simplest way possible. Digests (already incorporated by Cargo by default) only solve someone pushing malicious code; vendoring solves the problem wholesale (assuming that you actually read the code).
How large percent of the development time, would you say you spend on reading the code in the dependencies?
If you would be ok with posting a link to any online repo of yours,
then, it'd be interesting to have a look at how many lines of code there are, in the vendored dependencies, and compare with num lines in the actual program. Maybe I'd run `cloc`
Rust doesn't yank things though, except for legal reasons so taht shouldn't happen(assuming you're getting a crate from crates.io not a random github link)
And if you actually review crates' sources, record that via https://lib.rs/cargo-crev so that others can benefit, too.
What does go do when you have different deps depending on different versions of the same common dep?
https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-hav...
> If a library ends up being used transitively by several dependencies, it’s likely that just a single copy of the library is desired (based on semver compatibility). If Cargo used all of the dependencies' Cargo.lock files, then multiple copies of the library could be used, and perhaps even a version conflict.
> In other words, libraries specify SemVer requirements for their dependencies but cannot see the full picture. Only end products like binaries have a full picture to decide what versions of dependencies should be used.
tl;dr, Go does a BFS of the dep tree, selecting the maximum version found. There are no ranges, so you can only use a version which has been listed.
"minimum" comes for a minimal implementation which can create reproducible results without a lockfile
> it adds a line to a `go.sum` while with a hash of the code at the version specified
Cargo.lock also contains a checksum
> You can distribute your code without a copy of the dependency
Also true in rust, and the default way of using rust/cargo.
> If the hashes are different, an error is thrown.
Also true in rust.
For an example of what this looks like: https://github.com/servo/webrender/blob/54b725be37f13b166946...
You haven't described anything different between go and rust in your comment since every feature you've pointed out applies equally to both.
Versions are git tags or commits. Dependencies are specified in a go.mod file, specified here: https://golang.org/ref/mod#go-mod-file
From the following go import path:
import "mywebsite.com/foo/bar/baz/v2/fizz"
We can determine that one of the following things is true:1. mywebsite.com has a 'meta name="go-import"' html tag in it when accessed by the go tool that points to a git repo/svn repo/etc, and the contained package has the folder structure "foo/bar/baz/v2/fizz"
2. mywebsite.com/foo has a 'meta name="go-import"' html tag...
(several obvious cases omitted)
3. mywebsite.com/foo/bar/baz is a git repo that has no version tag, but has a "v2" folder in it
4. mywebsite.com/foo/bar/baz is a git repo that has a version tag with v2.x.y
5. the go.mod has a replace or other directive for this import path
The 4th case is the relevant one here. github.com/foo/bar/v2 means that the bar repo has a v2 tag usually.
So, what version of that repo is used if there's v2.0.0 and v2.1.0? Well, the version is the smallest version that you or any module you depend on uses, so you simply have to recursively parse all go.mod files to understand the version used. Much simpler than Cargo.lock.
How do you enable or disable features in a sub-go-module? Let's say I have a module that has a "+ui" or "-ui" build tag because you can optionally build an openGL ui or such.
In rust, this would be a 'features = [ "ui" ]' in your Cargo.toml. In go, you would change your own compilation step to be "go build -tags ui"... And if you have two dependencies with those flags, and you wish to make them opposite values, that is simply impossible in go even though it's trivial in rust.
There are other differences, but I feel like I've already made my point. My main point is that go modules make import paths way more opaque. It used to be that every path element was either part of the import path or a folder on the filesystem. Now, it's either part of the import path, or a git tag, or a folder, and none of those things are obvious.
My secondary point is that go has a much simpler set of requirements than rust, mostly due to missing important features rust has.
There is no NPM or PyPi like host, sources originate from code hosts
Modules are prefixed with a domain name (avoid dep confusion)
Hashes are calculated on the module, it will fail if it is not correct
GoProxy has a global transparency log (like a blockchain, but not) so that everyone is contributing and sharing hashes
*there are special versions (v0.0.0 and branch names). They have special meaning, and get fixed with the access date and commit hash
For the curious: https://golang.org/ref/mod and https://go.googlesource.com/proposal/+/master/design/25530-s...
>Versions are only importable from tags (semver style)
Tags are mutable in git, which sometimes causes problems. Not that I think they had much choice here - git and tags are the de-facto community standard, they're just using it like everyone else.
>There is no NPM or PyPi like host, sources originate from code hosts
>Modules are prefixed with a domain name (avoid dep confusion)
Agreed, I like this quite a bit... but it comes with the significant risk of domain name or hosting changes. Github probably won't add the metadata to redirect your old module name to your new non-github name, for instance. I've watched several modules fight through this sequence, and the end result is always "can't do anything, contact your users yourself / hope they read the readme".
You win some, you lose some. In large part I think this part of Go modules is better than others'.
>Hashes are calculated on the module, it will fail if it is not correct
Nearly all lock-file-based dependency systems do this, Cargo included.
>GoProxy has a global transparency log (like a blockchain, but not) so that everyone is contributing and sharing hashes
Publishing a log now doesn't make it any safer tomorrow, it's purely about convincing you to trust them. This is precisely the same attack target as an NPM or PyPi. The same party hosts the tar'd source files + documentation + checksums: if it ever gets changed there, it's changed for everyone. Lock and go.sum files will only protect you from changes to your previously-known dependencies.
They've done a pretty good job making goproxy/gosum easy to run yourself, and you can skip past the goproxy and download directly from the module's owner (which is a domain name, which works great here), which mitigates this specific risk at least. But no, they're absolutely running a pypi, they just don't let repo owners make changes directly (only by committing changes, e.g. module revocation).
> if it ever gets changed there, it's changed for everyone
sum.golang.org has a hash chain, so you have to change everything that comes after a malicious hash change. So it does seem safer the more time that passes.
Go uses zip files, as a fyi.
I've had more trouble with custom domain based Go mods than the major code hosts. I've come to avoid those on custom domains as much as possible.
Is there a work-factor attached to these? I kinda don't think so, the only reference I can see is "sha256" (I have not checked the code.... nor is the public proxy the same thing they run, though it should be good enough for this purpose). Unless it's "costly", the time needed to recompute the whole history of every module is probably well below one second. The chain just makes it so you don't need to do that on every single update.
It's a good structure for making attacks more noticeable (you can't change any piece of history without everyone who already has that history noticing), and it enables efficient and trustworthy partial updates (backfills? meh), so I think it's a very solid addition and I'm glad it exists. But it really doesn't protect against much in the way of practical attacks - it's a canary, not a preventative measure, mostly serving to say "see? we've been trustworthy, you can trust us going forward".
(edit: I'll completely believe there's a small niche of attacks that it prevents... but I can't think of any that aren't massively overshadowed by other also-possible attacks in the same situation)
>I've had more trouble with custom domain based Go mods than the major code hosts. I've come to avoid those on custom domains as much as possible.
Yeah, custom domain ones have definitely had more problems - the proxy helps a fair bit there :) I don't mean to imply I'm unhappy they're running a PyPi-alike; I'm absolutely thrilled that they finally admitted they need it, and it's a decent implementation too. But that's pretty much all it is.
That is true. The global transparency log is just to prevent a package version change after it has been recorded in the log.
This is not true. Only Google sum server contributes to the hashes.
really sucks
> There is no NPM or PyPi like host, sources originate from code hosts
Bad idea. Easy for packages to disapear.
> Modules are prefixed with a domain name (avoid dep confusion)
This seems like more trouble then its worth most of the time
This is how cargo-crev already works: https://github.com/crev-dev/cargo-crev
The link points to a page[0] of discussions from "February 18, 2021". I don't know if that's a date parsing bug, but the question of whether packages match their upstream source actually was considered in 2018 for the NPM ecosystem[1] which led to the creation of a tool called TBV (Trust But Verify). Unfortunately the idea didn't seem to get much traction beyond that.
[0] https://internals.rust-lang.org/t/making-crates-io-verify-co...
[1] https://hackernoon.com/what-if-we-could-verify-npm-packages-...