ADR-0014: Supply chain and build integrity¶
- Status: Accepted
- Date: 2026-04-12
- Supersedes: —
- Superseded by: —
Context¶
Trails composes third-party Rust and Python crates at the center of the trust boundary: Oxigraph (ADR-0007), Cedar (ADR-0006), biscuit-auth (ADR-0010), PyO3, oxrdflib, rdflib, pyshacl, JOSE / VC verification libraries. A malicious or careless update to any of these flows straight into the kernel — signed wheels don't protect against a tampered dependency in the build chain. The research/03 threat survey and the security review (§M-3 "Supply chain") both flag this: none of the existing ADRs commit to dependency pinning, audit cadence, reproducible builds, or release-signing discipline.
The framework ships wheels that execute in regulated-industry environments (the differentiator pitched in ADR-0013). "We use Rust" is not by itself a supply-chain story.
Options considered:
- No policy; rely on Cargo + pip defaults. Status-quo for most OSS projects. Dependabot PRs, community vetting. Acceptable for libraries where consumers own their own pinning. Unacceptable for a framework positioned as a trust substrate.
- Vendor everything. Fully vendored
vendor/trees, offline builds. Maximum control. Prohibitive maintenance cost; community contributions become painful. - Pin + audit + sign, no vendoring. Exact-version pins in lockfiles,
cargo audit/pip-auditin CI, signed git tags, reproducible wheel builds, per-release audit notes for sensitive deps. - Third-party build service (Sigstore, rules_oci). Strongest provenance. Heavyweight for a solo-maintained framework at M0/M1 scope; revisit at v1.0.
Decision¶
Option 3: pin + audit + sign, with a per-release audit note for sensitive crates. Concretely:
Rust¶
- Every kernel crate pins dependencies to exact minor versions in
Cargo.toml(e.g.,oxigraph = "=0.4.5", not^0.4). Patch-level floats only where the upstream semver discipline is trusted. Cargo.lockis committed in every kernel repo (including library crates, contrary to Rust-community default).cargo auditruns on every PR via CI; new advisories block merge until triaged.cargo denyenforces a license allowlist (Apache-2.0, MIT, BSD-⅔, ISC, MPL-2.0) and a crate-source allowlist (crates.io + kernel's git submodules only; no ad-hoc git URLs).
Python¶
uv.lockis committed for every Python package in the repo.pip-auditruns on every PR.- License checks via
pip-licenseswith the same allowlist as Rust.
Toolchains¶
- Rust version pinned in
rust-toolchain.tomlat the repo root (channel, components, targets); current pin is1.85.0(bumped from1.82.0for pyo3 0.28.3 compatibility). CI fails if the file is absent or modified without a PR that updates the pin deliberately. - Python version pinned in
.python-version(used byuv,pyenv). maturinversion pinned inpyproject.tomlbuild-system requires.
Reproducible wheel builds¶
- Wheels built with
maturin build --release --locked. - Linux wheels built in a pinned
manylinuximage (e.g.,quay.io/pypa/manylinux_2_28_x86_64:2024-01-01-abc123, not:latest). The image digest is recorded in the release notes. - macOS and Windows wheel builds use pinned
runs-on:images (macos-14,windows-2022); no floating runner tags.
Audit notes¶
- Third-party RDF and policy-evaluation crates require a per-release audit note in
CHANGELOG.mdlisting the version delta since the previous release and a one-line risk assessment. Sensitive crates for this purpose:oxigraph,oxrdf,oxrdflib,cedar-policy,biscuit-auth,pyshacl,rdflib, JOSE / COSE libraries, any DID resolver crate.
Release signing¶
- Framework releases are tagged with
git tag -susing the repo owner's SSH signing key (same pattern as the workspace'sid_ed25519_dev). Unsigned tags are rejected by CI's release workflow. - Wheel artifacts attached to GitHub releases include a
.intoto.jsonlprovenance statement (GitHub's built-in attestation) so consumers can verify the build workflow that produced each wheel. - Publishing to PyPI or any registry requires explicit human approval via workflow dispatch, not automatic on tag. (Policy, not a technical control — but the workflow fails closed by default.)
Consequences¶
Positive¶
- Supply-chain threat class from research/03 and §Supply-chain of the security review is addressed as specification, not implementation discretion.
- Deterministic builds: a given tag reproduces byte-identical wheels on Linux (and close-to-identical elsewhere).
- License risk bounded: a disallowed license cannot land silently.
- Advisories surface at PR time, before release cadence is disrupted.
- Release-signing discipline inherits from the workspace's existing SSH signing key — no new key ceremony.
Negative¶
- Maintenance cost. Every dependency bump requires an audit-note entry. Mitigated by scripting:
scripts/release-audit-notes.shdiffsCargo.lock+uv.lockbetween tags and scaffolds the CHANGELOG stub. - Slower response to upstream fixes. Exact pinning delays picking up patches until a Trails release. Mitigated by Dependabot + weekly security-only dependency sweep that is allowed to land without a feature release.
- CI footprint grows.
cargo audit,cargo deny,pip-audit,pip-licenses, image-digest verification — each is a CI step. Budget ~2 minutes added per PR. - Committed
Cargo.lockin library crates is mildly contrarian; some downstream consumers will ignore it. That is fine — the lock exists for our builds, not theirs.
Non-consequences¶
- Does not mandate SBOM emission at M1 (deferred to v1.0 alongside Sigstore/rules_oci evaluation).
- Does not mandate
cargo vetaudit chains (heavyweight for M1 solo-maintained scope; revisit when the contributor base grows). - Does not change kernel/surface split (ADR-0001) or any runtime decision — this is entirely a build-time and release-time policy.
Revisit conditions¶
- If Sigstore-style keyless signing or GitHub attestations become the consumer-side default for Python/Rust ecosystems, fold attestation verification into the framework's own install-time checks and remove the manual audit-note burden where automation covers it.
- If the kernel-crate count grows beyond a solo maintainer's ability to track advisories, introduce
cargo vettrust delegations to peer projects (Pelletier / Oxigraph, Cedar maintainers) before scaling the audit-note discipline. - If a third-party RDF or policy crate ships a supply-chain incident, promote the weekly sweep to per-release blocking and add mandatory staging-deployment soak before publish.