#79's process-containment suite is #![cfg(unix)] file-wide, so its properties are ungraded on the Windows we ship #117

Closed
opened 2026-09-04 16:16:26 +02:00 by buildagent · 1 comment
Member

From the #76–#79 audit, verified first-hand and stated more narrowly than the audit put it, because the stronger version is false and worth not repeating.

What is true

crates/plugin-host/tests/containment.rs carries #![cfg(unix)] at line 26 — file-wide. Its own module doc says what it grades:

supervision.rs grades what a hostile GUEST can do. This file grades what is left standing if that containment is ever wrong — what the process holding the guest can see, hold, spawn and outlive.

That is #79's acceptance criterion 4, and on Windows none of it compiles, let alone runs. So on a platform we ship, the answers to "what can the worker process see, hold, spawn, and outlive" are ungraded.

What is NOT true, and why the distinction matters

The audit reported this as "no containment property has ever executed on Windows." That overstates it. crates/plugin-supervisor/src/contain.rs:2066 holds a #[cfg(windows)] test module with five job-object tests that do run on the self-hosted Windows runner:

  • a_job_is_created_and_the_child_is_assigned_to_it
  • closing_the_job_handle_kills_the_process_inside_it
  • a_process_in_a_job_does_not_outlive_the_parent_that_held_it
  • a_process_inside_the_job_cannot_spawn_past_the_active_process_limit
  • helper_holds_a_job_over_a_sleeper

So Windows does have executed containment properties, covering kill-on-close, non-outliving, and the active-process limit. The gap is specifically the C4 process-reach suite: what the confined process can see and hold — filesystem reach, descriptor inheritance, environment.

Recording the correction rather than the convenient version: the accurate claim is smaller, and a fix aimed at the overstated one would rebuild things that already work.

Two adjacent facts, both already documented in-tree

  • No plugin smoke on Windows or aarch64. windows-gate runs cargo test, which never runs an example, and it builds MSVC while the shipped archive is cross-linked windows-gnu. release.yml already concedes this is "evidence the code works on Windows, not that this archive was tested."
  • _prdoc/guides/80-abi-support-policy.md:289 already grades this "NOT MET for two of four". So the honest assessment exists in the tree and is not reflected in the issue state — the same pattern as #114, where the README overclaimed past our own threat model.

What closing this looks like

  1. Write cfg(windows) counterparts for the C4 process-reach properties that have a Windows meaning, and let the existing runner execute them. Several do not translate (there is no seccomp, and the filesystem story differs) — say which, in the file, rather than leaving the reader to infer coverage from an absence.
  2. Where a property genuinely cannot be graded on a platform, that must be visible in the product, not only in a test file's cfg. host.platform_unverified already exists as the honest two-line form of exactly this deferral; the question is whether it fires for the right set today.
  3. aarch64 stays deferred — no runner, and qemu-user does not emulate seccomp, so a containment leg there would be vacuous even if a runner appeared. That is a decision already taken; this issue does not reopen it.

#79. Same family as #114 (a user-facing claim exceeding the analysis behind it) and #116 (ceilings that never run or cannot fail).

