Skip to content

Latest commit

 

History

History
228 lines (187 loc) · 15 KB

dependencies.md

File metadata and controls

228 lines (187 loc) · 15 KB

Dependencies

npro is built so that what runs in your program is what you chose to put there. The protocol crates depend on nothing but core, and alloc where a feature asks for it. Anything else enters by a decision, made once, written down here, and enforced on every commit.

This is a foundation of the project, not a preference. Each crate in a dependency tree is code you ship, and its authors are people you trust with your users' bytes and your build machines. A tree that grows by default, a crate at a time, ends up trusting hundreds of people for things a few lines would have done. npro takes the other path: a very small set of chosen dependencies, each worth what it costs.

That does not mean writing everything ourselves. Where a crate does a hard job well, such as an async runtime or a TLS stack, npro integrates with it rather than competing with it, behind a feature or in an adapter crate that only the people who want it build.

The door

deny.toml at the top of the tree is the door, and cargo deny check is the bouncer. scripts/ci.sh runs it on every commit, and sai runs it on every push.

  • [bans] allow names every crate that may appear anywhere in the build graph. Any other crate fails the gate, however deep in the tree it sits. A crate admitted for one job does not get to bring its own guests: each of its dependencies needs its own entry, and its own line in the register below.
  • [bans.build] allow-build-scripts names the crates that may have a build script, code that runs on the build machine at compile time. A crate with one fails the gate unless it is named there as well.
  • executables and interpreted fail the gate if a crate ships prebuilt binaries or scripts.
  • [licenses] allow admits only MIT. A crate offered as MIT OR Apache-2.0 passes; one only under another licence needs that licence admitted too, as part of the same decision.
  • multiple-versions = "deny": one version of each crate.
  • [sources]: crates.io only, no git or other registries.
  • cargo audit (also in ci.sh) fails the gate on any RustSec advisory against a crate in the tree.

The graph checked is the whole one: every crate, every feature, every platform, and dev-dependencies too. Tests and tools run on developers' machines and builders, so they are let in on the same terms.

cargo deny treats an empty allowlist as no allowlist, so the list also names the workspace's own crates. That keeps it in force while nothing else is on it. A new workspace crate is added to it when it is created.

Where dependencies may live

crates may depend on
the protocol crates: npro-core, -h1, -ws, -h2, -h3, -quic, -wt, -mqtt, -http nothing, except crates agreed below, each behind an opt-in feature, no_std, and with no build script
npro-io, the driver and the IO adapters nothing without features; std, and the crates of each adapter's feature and of its tls decision (io-model.md)
integrations with a runtime or framework, such as tokio that runtime, in its own opt-in crate or feature, never a default
npro-test, npro-fuzz, tools what testing needs, admitted like anything else
the libFuzzer targets in fuzz/ libFuzzer and what builds it, in a workspace of their own with its own Cargo.lock and deny.toml, so none of it enters the main workspace's graph
npro, the facade the npro crates only

A protocol crate never depends on an IO crate, an async runtime, or a crate that does IO. Time and random are inputs, not dependencies.

Admitting a crate

  1. Ask first. A new dependency is a design decision for the maintainer, not a step in implementing something.
  2. In its own commit, add it to the Cargo.toml that needs it, behind a feature unless it is needed by every build.
  3. Run cargo deny --all-features check bans. It names every crate the addition brings in, and every one with a build script. Each of those is part of the decision too.
  4. Add each crate to allow in deny.toml, and to allow-build-scripts if it has one, with a comment naming it here.
  5. Add each to the register below.
  6. Say in the commit message why it is worth having, covering what the register asks for.

What the register records for each crate, and what decides it:

  • What it is for, and what doing without it would cost.
  • Who maintains it, and how actively.
  • Its unsafe code: none, or how much, where, and why that is acceptable.
  • Its build script and proc-macros, if any, and what they do. A build script that touches the network is refused.
  • Its MSRV, which must be no newer than npro's rust-version.
  • no_std, for anything a protocol crate uses.
  • Its licence.
  • What it brings with it.

Removing a dependency is the same in reverse: take it out of Cargo.toml, deny.toml and the register, in one commit.

The register

The main workspace

Without the opt-in features below, npro builds from its own sources and the Rust toolchain alone.

