Skip to content

fix(deps): clear five RustSec advisories (webpki, wasmtime, p256 0.14 for crypto-bigint) - #480

Merged
TAIPANBOX merged 4 commits into
mainfrom
fix/audit-2026-10-26
Oct 26, 2026
Merged

TAIPANBOX merged 4 commits into
mainfrom
fix/audit-2026-10-26

Conversation

@TAIPANBOX

@TAIPANBOX TAIPANBOX commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

security (cargo audit) has been red on every PR since five RustSec advisories were published. This makes ./scripts/audit.sh (both audited lockfiles plus crates/radar) pass again by upgrading the affected crates. .cargo/audit.toml is unchanged: no new ignore.

Advisory Crate Before After Lockfiles
RUSTSEC-2026-0382 rustls-webpki 0.103.15 0.103.16 workspace, crates/cluster
RUSTSEC-2026-0381 webpki-roots 1.0.8 1.0.11 workspace, crates/cluster
RUSTSEC-2026-0379 wasmtime 49.0.3 49.0.4 workspace (stays on 49.x)
RUSTSEC-2026-0378 wasmtime 49.0.3 49.0.4 workspace
RUSTSEC-2026-0387 crypto-bigint 0.5.5 gone (0.7.9 via elliptic-curve 0.14) workspace

crypto-bigint 0.5.5 was reachable only through p256 0.13, which only this workspace's own crates depend on (cloud, delegation, dpop, gateway). So the fifth advisory is fixed by moving p256 to 0.14 (elliptic-curve 0.14.3, ecdsa 0.17.0, pkcs8 0.11, sec1 0.8, signature 3.0, rfc6979 0.6).

Commits

  1. fix(deps): patch rustls-webpki, webpki-roots and wasmtime for four advisories: lockfile-only patch bumps, resolved against main's manifests.
  2. fix(deps): move p256 to 0.14, dropping crypto-bigint 0.5.5: six manifest lines plus two API renames, nothing else:
    • VerifyingKey::to_encoded_point(compress) is now to_sec1_point(compress); x(), y(), as_bytes() are unchanged.
    • SigningKey::random(&mut OsRng) is deprecated and rand_core 0.10 has no OsRng. The four test fixtures that made a random key now call <SigningKey as p256::elliptic_curve::Generate>::generate(). Production key generation (gateway checkpoints) already used getrandom 0.2 bytes and SigningKey::from_slice, unchanged.
    • Signature::from_slice / to_bytes, VerifyingKey::from_sec1_bytes, and SecretKey::from_pkcs8_pem / from_sec1_pem / from_pkcs8_der / from_sec1_der kept their names and signatures.
    • P-256 keeps NORMALIZE_S = false in ecdsa 0.17 (p256-0.14.0/src/ecdsa.rs, ecdsa-0.17.0/src/hazmat.rs), so a high-S signature still verifies and signing still does not normalise s, as in 0.13.
    • The gateway manifest comment on hmac was stale after the bump and is corrected: hmac 0.12 came in through p256 0.13's rfc6979; now the gateway's own line is its only edge.
  3. test(crypto): pin the P-256 wire bytes to RFC 6979's published vector: three new tests, a feature file, and the test count.
  4. test(crypto): allow-list the RFC 6979 test key for the secret scan: one .secretscan-allow line. secretscan / tree flagged the PKCS#8 header of the RFC 6979 key in audit_sign.rs (fingerprint 3021d90eb9437b2d, the same header fingerprint already allowed for the test keys in oidc.rs and dpop/src/lib.rs). It is a published test vector and signs nothing; the SEC1 header in that file was already covered.

Existing tests

No assertion changed. Test files changed only where an API was renamed (to_encoded_point to to_sec1_point, the random-key constructor), plus one rustfmt line collapse in audit_sign::tests::base64_raw_scalar_key_round_trips that follows the shorter name.

Wire format: what pins it

