Skip to content

fix: get CI green again — rustix RUSTSEC-2026-0486 and the ts spin tenant - #2916

Merged
chaliy merged 3 commits into
mainfrom
eve/clever-mccarthy-m0hntm
Nov 11, 2026
Merged

chaliy merged 3 commits into
mainfrom
eve/clever-mccarthy-m0hntm

Conversation

@chaliy

@chaliy chaliy commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Two red CI signals, two independent causes, neither from a code change in this
repo. Kept as two commits.

What changed

1. fix(deps): bump rustix to 1.1.6 for RUSTSEC-2026-0486 — fixes the
Audit job, currently red on every PR and on main.

RUSTSEC-2026-0486 was published against rustix on 2026-10-27 and
scripts/audit-lockfiles.sh reads the advisory database live, so the job went
red with no repository change. It is an unsoundness in six xattr functions
(fgetxattr, flistxattr, getxattr, lgetxattr, listxattr,
llistxattr), which mark unwritten bytes as initialized when handed a
zero-length buffer, covering >=1.0.0, <1.1.6. The scan runs
cargo audit --deny unsound, so it fails rather than warns.

Three lockfiles carried an affected version; all three move to the patched
1.1.6:

Lockfile Before After
Cargo.lock 1.1.4 1.1.6
crates/bashkit/fuzz/Cargo.lock 1.1.5 1.1.6
examples/hyperlight/host/Cargo.lock 1.1.4 1.1.6

rustix is transitive everywhere (tempfile, polling, memfd and four others) and
bashkit calls none of the six functions, so this is supply-chain hygiene rather
than a reachable defect. A patched release exists, so nothing is suppressed
and the threat model needs no new entry.

This also folds in the rust-dependencies group bump from #2913, which was
blocked on a stale supply-chain/imports.lock: toml 1.2.0 → 1.2.1,
serde_spanned 1.1.2 → 1.1.3, toml_datetime 1.1.2 → 1.1.3. All three are
already publisher-trusted to Ed Page (epage) through 2027-05-16, so refreshing
the publisher records was all cargo vet needed — no new audit or exemption.
rustix was already an exemption at 1.1.4 and stays one at 1.1.6: nobody
reviewed the 1000-line upstream diff, and an audit entry would assert a source
review that did not happen. #2913 can be closed as superseded.

2. test(ts): stop the spin tenant racing two resource limits — fixes the
nightly ts-stress-soak job. Test-only; no product change.

ts_mixed_hostile_and_good_tenants_stay_isolated asserted that its spin
tenant (while (true) {}, 300 ms max_duration) exits 124. But ZapCode
charges one allocation per VM stack push and never decrements the counter
(zapcode_vendor/sandbox.rs, track_allocation), so max_allocations is a
per-run budget of operations, not a live heap bound — that is max_memory.
The loop spends about one push per iteration, so the two limits race:

  • release burns the 1,000,000 default in a few milliseconds → exit 1,
    allocation limit exceeded
  • debug reaches the 300 ms clock first → exit 124

PR CI runs debug; the soak runs release. The assertion pinned 124, so it could
only ever hold in debug. Both outcomes are correct bounded termination, so the
spin tenant now asserts only that it stopped on one of its own limits, and each
limit is instead pinned on its own, deterministically in both profiles:

  • ts_runaway_loop_stops_on_the_wall_clock raises the allocation budget past
    what a 300 ms loop can spend, so the clock binds and exit 124 is exact. This
    is the case the soak was really meant to cover (TM-TS-027).
  • ts_runaway_loop_stops_on_the_allocation_budget lowers it to 50,000 against
    a 60 s limit, so the push budget binds and exit 1 is exact.

Why they are together

The Audit gate runs on every PR, so no PR — including a standalone version of
commit 2 — can go green until the rustix bump lands. Commit 2 was pushed first
and its CI run showed exactly one failure, Audit, on the advisory above.

Before / After