crate used by feature why build script unsafe admitted in
miniz_oxide 0.9 npro-ws pmd permessage-deflate's inflater and deflater (RFC 1951 raw deflate), where C uses zlib. An inflater of our own was the alternative, judged not worth it for a first pass; a deflater as well would be more again. Maintained by oyvindln under the Frommi organisation, actively, and used by Rust's own standard library (its rustc-dep-of-std feature), so it is well exercised. no_std, needing alloc (with-alloc); edition 2021, building at npro's rust-version none none: #![forbid(unsafe_code)] the commit adding permessage-deflate's dependency
adler2 2.0 miniz_oxide (pmd) the Adler-32 checksum, which miniz_oxide needs for zlib streams; ws uses raw deflate, so it is only carried. The same maintainer, a maintained fork of adler none none: #![forbid(unsafe_code)] the same

Both are licensed MIT among alternatives (miniz_oxide MIT OR Zlib OR Apache-2.0, adler2 0BSD OR MIT OR Apache-2.0), and bring nothing else: their other dependencies are optional, and not enabled.

tls, npro-io's opt-in rustls feature: rustls with no crypto provider, the application giving it one (decided with Andy, 2026-10-09, io-model.md, "tls"). A provider, aws-lc-rs or another, is never npro's dependency. All of these are no_std with alloc, and build at npro's rust-version; rustls needs atomic compare-and-swap for its Arc, so not on the M0 or riscv32imc that sai builds for.

crate used by feature why build script unsafe admitted in
rustls 0.23 npro-io rustls tls 1.2 and 1.3, sans-IO through its unbuffered API, so it sits behind the driver's record layer with no IO of its own. The Rust tls stack, maintained by the rustls project, actively, with funding (ISRG's Prossimo), and audited. With tls12 only: no std, no log, no provider. Apache-2.0 OR ISC OR MIT a no-op unless on nightly with its read_buf feature, which is not enabled none: #![forbid(unsafe_code)] the commit admitting rustls
rustls-pki-types 1 rustls (rustls) the types of certificates, keys and server names. The rustls project. MIT OR Apache-2.0 none one: a transmute of an IPv6 address's sixteen bytes the same
rustls-webpki 0.103 rustls (rustls) certificate path validation, the provider's signature algorithms doing the crypto. The rustls project's fork of Brian Smith's webpki. ISC none none the same
untrusted 0.9 rustls-webpki (rustls) safe parsing of untrusted input, for webpki's DER. Brian Smith's. ISC. It ships its CI scripts (mk/), never run by a build, admitted by checksum in deny.toml none none the same
subtle 2.6 rustls (rustls) constant-time comparisons. dalek-cryptography's, widely used. BSD-3-Clause none two: the optimisation barrier that keeps a comparison constant-time the same
zeroize 1 rustls, rustls-pki-types (rustls) wiping keys from memory. RustCrypto's. Apache-2.0 OR MIT none volatile writes (18), so the compiler cannot drop a wipe the same
once_cell 1 rustls (rustls) lazily made statics, with its alloc and race features. Widely used; std now has most of it. MIT OR Apache-2.0 none its cells' primitives (about 50) the same

ISC and BSD-3-Clause were admitted with them: both are permissive, as MIT is, and ask only that the notice is kept.

The npro-aws-lc workspace

npro-aws-lc/ is npro with tls by rustls, its crypto provider aws-lc-rs, which Andy chose (2026-10-09, io-model.md, "tls"). A workspace of its own, with its own Cargo.lock and deny.toml, as fuzz/ is: aws-lc's C and the tools that build it never enter npro's graph, npro depends on no provider, and builds where aws-lc cannot, an esp32 among them. What wants npro with aws-lc builds against this. It holds the tests of npro-io's rustls record layer with a real provider; scripts/aws-lc.sh checks its door and runs them. Not published yet.

Its door admits npro's crates and their opt-in features' crates, as the main workspace's does, and:

crate why build script unsafe licence
aws-lc-rs 1.18 rustls' crypto provider, AWS's, and rustls' default. Maintained by AWS, actively; has a FIPS-validated build finds aws-lc-sys' build and probes the compiler; no network the bindings to AWS-LC ISC AND (Apache-2.0 OR ISC)
aws-lc-sys 0.45 AWS-LC itself, BoringSSL's descendant, its C and assembly carried in the crate (about 69MB of source) compiles AWS-LC with cc, or cmake where needed; no network. Its perlasm generators, and its Windows prebuilt-NASM script, are never run on Linux: the assembly comes ready generated. Those 70 scripts are admitted by glob in its deny.toml FFI, by nature ISC AND Apache-2.0 AND MIT AND BSD-3-Clause, with alternatives: Apache-2.0 is admitted here for it
cc, cmake, find-msvc-tools, jobserver, shlex, pkg-config, dunce, fs_extra aws-lc-sys' build script's tools: compiling C, running cmake, finding a system AWS-LC, paths and files none cc and jobserver some MIT or Apache-2.0 among others (fs_extra MIT; dunce CC0-1.0 OR MIT-0 OR Apache-2.0)
libc jobserver, cc probes the rustc version, no network the C bindings it is for MIT or Apache-2.0
getrandom, r-efi, cfg-if jobserver on Windows getrandom's probes the target, no network getrandom's syscalls MIT or Apache-2.0 among others

