Comment by pregnenolone

1 day ago

Well.. https://github.com/doy/rbw/blob/main/Cargo.toml#L16

You're still pulling a lot of dependencies. At least they're pinned though.

That's just direct dependencies. Including all the dependency tree is 785k LOC according to lib.rs. Most rust libraries include tons of others.

https://lib.rs/crates/rbw

  • 326 packages right now when doing a build. Seems large in general, but for a Rust project, not abnormal.

    Takes what, maybe 15 seconds to compile on a high-core machine from scratch? Isn't the end of the world.

    Worse is the scope to have to review all those things, if you'd like to use it for your main passwords, that'd be my biggest worry. Luckily most are well established already as far as I can tell.

    • Why are you talking about compile times in a thread about supply chain security.

      326 packages is approximately 326 more packages than I will ever fully audit to a point where my employer would be comfortable with me making that decision (I do it because many eyes make bugs shallow).

      It's also approximately 300 more than the community will audit, because it will only be "the big ones" that get audited, like serde and tokio.

      I don't see people rushing to audit `zmij` (v1.0.19), despite it having just as much potential to backdoor my systems as tokio does.

    • "326 seems large, but not abnormal" was the state of JS in the past as well.

      Chance of someone auditing all of them is virtually zero, and in practice no one audits anything, so you are still effectively blindly trusting that none of those 326 got compromised.

      14 replies →

    • > 326 packages right now when doing a build. Seems large in general, but for a Rust project, not abnormal.

      That's a damning indictment of Rust. Something as big as Chrome has IIRC a few thousand dependencies. If a simple password manager CLI has hundreds, something has gone wrong. I'd expect only a few dozen

  • Does this take into account feature flags when summing LOC? It's common practice in Rust to really only use a subset of a dependency, controlled by compile-time flags.

    • My experience has been that while there's significant granularity in terms of features, in practice very few people actively go out of their way to prune the default set because the ergonomics are kind of terrible, and whether or not the default feature set is practically empty or pulls in tons of stuff varies considerably. I felt strongly enough about this that I wrote up my only blog post on this a bit over a year ago, and I think most of it still applies: https://saghm.com/cargo-features-rust-compile-times/

    • Also just unit tests in the source files, which again aren’t included in the binary via compile-time flags!

  • For a given tool, I'd expect the Rust version to have even more deps than the JS version because code reuse is more important in a lower-level language. I get the argument that JS users are on average less competent than Rust users, but we're talking about authors who build serious tools/libs in the first place.

> At least they're pinned though.

Frustratingly, they're not by default though; you need to explicitly use `--locked` (or `--frozen`, which is an alias for `--locked --offline`) to avoid implicit updates. I've seen multiple teams not realize this and get confused about CI failures from it.

The implicit update surface is somewhat limited by the fact that versions in Cargo.toml implicitly assume the `^` operator on versions that don't specify a different operator, so "1.2.3" means "1.2.x, where x >= 3". For reasons that have never been clear to me, people also seem to really like not putting the patch version in though and just putting stuff like "1.2", meaning that anything other than a major version bump will get pulled in.

  • > The implicit update surface is somewhat limited by the fact that versions in Cargo.toml implicitly assume the `^` operator on versions that don't specify a different operator, so "1.2.3" means "1.2.x, where x >= 3". For reasons that have never been clear to me, people also seem to really like not putting the patch version in though and just putting stuff like "1.2", meaning that anything other than a major version bump will get pulled in.

    Not quite: "1.2.3" = "^1.2.3" = ">=1.2.3, <2.0.0" in Cargo [0], and "1.2" = "^1.2.0" = ">=1.2.0, <2.0.0", so you get the "1.x.x" behavior either way. If you actually want the "1.2.x" behavior (e.g., I've sometimes used that behavior for gmp-mpfr-sys), you should write "~1.2.3" = ">=1.2.3, <1.3.0".

    [0] https://doc.rust-lang.org/cargo/reference/specifying-depende...

    • I don't know how I got this wrong because I literally went and looked at that page to try to remind myself, but I somehow misread it, because you're definitely right. This probably isn't the first time I've gotten this wrong either.

      From thinking it through more closely, it does actually seem like it might be a little safer to avoid specifying the patch version; it seems like putting 1.2.3 would fail to resolve any valid version in the case that 1.2.2 is the last non-yanked version and 1.2.3 is yanked. I feel like "1.2.3" meaning "~1.2.3" would have been a better default, since it at least provides some useful tradeoff compared to "1.2", but with the way it actually works, it seems like putting a full version with no operator is basically worse than either of the other options, which is disappointing.

  • Are we talking about `cargo build` here? Because my understanding is that if a lockfile is present and `Cargo.toml` hasn't changed since the lockfile was created then the build is guaranteed to use the versions in the lockfile.

    If however `Cargo.toml` has changed then `cargo build` will have to recalculate the lockfile. Hence why it can be useful to be explicit about `cargo build --locked`.

  • Is there a plan to change this? I don't see why --locked shouldn't be the default

    • I haven't heard anything about this, but I really wish it was there by default. I don't think the way it works right now fits anyone's expectations of what the lockfile is supposed to do; the whole point of storing the resolved versions in a file is to, well, lock them, and implicitly updating them every time you build doesn't do that.

  • It should be fine to do this according to semver as long as the major version is above zero.

    • Sure, but according to semver it's also totally fine to change a function that returns a Result to start returning Err in cases that used to be Ok. Semver might be ae to project from your Rust code not compiling after you update, but it doesn't guarantee it will do the same thing the next time you run it. While changes like that could still happen in a patch release, I'd argue that you're losing nothing by forgoing new API features if all you're doing is recompiling the existing code you have without making any changes, so only getting patches and manually updating for anything else is a better default. (That said, one of the sibling comments pointed out I was actually wrong about the implicit behavior of Cargo dependencies, so what I recommended doesn't protect from anything, but not for the reasons it sounds like you were thinking).

      Some people might argue that changing a function to return an error where it didn't previously would be a breaking change; I'd argue that those people are wrong about what semver means. From what I can tell, people having their own mental model of semver that conflicts with the actual specification is pretty common. Most of the time when I've had coworkers claim that semver says something that actively conflicts with what it says, after I point out the part of the spec that says something else, they end up still advocating for what they originally had said. This is fine, because there's nothing inherently wrong with a version schema other than semver, but I try to push back when the term itself gets used incorrectly because it makes discussions much more difficult than they need to be.