From the #76–#79 audit, **verified first-hand and stated more narrowly than the audit put it**, because the stronger version is false and worth not repeating. ## What is true `crates/plugin-host/tests/containment.rs` carries `#![cfg(unix)]` at line 26 — **file-wide**. Its own module doc says what it grades: > `supervision.rs` grades what a hostile GUEST can do. This file grades what is left standing if that containment is ever wrong — what the process holding the guest can see, hold, spawn and outlive. That is #79's acceptance criterion 4, and on Windows **none of it compiles, let alone runs**. So on a platform we ship, the answers to "what can the worker process see, hold, spawn, and outlive" are ungraded. ## What is NOT true, and why the distinction matters The audit reported this as *"no containment property has ever executed on Windows."* **That overstates it.** `crates/plugin-supervisor/src/contain.rs:2066` holds a `#[cfg(windows)]` test module with five job-object tests that do run on the self-hosted Windows runner: - `a_job_is_created_and_the_child_is_assigned_to_it` - `closing_the_job_handle_kills_the_process_inside_it` - `a_process_in_a_job_does_not_outlive_the_parent_that_held_it` - `a_process_inside_the_job_cannot_spawn_past_the_active_process_limit` - `helper_holds_a_job_over_a_sleeper` So Windows **does** have executed containment properties, covering kill-on-close, non-outliving, and the active-process limit. The gap is specifically the C4 *process-reach* suite: what the confined process can **see** and **hold** — filesystem reach, descriptor inheritance, environment. Recording the correction rather than the convenient version: the accurate claim is smaller, and a fix aimed at the overstated one would rebuild things that already work. ## Two adjacent facts, both already documented in-tree - **No plugin smoke on Windows or aarch64.** `windows-gate` runs `cargo test`, which never runs an example, and it builds **MSVC** while the shipped archive is cross-linked **windows-gnu**. `release.yml` already concedes this is "evidence the code works on Windows, not that this archive was tested." - `_prdoc/guides/80-abi-support-policy.md:289` **already grades this "NOT MET for two of four"**. So the honest assessment exists in the tree and is not reflected in the issue state — the same pattern as #114, where the README overclaimed past our own threat model. ## What closing this looks like 1. Write `cfg(windows)` counterparts for the C4 process-reach properties that have a Windows meaning, and let the existing runner execute them. Several do not translate (there is no seccomp, and the filesystem story differs) — **say which, in the file**, rather than leaving the reader to infer coverage from an absence. 2. Where a property genuinely cannot be graded on a platform, that must be **visible in the product**, not only in a test file's cfg. `host.platform_unverified` already exists as the honest two-line form of exactly this deferral; the question is whether it fires for the right set today. 3. **aarch64 stays deferred** — no runner, and qemu-user does not emulate seccomp, so a containment leg there would be vacuous even if a runner appeared. That is a decision already taken; this issue does not reopen it. ## Related #79. Same family as #114 (a user-facing claim exceeding the analysis behind it) and #116 (ceilings that never run or cannot fail).
Author
Member

Fixed — Linux 12 → 14 tests, Windows 0 → 5. And two corrections to this issue's own text.

Correction 1: I wrote "five job-object properties". It is four.

helper_holds_a_job_over_a_sleeper is #[ignore]d — a process the tests drive, not a graded property. My list padded the count by one while arguing that a narrower claim was the honest one. Recording it rather than quietly fixing the number.

Correction 2: host.platform_unverified does not exist.

Step 2 of this issue's "what closing this looks like" rests on it — "host.platform_unverified already exists as the honest two-line form of exactly this deferral; the question is whether it fires for the right set today."

It does not exist. Confirmed twice independently: search_text returns total: 0 across all four separator spellings, and grep -rn 'platform_unverified' . --include='*.rs' --include='*.md' --include='*.yml' --include='*.json' returns nothing.

It was a plan that was never built, and it has been repeated as shipped behaviour — including in my own working notes, now corrected. The only statement of it anywhere in the tree is the new coverage table in containment.rs. It is not in the product. So "does it fire for the right set" was the wrong question; the right one is whether an unverified platform should say anything at all, and today it says nothing.

The fix

crates/plugin-host/tests/containment.rs: the file-wide #![cfg(unix)] is gone, replaced by per-item gating. Windows 0 → 5 tests, executing on the existing ci-windows.yml gate with no workflow change.

Running on Windows now: a private, empty working directory that goes with the worker; environment sanitation measured as inheritance through the worker_parent intermediate; the self-report channel closed by default; a_worker_never_claims_a_filter_it_does_not_have (must report Unsupported, never Enforced); and a_worker_says_what_its_probes_did_rather_than_omitting_them — the first execution anywhere of contain::observe's cfg(not(unix)) arm.

Deliberately not translated, each named in the file with its reason

seccomp denial (no seccomp); setsid → covered by a_job_is_created_and_the_child_is_assigned_to_it; setrlimit AS/FSIZE/CORE/NOFILE; RLIMIT_NPROC → a_process_inside_the_job_cannot_spawn_past_the_active_process_limit; PR_SET_PDEATHSIG → closing_the_job_handle_kills_the_process_inside_it and a_process_in_a_job_does_not_outlive_the_parent_that_held_it; VmPeak.

One genuinely open C4 property is stated as open rather than papered over: /proc/self/fd descriptor inheritance has no Windows counterpart here. Closing it needs NtQuerySystemInformation / handle-scan FFI written blind against a machine nobody can run — a worse trade than the recorded gap. aarch64 restated as deferred, not reopened.

Mutations