All but aws-lc-rs and aws-lc-sys run only at build time, on the machine building. They were admitted in the commit adding the workspace.

The npro-cli workspace

npro-cli/ is npro's utility, npro, and its runner, which binds SS2 to the threads adapter and to rustls over aws-lc (ss-design.md, "Where the utility lives"). A workspace of its own, as npro-aws-lc/ is, which it builds on: its door admits what that one admits, re-listed since a workspace cannot inherit another's (Andy, 2026-10-12: a provider is admitted where it is chosen), and one crate of its own. scripts/cli.sh checks its door and runs its tests. Not published.

crate why build script unsafe licence
caps 0.5.6 dropping every capability once the listeners are bound, so code an attacker runs in the server cannot bind a privileged port (Andy, 2026-10-12, rather than rustix, which opens the rest of POSIX). Linux only, as capabilities are; its one dependency is libc, admitted already for aws-lc-sys none capget, capset and prctl, by libc MIT OR Apache-2.0

The fuzz workspace

fuzz/ builds the libFuzzer targets (fuzzing.md) and nothing else; no npro crate depends on it. Its door is fuzz/deny.toml, which scripts/fuzz.sh checks before it builds anything. It checks the Linux graph only, since that is where fuzzing runs: on Windows, jobserver would also bring getrandom, r-efi and cfg-if.

The choice was between this, the standard Rust route, and linking the builder's own libFuzzer runtime through a shim of npro's own. The shim would have admitted no crates, but needed unsafe code in npro to turn libFuzzer's pointer and length into a slice; npro took the crates, so that none of its own code needs qualifying as safe.

crate why build script unsafe licence
libfuzzer-sys 0.4.13 the fuzz_target! macro, and libFuzzer's runtime, whose C++ sources it carries compiles those sources with cc; nothing else, and no network the macro's entry point, taking libFuzzer's pointer and length (MIT or Apache-2.0) and NCSA, the LLVM licence of libFuzzer's sources, admitted for this workspace only
arbitrary a dependency of libfuzzer-sys, for targets taking structured input; npro's take bytes none. It ships its maintainer's publish.sh, never run by a build, admitted by checksum some MIT or Apache-2.0
cc compiles libFuzzer, from libfuzzer-sys' build script none some MIT or Apache-2.0
find-msvc-tools split out of cc none some MIT or Apache-2.0
jobserver cc's share of cargo's parallel jobs none some MIT or Apache-2.0
libc jobserver and cc on unix probes the rustc version, no network the C bindings it is for MIT or Apache-2.0
shlex cc's parsing of compiler flags from the environment none a little MIT or Apache-2.0

Apart from libfuzzer-sys and arbitrary, which are linked into the targets, they are build-time only: they run on the fuzz builder while the targets build. All were admitted in the commit adding the libFuzzer targets.

Decided against

  • rand: random is an input the caller provides (npro_core::random::Random).
  • serde: the test transcripts are read by a small strict reader in npro-test. Nothing else in npro needs a serialisation framework.
  • SHA-1 and base64 crates: the ws handshake's needs are about 150 lines, written in npro-core and checked against the RFC vectors.
  • proptest and similar: property tests are written over npro-core's seeded generator, which also reproduces C's random draws.
  • An async runtime in any protocol crate: integrations live outside them.

Open

  • tls, for npro-io. rustls goes inside the driver, behind a record-layer trait. rustls itself is #![forbid(unsafe_code)]; its crypto provider is where the unsafe code is, and is the decision. What was found about aws-lc-rs, ring, rustls-rustcrypto and graviola is in io-model.md. graviola leads for desktop and server, but its licence (Apache-2.0 OR ISC OR MIT-0) is not MIT, and it covers neither older CPUs nor microcontrollers. Decided when the tls commit comes, and recorded here.
  • mio, for npro-io's one-thread readiness loop (agreed in principle 2026-10-06), with libc, which has a build script, and windows-sys. Admitted, with what cargo deny shows they bring, when that adapter is written.