Before this PR the external P-256 values the suite checked were the three keyryx thumbprints measured from keyryx's Go code (KID_11/22/33) and agent-passport's checkpoint vectors, which the_vector_signatures_verify_under_the_vector_key verifies but does not produce. Nothing held a signature this code produces, a SEC1 public key, or a PKCS#8 / SEC1 private key parse to an external value. The new tests use RFC 6979 appendix A.2.5 (a P-256 key, its public point and its deterministic SHA-256 signatures). The base64 forms, the RFC 7638 thumbprint, the compressed point, the DER signature and the PKCS#8 / SEC1 encodings were computed with Python cryptography 50.0.1, which also verified both RFC signatures under the RFC point and reproduced the RFC's "sample" signature with its deterministic ECDSA.

Test What it pins Red first against
gateway::checkpoints::tests::the_rfc_6979_key_puts_its_published_point_and_signatures_on_the_wire JWK x/y (base64url, no padding), kid (RFC 7638), raw r||s over "sample" and "test" x/y in padded standard base64; s written before r
cloud::devices::tests::a_published_rfc_6979_signature_verifies_and_no_other_encoding_of_it_does the RFC signature verifies under uncompressed and compressed SEC1; swapped halves and DER are refused Signature::from_der in place of from_slice
cloud::audit_sign::tests::the_rfc_6979_key_loads_from_every_accepted_encoding_and_signs_a_known_manifest PKCS#8 PEM, SEC1 PEM, PKCS#8 DER, SEC1 DER and raw scalar all load as the RFC key and sign one manifest to the same public_key_b64 and signature_b64 compressed public key; DER signature

Encoding the manifest's public key compressed passed every older test; only the new test caught it.

All three new tests were also run unchanged on main (p256 0.13.2, crypto-bigint 0.5.5, compiled in a separate worktree): all pass. Both versions therefore produce the RFC's bytes. Separately, the_committed_fixture_is_what_the_writer_writes_byte_for_byte (checkpoint records and signatures written with p256 0.13) passes on 0.14.

Scenarios: features/the-p256-wire-format-is-unchanged.feature, five, each bound (features-are-bound.sh: 888 scenarios, 1248 bindings, 0 broken).

Mutation check (not committed)

Each mutant was planted in product code, run, and reverted (files compared byte for byte afterwards).

Mutant Caught by
dpop::verify_proof: a signature failure (ErrorKind::InvalidSignature) is accepted by decoding again with signature validation disabled dpop::tests::a_proof_signed_by_a_key_other_than_the_one_it_carries_is_refused (plus three RSA bound tests)
audit_sign::build_signed_manifest signs with another key than the one it publishes lib: manifest_signature_verifies_and_pins_the_tip, empty_chain_signs_a_valid_zero_tip_manifest, the new RFC test; tests/it: audit_manifest::manifest_signature_verifies_and_matches_chain, empty_chain_signs_a_valid_zero_tip_manifest, manifest_tip_moves_after_a_new_mutation
checkpoints::check_statement accepts a keyryx statement with no signatures survived: no test serves an unsigned statement (25 lib tests, 3 tests/it/event_checkpoints tests green)
checkpoints::check_statement accepts a statement certifying another key checkpoints::tests::a_statement_that_does_not_certify_this_key_is_refused
DelegatedSigner::key_at ignores the statement window a_key_signs_only_inside_its_statement_window_in_whole_seconds, a_key_past_its_statement_is_never_used_and_the_record_is_unsigned, a_key_that_runs_out_mid_stream_gives_unsigned_records_not_stale_signatures, the_committed_fixture_is_what_the_writer_writes_byte_for_byte

