Skip to content

chore(deps): bump russh to 0.65.0 and the napi group, with vet audits - #2822

Closed
chaliy wants to merge 1 commit into
mainfrom
eve/clever-mccarthy-s7e3pz
Closed

chaliy wants to merge 1 commit into
mainfrom
eve/clever-mccarthy-s7e3pz

Conversation

@chaliy

@chaliy chaliy commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

What changed

russh 0.64.1 → 0.65.0, napi 3.15.0 → 3.16.1, napi-derive 3.6.13 → 3.7.0, and
napi-derive-backend 6.1.6 → 7.0.0 behind it, plus the four cargo vet delta audits
the bump needs. No source changes — only the root manifest, the root lockfile,
supply-chain/audits.toml and the knowledge log.

No behavior change for callers. The SSH builtins keep their API and their sandbox
boundary; the Node binding keeps its ABI shape. What users get is the hardening in
these releases, listed below.

Targeted cargo update -p keeps the lockfile to exactly those four entries, with none
of the opportunistic re-resolution Dependabot's own run carried. No other lockfile in the
tree (crates/bashkit/fuzz, examples/hyperlight, examples/hyperlight/host) references
these crates, so the --locked failure that sank #2806/#2810 cannot recur here — that one
came from the stale fancy-regex pin already fixed on main.

Why

Supersedes Dependabot #2814, which bumped the versions alone and failed Audit with four
unvetted crates. Dependabot cannot write supply-chain audits, so a group bump never goes
green on its own — the same shape as #2792 and #2790 before it. Check was red only as the
aggregate gate reporting Audit; every other job on #2814 was green.

This also picks up six russh advisories, four of which touch paths this tree reaches.

Before / After

Before — cargo vet --locked on the bumped lockfile:

Vetting Failed!

4 unvetted dependencies:
  napi:3.16.1 missing ["safe-to-deploy"]
  napi-derive:3.7.0 missing ["safe-to-deploy"]
  napi-derive-backend:7.0.0 missing ["safe-to-deploy"]
  russh:0.65.0 missing ["safe-to-deploy"]

After:

Vetting Succeeded (98 fully audited, 86 partially audited, 577 exempted)

No new exemptions, no broadened publisher trust, and no stale exemption left behind — the
existing russh 0.63.3 and napi 3.12.4 exemptions stay because they anchor these delta
chains.

Builds and tests, locally:

$ cargo build -p bashkit --features ssh
Finished (russh v0.65.0 compiled, no source changes)

$ cargo build -p bashkit-js
Finished (napi 3.16.1 + napi-derive 3.7.0 + napi-derive-backend 7.0.0)

$ cargo test --features ssh -p bashkit --test ssh_builtin_tests
test result: ok. 24 passed; 0 failed

$ cargo test --workspace --lib --bins --tests --features http_client,ssh,sqlite
test result: ok. 3938 passed; 0 failed
test result: ok. 2636 passed; 0 failed; 4 ignored
(all other targets ok)

$ cargo test --workspace --doc --features http_client,ssh,sqlite
test result: ok. 177 passed; 0 failed; 62 ignored

$ cargo fmt --check && cargo clippy --all-targets --features http_client,ssh,sqlite -- -D warnings
clean

$ just check-okf && just check-doc-links
knowledge: OKF v0.2 conformant (47 concepts, 7 index files, 1 log file)
docs OK: 210 relative links and 10 dependency versions across 51 files

$ python3 -m unittest discover -s scripts/tests -p test_ci_supply_chain.py
Ran 9 tests — OK

The 24 ssh tests include the sandbox-boundary cases — ssh_blocked_host,
ssh_wildcard_host, ssh_not_configured, scp_blocked_host, scp_not_configured,
sftp_not_configured — so the allowlist behavior is intact across the russh bump.

Not green locally: ssh_supabase::ssh_supabase_connects. It dials the live public
supabase.sh endpoint, and outbound port 22 is blocked in this sandbox entirely — a raw
socket to supabase.sh:22 and to github.com:22 both time out, so the test cannot pass
here on any russh version. It is a required CI gate and runs there.

What the audits actually say