Audit — before (this PR's own first CI run, and every other open PR):

cargo audit ./Cargo.lock
error: 1 denied warning found!
Crate:    rustix
Version:  1.1.4
Warning:  unsound
Title:    xattr functions mark unwritten bytes as initialized when given a zero-length buffer
ID:       RUSTSEC-2026-0486

...repeated for crates/bashkit/fuzz/Cargo.lock (1.1.5) and
examples/hyperlight/host/Cargo.lock (1.1.4).

After, ./scripts/audit-lockfiles.sh locally:

warning: 3 allowed warnings found      # atomic-polyfill, ttf-parser, crypto-bigint
warning: 1 allowed warning found       # atomic-polyfill (fuzz)
./crates/bashkit-js: no known vulnerabilities found
./examples/bashkit-pi: no known vulnerabilities found
./examples/browser: no known vulnerabilities found
./examples: no known vulnerabilities found
./site: no known vulnerabilities found
./examples/docs-grep-agent/uv.lock: no known vulnerabilities found (47 packages)
=== audit script exit: 0 ===

The rustix denial is gone; the remaining warnings are the pre-existing allowed
ones. cargo vet --locked also succeeds:
Vetting Succeeded (97 fully audited, 79 partially audited, 585 exempted).

Nightly soak — before, in release:

thread 'tokio-rt-worker' panicked at ts_tenant_stress_tests.rs:115:13:
assertion `left == right` failed: Spin stderr: allocation limit exceeded
  left: 1
 right: 124
test result: FAILED. 0 passed; 1 failed; finished in 0.33s

It failed in 0.33 s — the allocation budget was gone long before the 300 ms
clock.

After, release at the nightly's 300 rounds
(BASHKIT_STRESS_ROUNDS=300 cargo test --release -p bashkit --features typescript --test integration -- ts_tenant_stress):

running 7 tests
test ts_tenant_stress_tests::ts_huge_single_allocations_are_refused_up_front ... ok
test ts_tenant_stress_tests::ts_host_call_loops_stop_at_the_wall_clock_limit ... ok
test ts_tenant_stress_tests::ts_runaway_loop_stops_on_the_allocation_budget ... ok
test ts_tenant_stress_tests::ts_runaway_loop_stops_on_the_wall_clock ... ok
test ts_tenant_stress_tests::ts_one_session_reuses_its_budget_across_many_calls ... ok
test ts_tenant_stress_tests::ts_abandoned_calls_do_not_poison_the_worker_threads ... ok
test ts_tenant_stress_tests::ts_mixed_hostile_and_good_tenants_stay_isolated ... ok

test result: ok. 7 passed; 0 failed; finished in 112.91s

And in debug, as PR CI runs it: test result: ok. 7 passed; 0 failed; finished in 2.09s.

Risk

  • Low
  • The dependency change is three lockfiles plus two supply-chain records; the
    diff touches only rustix and the three toml-family crates and nothing else.
    rustix 1.1.6 is a patch release whose only relevant change is the soundness
    fix. Workspace builds with http_client,ssh,sqlite,typescript and the main
    test slice passes ~7,900 tests.
  • The test change is assertions only. The one assertion that got weaker is the
    mixed test's spin tenant, and the coverage it gave up is now held by two
    stricter tests. A broken wall clock would leave
    ts_runaway_loop_stops_on_the_wall_clock hanging until the job timeout
    rather than failing fast, which is the usual failure mode for a timeout test.

Checklist

  • Tests added or updated
  • Backward compatibility considered (lockfile + internal test code; no public API)

Verified locally: ./scripts/audit-lockfiles.sh (exit 0), cargo vet --locked,
cargo fmt --check,
cargo clippy --all-targets --features typescript,http_client,ssh,sqlite -- -D warnings,
just check-okf, just check-doc-links,
cargo build --workspace --features http_client,ssh,sqlite,typescript,
cargo test --workspace --lib --bins --tests --features http_client,ssh,sqlite
(~7,900 pass; sole failure is ssh_supabase_connects, which dials the live
public supabase.sh on port 22 — refused in this sandbox, a network gate that
passes in CI), and the ts stress tests in both profiles as shown above.

The nightly `ts-stress-soak` job went red on its first scheduled run while
every PR run of the same test stayed green.

`ts_mixed_hostile_and_good_tenants_stay_isolated` asserted that the spin
tenant (`while (true) {}`, 300 ms `max_duration`) exits 124. But ZapCode
charges one allocation per VM stack push and never decrements the counter
(`zapcode_vendor/sandbox.rs`, `track_allocation`), so `max_allocations` is a
per-run budget of pushes, not a live heap bound. The loop spends about one per
iteration, so the two limits race: a release build burns the 1,000,000 default
in a few milliseconds and exits 1 with "allocation limit exceeded", while a
debug build reaches 300 ms first and exits 124. PR CI runs debug, the soak runs
release, so the assertion picked the winner of a build-speed race.

Both outcomes are correct bounded termination, so the spin tenant now asserts
only that it stopped on one of its own limits. Dropping a precise assertion
would have cost coverage, so each limit is instead pinned on its own, and
deterministically in either profile:

- `ts_runaway_loop_stops_on_the_wall_clock` raises the allocation budget past
  what a 300 ms loop can spend, so the clock binds and exit 124 is exact.
  This is the case the soak was really meant to cover (TM-TS-027).
- `ts_runaway_loop_stops_on_the_allocation_budget` lowers it to 50,000 against
  a 60 s limit, so the push budget binds and exit 1 is exact.

No product change: the limits already bounded the loop and no tenant could
reach the host abort either way.

Before: release soak fails in 0.33s, exit 1 "allocation limit exceeded" vs
expected 124, on 3 of 4 spin tenants.
After: 7/7 green in release at the nightly's 300 rounds (112.91s) and 7/7
green in debug (2.09s).

Knowledge records that `max_allocations` bounds operations rather than heap,
and why the spin assertion is deliberately loose.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 11, 2026 •

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 315e2a6 Commit Preview URL

Branch Preview URL
Nov 11 2026, 10:03 AM

The Audit job went red on every PR and on main without any change to the
repository: RUSTSEC-2026-0486 was published against rustix on 2026-10-27, and
`scripts/audit-lockfiles.sh` reads the advisory database live. This is exactly
the case the scheduled advisory scan exists for.

The advisory is an unsoundness in six xattr functions (`fgetxattr`,
`flistxattr`, `getxattr`, `lgetxattr`, `listxattr`, `llistxattr`), which mark
unwritten bytes as initialized when handed a zero-length buffer. It covers
`>=1.0.0, <1.1.6`, and the scan runs `cargo audit --deny unsound`, so it fails
rather than warns.

Three lockfiles carried an affected version, and all three move to the patched
1.1.6:

- `Cargo.lock` 1.1.4
- `crates/bashkit/fuzz/Cargo.lock` 1.1.5
- `examples/hyperlight/host/Cargo.lock` 1.1.4

rustix is transitive everywhere (tempfile, polling, memfd and four others), and
bashkit calls none of the six functions, so this is a supply-chain hygiene bump
rather than a reachable defect. A patched release exists, so nothing is
suppressed and the threat model needs no new entry.

Also folds in the `rust-dependencies` group bump from #2913, which was blocked
on a stale `supply-chain/imports.lock`: toml 1.2.0 -> 1.2.1, serde_spanned
1.1.2 -> 1.1.3 and toml_datetime 1.1.2 -> 1.1.3. All three are already
publisher-trusted to Ed Page (epage) through 2027-05-16, so refreshing the
publisher records in imports.lock is all `cargo vet` needed - no new audit or
exemption. rustix was already an exemption at 1.1.4 and stays one at 1.1.6:
nobody reviewed the 1000-line upstream diff, and an audit entry would assert a
source review that did not happen.

Verified: `scripts/audit-lockfiles.sh` exits 0 with the rustix denial gone
(remaining atomic-polyfill, ttf-parser and crypto-bigint warnings are the
pre-existing allowed ones), `cargo vet --locked` succeeds (97 fully audited,
79 partially audited, 585 exempted), the workspace builds with
http_client,ssh,sqlite,typescript, and the main test slice passes ~7,900 tests.
The only local failure is `ssh_supabase_connects`, which dials the live public
supabase.sh over port 22; this sandbox refuses outbound 22, and that test is a
network gate that passes in CI.
@chaliy chaliy changed the title test(ts): stop the spin tenant racing two resource limits fix: get CI green again — rustix RUSTSEC-2026-0486 and the ts spin tenant Nov 11, 2026

@bashkit-pr-reviewer bashkit-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test-only fix for the spin-tenant race looks right; one minor comment-accuracy note below. No prod code, no sandbox-boundary change.

@bashkit-pr-reviewer

bashkit-pr-reviewer Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Review summary (PR #2916, head 315e2a6)

Verdict: Looks good. The retitled PR now carries two independent CI fixes as two commits, and both check out. No new findings; the P2 from the previous revision (spin-comment-refs-wrong-budget) is resolved — the Spin branch now states explicitly that tenant() keeps the default max_allocations.

What changed since the last review: (1) fix(deps): bump rustix to 1.1.6 for RUSTSEC-2026-0486 — all three lockfiles (Cargo.lock, fuzz/Cargo.lock, hyperlight/host/Cargo.lock) move rustix to 1.1.6 with a consistent checksum, and supply-chain/config.toml lifts the vet exemption to 1.1.6 so cargo vet --locked stays green; the toml publisher bumps folded in are the documented #2913 group update. (2) The ts spin-tenant fix is unchanged in behavior; only its comments/knowledge note were refined.

What I verified in-repo: rustix 1.1.6 + checksum e5e76742… in all three locks; exemption version matches; scripts/audit-lockfiles.sh discovers every lockfile (so all three needed the bump); the windows-sys 0.61.2→0.52.0 / syn 3.0.9→2.0.119 edge flips are resolver re-resolution with unchanged crate versions/checksums and both majors retained in-tree (rustix 1.1.6's own edge is windows-sys 0.52.0), so they are benign; the unsound-xattr functions named in the PR are not called by bashkit (transitive-only exposure, hygiene bump, no threat-model entry needed — agreed).

Findings: none blocking. Prior P2 resolved.

Could not review: the advisory itself (RUSTSEC-2026-0486 contents, rustix 1.1.6 patch validity) — the advisory DB is read live and this shell has no network; trusting the author's ./scripts/audit-lockfiles.sh exit-0 report. No build/test per sandbox limits (reasoned from code only).

Review follow-up. The comment pointed at SPIN_ALLOCATIONS to explain which
limit stops this tenant, but the tenant is built by `tenant(16, 300)`, which
keeps the default `max_allocations`; SPIN_ALLOCATIONS is only the budget for
`ts_runaway_loop_stops_on_the_wall_clock`. A reader following the pointer would
have tried to tune this tenant through a constant that does not govern it.

Names the default budget instead and keeps the explanation local. No assertion
or behavior change.
@chaliy
chaliy merged commit 4c3f625 into main Nov 11, 2026
51 checks passed
@chaliy
chaliy deleted the eve/clever-mccarthy-m0hntm branch November 11, 2026 10:34
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