W1, W2, W4 and a re-run C3 went red with output pasted. W3 and W5 — the cfg(windows) arms — were NOT run, because there is no Windows machine here. They are marked NOT RUN in the file rather than left reading as though they had been.

That is the honest state and it is worth naming: the Windows tests are written and will execute on the runner, but their mutations are unverified until they do. contain.rs was not modified (md5-verified against HEAD).

Windows cross-clippy green: cargo clippy -p code-index-plugin-host --all-targets --target x86_64-pc-windows-gnu -- -D warnings, exit 0.

What this leaves

The gap this issue named is closed for every property that has a Windows meaning. What remains is one descriptor-inheritance property, and the larger fact that an unverified platform still discloses nothing to the operator — which is now a real absence rather than a mis-firing feature, and belongs with #114's disclosure half.

## Fixed — Linux 12 → 14 tests, Windows 0 → 5. And **two corrections to this issue's own text.** ### Correction 1: I wrote "five job-object properties". It is **four**. `helper_holds_a_job_over_a_sleeper` is `#[ignore]`d — a process the tests drive, not a graded property. My list padded the count by one while arguing that a *narrower* claim was the honest one. Recording it rather than quietly fixing the number. ### Correction 2: **`host.platform_unverified` does not exist.** Step 2 of this issue's "what closing this looks like" rests on it — *"`host.platform_unverified` already exists as the honest two-line form of exactly this deferral; the question is whether it fires for the right set today."* It does not exist. Confirmed twice independently: `search_text` returns `total: 0` across all four separator spellings, and `grep -rn 'platform_unverified' . --include='*.rs' --include='*.md' --include='*.yml' --include='*.json'` returns nothing. It was a **plan that was never built**, and it has been repeated as shipped behaviour — including in my own working notes, now corrected. The only statement of it anywhere in the tree is the new coverage table in `containment.rs`. **It is not in the product.** So "does it fire for the right set" was the wrong question; the right one is whether an unverified platform should say anything at all, and today it says nothing. ### The fix `crates/plugin-host/tests/containment.rs`: the file-wide `#![cfg(unix)]` is gone, replaced by per-item gating. **Windows 0 → 5 tests**, executing on the existing `ci-windows.yml` gate with **no workflow change**. Running on Windows now: a private, empty working directory that goes with the worker; environment sanitation measured as *inheritance* through the `worker_parent` intermediate; the self-report channel closed by default; `a_worker_never_claims_a_filter_it_does_not_have` (must report `Unsupported`, never `Enforced`); and `a_worker_says_what_its_probes_did_rather_than_omitting_them` — **the first execution anywhere of `contain::observe`'s `cfg(not(unix))` arm.** ### Deliberately not translated, each named in the file with its reason seccomp denial (no seccomp); `setsid` → covered by `a_job_is_created_and_the_child_is_assigned_to_it`; `setrlimit` AS/FSIZE/CORE/NOFILE; `RLIMIT_NPROC` → `a_process_inside_the_job_cannot_spawn_past_the_active_process_limit`; `PR_SET_PDEATHSIG` → `closing_the_job_handle_kills_the_process_inside_it` and `a_process_in_a_job_does_not_outlive_the_parent_that_held_it`; `VmPeak`. **One genuinely open C4 property is stated as open rather than papered over:** `/proc/self/fd` descriptor inheritance has no Windows counterpart here. Closing it needs `NtQuerySystemInformation` / handle-scan FFI written blind against a machine nobody can run — a worse trade than the recorded gap. aarch64 restated as deferred, not reopened. ### Mutations W1, W2, W4 and a re-run C3 went red with output pasted. **W3 and W5 — the `cfg(windows)` arms — were NOT run**, because there is no Windows machine here. They are marked `NOT RUN` in the file rather than left reading as though they had been. That is the honest state and it is worth naming: the Windows tests are *written* and will execute on the runner, but their mutations are unverified until they do. `contain.rs` was not modified (md5-verified against HEAD). Windows cross-clippy green: `cargo clippy -p code-index-plugin-host --all-targets --target x86_64-pc-windows-gnu -- -D warnings`, exit 0. ### What this leaves The gap this issue named is closed for every property that has a Windows meaning. What remains is one descriptor-inheritance property, and the larger fact that **an unverified platform still discloses nothing to the operator** — which is now a real absence rather than a mis-firing feature, and belongs with #114's disclosure half.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#117
No description provided.