The four delta audits record a source-safety review of each published package diff. Common
to all four: no new build.rs behavior (napi's is byte-identical, the others have none),
no new std::fs / std::net / std::process / std::env reference, and no new dependency.

  • russh (716 changed lines under src/) — still exactly one unsafe block, the
    offset_from in server/encrypted.rs, textually unchanged. async-trait and its crate
    feature are removed. The two added tokio::net::Unix* references are both inside
    #[cfg(test)] modules. The breaking change is server-side (Handler::auth_publickey_offered
    / auth_publickey now take server::PublicKeyOffer / server::VerifiedPublicKey); this tree
    implements only client::Handler, which is why it builds unchanged. Of the six advisories:
    EXT_INFO is now capped at two per session per RFC 8308 §2.4, bounding previously unbounded
    received_extensions growth a malicious server could drive (client-reachable); a
    peer-declared zero maximum_packet_size is rejected in both OpenChannelMessage::parse and
    the new ChannelOpenConfirmation::parse, which is reachable here via the confirmation a
    server sends for our channel open; certificate nesting is bounded at MAX_CERT_NESTING = 8
    -cert-v01@ markers, guarding parse_public_key and PublicKeyOrCertificate::decode, and
    this tree uses PublicKeyOrCertificate. The remaining three are agent-server issues
    (pageant framing, SSH_AGENT_CONSTRAIN_CONFIRM dedup, the defunct Agent::confirm) and are
    unreachable — no SSH agent server here. Separately, WindowSize::update(value) becomes
    grow(amount) with saturating_add: the old code overwrote the window with the adjustment
    and could therefore shrink it, against RFC 4254 §5.2's additive semantics.
  • napi (1241 changed lines, 10 files) — unsafe rises 1812 → 1844, but that is
    centralization, not new raw-pointer work: a new unsafe fn call_threadsafe_function_with_data
    absorbs call sites that already held those blocks. It is also the real fix — Node-API takes
    ownership of the boxed call data only on napi_ok, so on napi_queue_full / napi_closing
    the box previously leaked; it is now reclaimed, and dropped only after the aborted read
    guard releases, because those destructors are user code that can relock the same handle.
    ClassId compacts from Option<TypeId> to Option<NonZeroU32> via an append-only intern
    registry bounded at MAX_CLASS_IDS = 65_536 with an explicit full error; append-only is what
    keeps an ID valid for every REFERENCE_MAP entry that recorded it. That module is gated to
    non-napi8, and bashkit-js selects napi6, so it is the path compiled here.
  • napi-derive — no source change at all; only the backend requirement moves.
  • napi-derive-backend (156 lines, codegen/AST only) — unsafe unchanged at 73, zero
    ambient references in either version. The major is a public-AST break (NapiFn and
    NapiStructField gain symbol_for / well_known_symbol), with napi-derive 3.7.0 the only
    consumer and pinned to exactly 7.0.0. It also carries a genuine fix: the generated property
    key now includes is_static, so a static and an instance member of the same name no longer
    collapse into one descriptor.

Each note states its own scope — source safety review of the delta, not a cryptographic audit
and not a review of the whole crate.

Worth a maintainer's eye: these four audits were written during this maintenance run, on the
strength of the review described above, following the convention established by #2792
("record its vet audits"). If you would rather attest these yourself, drop the four entries
and the rest of the PR still stands.

Risk

  • Low for the mechanics: no source change, no manifest change beyond the russh requirement,
    both the SSH and Node surfaces build and test clean, and no other lockfile is involved.
  • Low–Medium on the supply-chain surface, and worth naming: four deltas on the sandboxed SSH
    client and the published Node ABI now carry audits rather than exemptions. Every advisory in
    the russh delta narrows, and the napi changes are leak and identity fixes, so the direction of
    travel is toward less attack surface — but napi-derive-backend is a major bump, and the one
    live-endpoint SSH test is proved only by CI, not locally.

Checklist

  • Tests added or updated — none needed; the existing ssh suite and the vet gate are the tests
  • Backward compatibility considered — no API change; the russh breaking change is server-side only
  • cargo vet --locked passing, with no new exemptions
  • Knowledge updated (knowledge/log.md)

Follow-ups

https://claude.ai/code/session_01Wy7MBup4thzdX8Mm38y59z


Generated by Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Supersedes Dependabot #2814, which bumped the versions alone and failed Audit
with four unvetted crates. Dependabot cannot write supply-chain audits, so a
group bump never goes green on its own.

