1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
name: CI
on:
push:
branches:
pull_request:
jobs:
test:
name: build & test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --all-targets
- run: cargo test
# Zero warnings, kept at zero. Default features plus `component` (the WIT
# world's adapter, which ships). `puf` is research code outside the charter
# (R-04 in docs/DEVIATIONS.md) and is left out on purpose.
lint:
name: clippy + rustdoc (-D warnings)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: cargo clippy --all-targets -- -D warnings
run: cargo clippy --all-targets --locked -- -D warnings
- name: cargo clippy --lib --features component -- -D warnings
run: cargo clippy --lib --locked --features component -- -D warnings
- name: cargo doc --no-deps (-D warnings)
run: cargo doc --no-deps --locked
env:
RUSTDOCFLAGS: -D warnings
# Declared rust-version in Cargo.toml, pinned and actually built rather than only stated.
# 1.75 was the original declared MSRV but was never tested and does not build: the locked
# zeroize_derive already requires the `edition2024` Cargo feature, which needs rustc 1.85+.
# 1.85 also matches the MSRV declared by pqc-kem/pqc-sig, so this keeps all three repos in
# the Identity Release initiative on the same floor.
msrv:
name: MSRV (1.85) build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@1.85.0
with:
targets: wasm32-unknown-unknown
# MSRV is a promise about what a *consumer* compiles: the library and the
# dependencies it pulls in. It is not a promise about our own test tooling.
# `--all-targets` pulls dev-dependencies, and wasmtime, used only by the
# execution proof and never shipped, requires rustc 1.95. Building it here
# would raise the floor for every consumer in order to satisfy a test.
#
# The library is built in both configurations a consumer can select, so
# the floor is still checked against everything that actually ships.
- run: cargo build --locked
# The component is what actually ships as L1, so the floor is checked
# against it too. `--no-default-features` is only meaningful for wasm32:
# on a host target a no_std build has no allocator or panic handler and
# cannot link, which is why it was never built that way.
- run: cargo build --locked --no-default-features --features component --target wasm32-unknown-unknown
# P3-02: proves identity generation works fully offline by denying the
# capability at the boundary (a real network namespace with no interface),
# not by an in-process "am I offline?" assertion. An in-process check can
# only observe the paths it knows to instrument and would pass even if a
# dependency three levels down still had a route out.
#
# `sudo unshare --net` puts the step's process tree in a fresh network
# namespace with no configured interface (loopback only, and unused here)
# — equivalent to the `--network none` a container runtime would give you,
# without needing to solve "how does an offline container fetch crates".
# GitHub-hosted ubuntu-latest runners have passwordless sudo, so this needs
# no extra setup.
#
# NOTE: an earlier version of this job used the unprivileged
# `unshare --net --map-root-user` (no sudo) specifically to avoid depending
# on passwordless sudo, and that was verified working in WSL2/Ubuntu. It
# does NOT work on GitHub-hosted runners: Ubuntu 23.10+ restricts
# unprivileged user-namespace creation via AppArmor
# (kernel.apparmor_restrict_unprivileged_userns=1), so the job died with
# "unshare: write failed /proc/self/uid_map: Operation not permitted"
# before either isolated step ran at all — the first real run of this
# workflow produced zero evidence in either direction. `sudo unshare --net`
# sidesteps the user-namespace restriction entirely (root doesn't need
# CAP_SYS_ADMIN granted via userns) and is what's actually confirmed
# working on ubuntu-latest.
#
# Dependencies and test binaries are built in the earlier, networked step;
# the isolated steps only execute already-built code with `--offline`, so
# cargo itself never needs a route either.
offline-generation:
name: offline generation (network-isolated)
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Prefetch deps and build test binaries (network available here)
run: |
cargo build --all-targets
cargo test --no-run --lib --test plp_tests
cargo test --no-run --test network_isolation_negative_control
# rustup's directories are named explicitly. sudo resets HOME to /root,
# and once the runner stopped exporting RUSTUP_HOME, rustup under sudo
# looked in /root/.rustup, found no toolchain, and exited before cargo
# ran. That failed the generation step, and it let the negative control
# below "fail as expected" without testing anything. The negative control
# now also requires that its test actually ran and failed.
- name: Generation suite MUST succeed with no network interface available
run: |
sudo -E env "PATH=$PATH" "RUSTUP_HOME=${RUSTUP_HOME:-$HOME/.rustup}" "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" \
unshare --net -- \
cargo test --offline --lib --test plp_tests
- name: Negative control MUST fail with no network interface available
run: |
if sudo -E env "PATH=$PATH" "RUSTUP_HOME=${RUSTUP_HOME:-$HOME/.rustup}" "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" \
unshare --net -- \
cargo test --offline --test network_isolation_negative_control > negative-control.log 2>&1; then
cat negative-control.log
echo "::error::negative-control network call succeeded inside the network-isolated step — isolation is NOT enforced, the offline-generation claim is unproven"
exit 1
fi
cat negative-control.log
# A failure only counts if the test ran and failed. Anything earlier
# (no toolchain, a build error) says nothing about isolation.
grep -q "^test network_call_succeeds_when_a_route_exists \.\.\. FAILED" negative-control.log || {
echo "::error::the negative control did not run its test, so its failure is not evidence of isolation"
exit 1
}
echo "Negative control failed as expected under network isolation — isolation confirmed real."
# P3-08 follow-up: zeroize_wipes_master_identity_secret_wasm (src/plp.rs)
# proves MasterIdentity's secret is actually zeroized in WASM linear
# memory, not just on native — but a test nobody runs isn't proof (same
# failure shape as the earlier `unshare` incident on this repo, where the
# isolation job silently didn't execute what it claimed to). This job runs
# it for real, under wasm32-unknown-unknown via wasm-pack + Node.
#
# The `wasm` feature it used to build no longer exists: P3-13 (0X3-81)
# retired the wasm-bindgen export surface so there is one WebAssembly
# artifact, the component. `wasm-bindgen-test` survives as a TEST HARNESS,
# which is a different thing from those exports — the test calls the native
# Rust API and asserts the secret is wiped, and running it under wasm32 is
# what shows the wasm codegen path does not elide that write. So this job
# keeps doing its job, on the default feature set.
wasm-test:
name: wasm test (zeroization + full suite, node)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Run wasm test suite
run: wasm-pack test --node -- --no-default-features
# A passing run above is not by itself proof the zeroization test ran:
# if it were filtered out or silently skipped, the job would still be
# green. Name it explicitly and require it.
- name: The zeroization test specifically must have run in WASM
run: |
out=$(wasm-pack test --node -- --no-default-features 2>&1)
echo "$out"
echo "$out" | grep -q "zeroize_wipes_master_identity_secret_wasm" || {
echo "::error::zeroize_wipes_master_identity_secret_wasm did not run under wasm32 - the zeroization-in-linear-memory claim is unproven"
exit 1
}