checkpoints.rs is a writer and verifies no signature itself (invariant 128 leaves the statement's root signatures to the witness); the presence check on signatures is the nearest thing and has no test. That gap predates this PR and is left for a follow-up rather than widening this one.

Gates run locally

  • cargo fmt --all -- --check: clean (workspace and crates/cluster).
  • cargo clippy --workspace --all-targets with features cluster, wasm, apns, testing: 0 warnings. --all-features could not complete here because ort-sys (the onnx feature, unchanged) downloads ONNX Runtime in its build script and this sandbox blocks the host; CI's --all-features run covers it.
  • cargo test --all --no-fail-fast: 2417 tests, 2415 passed as root; the two that need a 0500 directory failed as root (invariant 78) and passed when re-run with dac_override dropped. Two further full runs without those capabilities: 2417 passed, 0 failed.
  • cargo test -p tokenfuse-gateway --features cluster --test cluster_backend: 6 passed. crates/cluster: clippy 0 warnings, 13 tests pass.
  • ./scripts/audit.sh: exit 0. Before this PR it stopped at error: 5 vulnerabilities found! on the workspace lockfile. After: no vulnerability in the workspace, crates/cluster or crates/radar; the remaining output is the existing warnings (unmaintained, unsound, yanked), which cargo-audit reports as allowed.
  • cargo tree -i crypto-bigint --all-features --target all: only 0.7.9; crypto-bigint@0.5.5 matches no package.
  • ./scripts/stated-numbers.sh (run without DAC override): 2417, README badge and PROGRESS.md agree. A first run reported the three figures one off, consistent with one test failing once; it did not repeat in three later full runs and was not identified.
  • constants.sh, d9-send-retention.sh, core-deps.sh, dto-boundary.sh, replicated-shape.sh, honest-claims.sh, features-are-bound.sh, runnable-quickstart.sh, pinned-installs.sh, compat-surface.sh, one-test-binary-per-crate.sh, no-owner-quotes.sh, no-html-from-data.sh, teeth-shards-cover-every-case.sh, attestation-is-derived.sh, run-owner-is-checked-first.sh, upstream-key-is-never-forwarded.sh, cloud-credentials-are-digested.sh, dashboard-key-stays-out-of-the-query.sh, client-keys-are-digested.sh, both-processes-configure-the-same-doors.sh: all pass.
  • ./scripts/gates-have-teeth.sh after the commits: 237 cases, every one ok, exit 0 (run with dac_override and dac_read_search dropped; as root the three stated-numbers cases are UNJUDGEABLE because the gate already fails on the two root-only tests).
  • The estate secret scan (secretscan / tree) lives in another repository and was not run locally; CI runs it.

Not proven

  • The APNs provider JWT (apns.rs) has no fixed-vector test: it signs with the same SigningKey::sign and to_bytes() the checkpoint test pins, but mints at the current time.
  • No test uses a signature from a real Apple Secure Enclave key; high-S acceptance is read from the crate source, not run.
  • The onnx feature was not built here (see clippy above).
  • The keyryx-signed manifest path was exercised only against this repository's stub keyryx, as before.
  • Lockfile-only bumps of rustls-webpki, webpki-roots and wasmtime were not exercised beyond the existing suite (no TLS handshake against a live provider, no wasm policy run outside the tests).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1

…visories

rustls-webpki 0.103.15 -> 0.103.16 (RUSTSEC-2026-0382) and webpki-roots
1.0.8 -> 1.0.11 (RUSTSEC-2026-0381) in both lockfiles, the workspace and
crates/cluster. wasmtime 49.0.3 -> 49.0.4 (RUSTSEC-2026-0378,
RUSTSEC-2026-0379) in the workspace lockfile only, staying on 49.x; the
cranelift and pulley crates move with it from 0.136.3 / 49.0.3 to
0.136.4 / 49.0.4.

Lockfile-only: no manifest changes, no new crate. crypto-bigint 0.5.5
(RUSTSEC-2026-0387) is the fifth advisory and needs the p256 0.14
migration in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
…026-0387)

p256 0.13 is pulled only by this workspace's own crates (cloud,
delegation, dpop, gateway). Moving all six manifest lines to 0.14 brings
elliptic-curve 0.14.3, ecdsa 0.17.0, pkcs8 0.11, sec1 0.8, signature 3.0
and rfc6979 0.6, and replaces crypto-bigint 0.5.5 with 0.7.9, so
`cargo tree -i crypto-bigint --all-features --target all` no longer shows
a 0.5.x.

The code changes are two renames, nothing else:
- `VerifyingKey::to_encoded_point(compress)` is `to_sec1_point(compress)`
  in ecdsa 0.17; `x()`, `y()` and `as_bytes()` on the result are unchanged.
- `SigningKey::random(&mut OsRng)` is deprecated, and rand_core is 0.10
  with no `OsRng`; the test fixtures that made a random key now call
  `<SigningKey as p256::elliptic_curve::Generate>::generate()`. Production
  key generation (gateway checkpoints) already drew bytes with getrandom
  0.2 and `SigningKey::from_slice`, which is unchanged.