russh 0.64.1 -> 0.65.0, napi 3.15.0 -> 3.16.1, napi-derive 3.6.13 -> 3.7.0 and
napi-derive-backend 6.1.6 -> 7.0.0 behind it. Targeted `cargo update -p` keeps
the lockfile to those four entries, with no opportunistic re-resolution. No
other lockfile in the tree references these crates.

russh 0.65.0 needs no source change: its breaking change is server-side
(Handler::auth_publickey_offered and auth_publickey now take
server::PublicKeyOffer and server::VerifiedPublicKey) and this tree implements
only client::Handler. Four of its six advisories harden paths it reaches, and
all four narrow -- EXT_INFO is capped at two per session per RFC 8308 section
2.4, a peer-declared zero maximum_packet_size is rejected in both channel-open
directions, and certificate nesting is bounded at 8 `-cert-v01@` markers on the
live PublicKeyOrCertificate path. The agent-server advisories are unreachable;
this tree runs no SSH agent server. Channel windows now saturating_add the
adjustment instead of overwriting it, matching RFC 4254 section 5.2, where the
old overwrite could shrink the window. async-trait leaves the graph.

napi's unsafe count rises only because the threadsafe-function call sites are
centralized into one helper, which reclaims the boxed call data Node-API does
not take on a non-ok status and drops it after the aborted guard releases,
because those destructors are user code that can relock the handle. ClassId
compacts to Option<NonZeroU32> through an append-only registry bounded at
65_536, in the module gated to non-napi8 -- which is what bashkit-js selects.
The backend major is a public-AST break (symbol_for, well_known_symbol) plus a
static-flag fix: the generated property key now carries is_static, so a static
and an instance member of one name no longer collapse into one descriptor.

Four hand delta audits chained onto the existing entries record the review, so
`cargo vet --locked` passes without new exemptions or broadened publisher trust.

Claude-Session: https://claude.ai/code/session_01Wy7MBup4thzdX8Mm38y59z
@bashkit-pr-reviewer

Copy link
Copy Markdown

Review verdict

Looks good to merge. Dependency-only bump with the four matching cargo vet delta audits; no source changes. I found no correctness, sandbox, or supply-chain issues.

Findings

None — no P0/P1/P2.

What I checked beyond the diff

  • Checked out PR head (0564845) at /workspace/bashkit and read the surrounding files, not just the hunks.
  • Cargo.toml: russh = "0.65" is the single manifest change; workspace napi = "3.8.6" / napi-derive = "3.5.5" already admit the new lock versions via caret, so no manifest edit was needed there. Cargo.lock endpoints (russh 0.65.0, napi 3.16.1, napi-derive 3.7.0, napi-derive-backend 7.0.0) match the four new [[audits.*]] deltas exactly.
  • supply-chain/audits.toml: audit chain is continuous with no gaps (napi 3.15.0→3.16.1 follows 3.14.2→3.15.0; napi-derive 3.6.13→3.7.0 follows 3.6.12→3.6.13; backend 6.1.6→7.0.0 follows 6.1.5→6.1.6; russh 0.64.1→0.65.0 follows 0.63.3→0.64.1). Notes record no new unsafe, no new std::fs/net/process/env access, no new dependencies/build scripts, consistent with a deps-only bump.
  • Caller impact: crates/bashkit/src/builtins/ssh/russh_handler.rs implements only russh::client::Handler and calls russh::client::connect; no russh::server usage exists in crates/, so the server-side breaking change (auth_publickey* signature move) plausibly needs no source change. The tree does use PublicKeyOrCertificate, so the audited cert-nesting bound is on a live path, as the log entry claims.
  • knowledge/log.md: updated in the same change with the dependency rationale and a pointer to the Dependency Policy, per project rules. No threat-model update needed — the audits assert no new ambient capabilities.
  • knowledge/operations/dependencies.md: skimmed for policy consistency; nothing stale introduced.

Not reviewed / limits

  • Did not build or run tests (no cargo execution in this environment); the PR's claim of 24 passing ssh builtin tests is taken at face value. Diff was complete (truncated: false).
  • Upstream crate source diffs (e.g. russh 716-line delta, napi 1241-line delta) were reviewed only via the audit notes, not fetched independently — no network verification of advisory IDs or line counts.

chaliy commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Audit is red, and not for this PR's reasons

The failing step is cargo audit on the root lockfile, not cargo vet. cargo vet --locked — the gate this PR exists to satisfy — passes:

Vetting Succeeded (98 fully audited, 86 partially audited, 577 exempted)

What fails is a brand-new advisory on a crate this diff does not touch:

Crate:    calamine
Version:  0.37.0
Title:    Panic and unbounded allocation (OOM) in the CFB/OLE2 parser on malformed input
Date:     2026-10-28
ID:       RUSTSEC-2026-0436
Severity: 7.5 (high)
Solution: Upgrade to >=0.38.0

Why it isn't this PR's

calamine is at 0.37.0 on main and 0.37.0 here — byte-identical entries; git diff origin/main...HEAD -- Cargo.lock does not mention it. This PR moves four crates (russh, napi, napi-derive, napi-derive-backend) and nothing else. cargo audit is a function of the lockfile and the advisory database, so an identical lockfile entry gives an identical result on main.

main's last CI run (6291, 07:28Z today) was green including Audit; this PR's run at 13:15Z is red. The lockfile did not change between them, so the advisory entered the RustSec database in that window. main is latently red: its next push, and tonight's scheduled advisories job, will fail the same way. That is a main problem this PR happened to surface first.

No fix exists to port yet

The advisory's patched range is >= 0.38.0 and 0.38.0 is published, but it cannot be reached from this tree:

$ cargo update -p calamine --precise 0.38.0
error: failed to select a version for the requirement `calamine = "^0.37"`
candidate versions found which didn't match: 0.38.0
required by package `markkit v0.3.0`

markkit 0.3.0 (released 2026-11-01, the latest published version) requires calamine = "^0.37", which excludes 0.38.0. So the fix needs an upstream markkit release that widens or bumps that requirement. There is no version of markkit or markkit-cli available that does.

The remaining options are all policy calls rather than ports, which is why I am not making one here:

  • Suppress RUSTSEC-2026-0436 in IGNORED_ADVISORIES / deny.toml with a threat-model entry. This is the mechanism the repo has, but its documented rationale is reserved for advisories "published against the newest release of a package, where there is nothing to upgrade to" — and here 0.38.0 exists. Suppressing would be a deliberate widening of that policy.
  • Wait for upstream markkit and bump it when it lands.
  • Drop or re-gate the markkit feature, which is out of scope for a dependency bump.

Worth weighing either way: the advisory is denial-of-service only, with no memory unsafety (safe Rust turns the out-of-bounds read into a bounds-checked panic), and markkit is off by default. But it is reachable through open_workbook / Xls::new / Xlsb::new on an attacker-supplied file, which is exactly what the markkit builtin does with VFS content when the feature is on — the panic path is not bounded by the CliHost::limits clamp that TM-DOS-139 records.

Where that leaves this PR

Everything this PR is responsible for is green locally — cargo vet --locked, cargo fmt --check, clippy -D warnings, the full test slice (3938 + 2636 + the 24 ssh builtin tests, including the sandbox-boundary cases), doc tests, check-okf, check-doc-links, and the supply-chain script tests. It is blocked only on an advisory it did not introduce, on a crate it does not touch, and it must not merge while Audit is red.

I am leaving this to a maintainer decision rather than widening the PR or relaxing the audit policy on my own.


Generated by Claude Code

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 0564845 Commit Preview URL

Branch Preview URL
Nov 03 2026, 01:14 PM

chaliy commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #2826, merged as c147971 — that PR is this change rebased onto current main, with its audits and analysis intact.

For the record, the diagnosis in this comment was correct and is what unblocked it: Audit was red here on RUSTSEC-2026-0436 (calamine 0.37), a latent main problem this PR surfaced rather than caused, and the call to leave it to a maintainer rather than widen the PR or relax the audit policy was the right one. markkit 0.3.1 has since shipped with calamine 0.38, so #2823 took that path — no suppression, no policy widening — and Audit is green on main again.

The rebase needed only a knowledge/log.md resolution (both entries kept); Cargo.lock auto-merged, and the tree now resolves single copies of calamine 0.38.0, markkit 0.3.1 and fetchkit 0.7.1 alongside the four bumped crates. Re-verified on the rebased head before merge: just pre-pr green (7317 tests, Vetting Succeeded), the 24 ssh builtin tests, and bashkit-js building on the new napi.


Generated by Claude Code

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.

1 participant