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.
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] allownames 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-scriptsnames 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.executablesandinterpretedfail the gate if a crate ships prebuilt binaries or scripts.[licenses] allowadmits only MIT. A crate offered asMIT OR Apache-2.0passes; 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 inci.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.
| 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.
- Ask first. A new dependency is a design decision for the maintainer, not a step in implementing something.
- In its own commit, add it to the
Cargo.tomlthat needs it, behind a feature unless it is needed by every build. - 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. - Add each crate to
allowindeny.toml, and toallow-build-scriptsif it has one, with a comment naming it here. - Add each to the register below.
- 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.
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.
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.
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 |
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.
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.
proptestand 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.
- 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 aboutaws-lc-rs,ring,rustls-rustcryptoandgraviolais 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), withlibc, which has a build script, andwindows-sys. Admitted, with whatcargo denyshows they bring, when that adapter is written.