`Signature::from_slice`, `to_bytes`, `VerifyingKey::from_sec1_bytes`,
`SecretKey::from_pkcs8_pem` / `from_sec1_pem` / `from_pkcs8_der` /
`from_sec1_der` keep their names and signatures. P-256 keeps
`NORMALIZE_S = false`, so a high-S signature still verifies and signing
still does not normalise s, as in 0.13.

One rustfmt collapse in an audit_sign test follows the shorter name; the
assertion is the same. The gateway manifest comment on hmac is corrected:
hmac 0.12 reached the lockfile through p256 0.13's rfc6979, and since 0.14
(which uses hmac 0.13) the gateway's own line is its only edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
The external P-256 values the suite already checked were three keyryx
thumbprints (measured from keyryx's Go code) and agent-passport's
checkpoint vectors, which it verifies but does not produce. Nothing held a
signature this code produces, a SEC1 public key, or a PKCS#8 / SEC1
private key parse to an external value, so a curve library that changed
one of those could pass the suite as long as it agreed with itself.
Three tests now hold the bytes to RFC 6979
appendix A.2.5 (a P-256 key, its point, its deterministic SHA-256
signatures), with the base64 forms, the RFC 7638 thumbprint and the
PKCS#8 / SEC1 encodings written out by Python `cryptography` 50.0.1,
never by p256:

- gateway::checkpoints: the checkpoint key's JWK x and y, its kid, and the
  raw r||s over "sample" and "test".
- cloud::devices: the RFC signature verifies under the uncompressed and
  compressed SEC1 key; with its halves swapped, or in DER, it is refused.
- cloud::audit_sign: the RFC key loads from PKCS#8 PEM, SEC1 PEM, PKCS#8
  DER, SEC1 DER and a raw scalar, and each signs the same empty-chain
  manifest to the same public key and signature bytes.

Each went red against a deliberately broken encoding first (padded
standard base64 for x and y; s before r; a DER-only signature parser; a
compressed public key and a DER signature in the manifest) and green on
the real code. All three also pass unchanged on main with p256 0.13.2,
so 0.13 and 0.14 put the same bytes on the wire. Encoding the manifest's
public key compressed passed every older test; only the new one caught it.

Scenarios: features/the-p256-wire-format-is-unchanged.feature, five,
each bound. The suite goes from 2414 to 2417; README.md and PROGRESS.md
say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
`secretscan / tree` flagged the PKCS#8 header of the RFC 6979 appendix
A.2.5 P-256 key that the audit-signing-key encoding test loads
(crates/cloud/src/audit_sign.rs, fingerprint 3021d90eb9437b2d, the same
header fingerprint already allowed for the test keys in
crates/cloud/tests/it/oidc.rs and crates/dpop/src/lib.rs). The key is a
published test vector and signs nothing. The SEC1 header in the same
file was already covered by its existing entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1
@TAIPANBOX
TAIPANBOX marked this pull request as ready for review October 26, 2026 04:05
@TAIPANBOX
TAIPANBOX enabled auto-merge October 26, 2026 04:05
@TAIPANBOX
TAIPANBOX merged commit a1d31f3 into main Oct 26, 2026
16 checks passed
@TAIPANBOX
TAIPANBOX deleted the fix/audit-2026-10-26 branch October 26, 2026 04:26
TAIPANBOX added a commit that referenced this pull request Oct 26, 2026
A patch release: the MCP broker refuses a JSON-RPC batch and any message
it cannot judge (#478), five RustSec advisories cleared with p256 0.14
(#480), and the teeth harness on eight shards (#481).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TAIPANBOX added a commit that referenced this pull request Oct 26, 2026
check_statement requires a non-empty `signatures` list before it reads a
statement (the witness verifies the signatures, invariant 128), but no
test served a statement without one, so a mutant that dropped the check
survived the p256 0.14 PR's mutation run (#480). The keyryx stub gains
two modes, no `signatures` and an empty list, and both must be refused
as not of the known shape. One scenario, bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants