From 190dcae64d9762d59653ba021ca2e594add427ec Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:32:29 -0700 Subject: [PATCH 01/23] test: require observed Windows preflight signals --- .../examples/startup_benchmark_preflight.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index f751746cb421..62a56cb80f2b 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -2123,4 +2123,26 @@ mod tests { SupervisorStartupEvent::ProductOutputClosed(Ok(())) )); } + + #[test] + fn unsupported_windows_observations_remain_unavailable() { + let observations = windows_preflight_observations(None); + + assert_eq!(observations.grandchild_in_job, None); + assert_eq!(observations.active_process_zero, None); + assert_eq!(observations.caller_se_impersonate_enabled, None); + assert_eq!(observations.standard_handles_valid, None); + assert_eq!(observations.explicit_handle_list, None); + } + + #[test] + fn observed_windows_job_membership_is_relayed_without_inventing_other_signals() { + let observations = windows_preflight_observations(Some(true)); + + assert_eq!(observations.grandchild_in_job, Some(true)); + assert_eq!(observations.active_process_zero, None); + assert_eq!(observations.caller_se_impersonate_enabled, None); + assert_eq!(observations.standard_handles_valid, None); + assert_eq!(observations.explicit_handle_list, None); + } } From e11b788ac36d2eda6ee9e086c36fa327f3d4249d Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:35:21 -0700 Subject: [PATCH 02/23] fix: fail closed on unavailable Windows evidence --- .../examples/startup_benchmark_preflight.rs | 37 +++++++++++++++---- .../startup_benchmark_support/report.rs | 2 + 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index 62a56cb80f2b..3be3bf859eaa 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -132,6 +132,29 @@ struct ChildProbeEvidence { windows_in_job: Option, } +#[derive(Debug, PartialEq)] +struct WindowsPreflightObservations { + grandchild_in_job: Option, + active_process_zero: Option, + caller_se_impersonate_enabled: Option, + standard_handles_valid: Option, + explicit_handle_list: Option, +} + +fn windows_preflight_observations( + grandchild_in_job: Option, +) -> WindowsPreflightObservations { + // Keep only observations produced by a probe. Do not infer process, privilege, or handle + // state from supervisor exit status or from the fact that this code runs on Windows. + WindowsPreflightObservations { + grandchild_in_job, + active_process_zero: None, + caller_se_impersonate_enabled: None, + standard_handles_valid: None, + explicit_handle_list: None, + } +} + #[derive(Debug, Deserialize, Serialize)] struct InboundProbeEvidence { bound_address: Option, @@ -1269,6 +1292,7 @@ fn run_controller(values: &[String]) -> Result<()> { }; #[cfg(not(windows))] let bootstrap_evidence: Option = None; + let windows_observations = windows_preflight_observations(child_evidence.windows_in_job); let evidence = PreflightEvidence { schema_version: 8, backend, @@ -1293,15 +1317,12 @@ fn run_controller(values: &[String]) -> Result<()> { windows_low_integrity: probe.windows_low_integrity, windows_no_enabled_privileges: probe.windows_no_enabled_privileges, windows_registry_write_denied: probe.windows_registry_write_denied, - // The restricted bootstrap proves the suspended product belongs to its exact private Job. - // The detached child then stays in that non-breakaway Job until cleanup proves EOF. - windows_grandchild_in_job: cfg!(windows).then_some(status.success() && contained), + windows_grandchild_in_job: windows_observations.grandchild_in_job, windows_breakaway_denied: probe.windows_breakaway_denied, - windows_active_process_zero: cfg!(windows).then_some(status.success() && contained), - // The Windows supervisor enables and verifies this privilege before it sends READY. - windows_caller_se_impersonate_enabled: cfg!(windows).then_some(true), - windows_standard_handles_valid: cfg!(windows).then_some(true), - windows_explicit_handle_list: cfg!(windows).then_some(true), + windows_active_process_zero: windows_observations.active_process_zero, + windows_caller_se_impersonate_enabled: windows_observations.caller_se_impersonate_enabled, + windows_standard_handles_valid: windows_observations.standard_handles_valid, + windows_explicit_handle_list: windows_observations.explicit_handle_list, windows_bootstrap_sha256: bootstrap_evidence .as_ref() .map(|evidence| evidence.bootstrap_sha256.clone()), diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/report.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/report.rs index 4eff8d578ce6..5b0f3037f708 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/report.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/report.rs @@ -404,6 +404,8 @@ impl SandboxPreflightEvidence { && self.windows_proofs_absent() } "windows-restricted-token-job" => { + // A missing native observation is a failed proof. Never treat unavailable + // Windows signals as an implicit success. self.linux_no_new_privs.is_none() && self.linux_effective_capabilities_zero.is_none() && self.linux_provenance_absent() From 3fb1a570740df8a4719c0d1bf6b78f4c847f7534 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:41:45 -0700 Subject: [PATCH 03/23] test: distinguish unavailable Windows claims from failures --- .../examples/startup_benchmark_preflight.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index 3be3bf859eaa..78b16dd8a6c3 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -2166,4 +2166,25 @@ mod tests { assert_eq!(observations.standard_handles_valid, None); assert_eq!(observations.explicit_handle_list, None); } + + #[test] + fn missing_windows_observation_is_skippable_but_not_verified() { + let observations = windows_preflight_observations(None); + + assert_eq!( + classify_windows_preflight_observations(&observations), + WindowsPreflightObservationState::Unverified, + ); + } + + #[test] + fn false_windows_observation_is_failure_not_skippable() { + let mut observations = windows_preflight_observations(None); + observations.active_process_zero = Some(false); + + assert_eq!( + classify_windows_preflight_observations(&observations), + WindowsPreflightObservationState::Failed, + ); + } } From e66ef589c4cabd4a0d512f1ce985e1ee56482752 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:43:03 -0700 Subject: [PATCH 04/23] fix: classify unavailable Windows claims explicitly --- .../examples/startup_benchmark_preflight.rs | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index 78b16dd8a6c3..c6ca3c18c773 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -3,6 +3,10 @@ mod startup_benchmark_appcontainer; mod startup_benchmark_protocol; use std::env; +#[cfg(windows)] +use std::error::Error; +#[cfg(windows)] +use std::fmt::{self, Display, Formatter}; use std::fs::{self, OpenOptions}; use std::io::{self, Read, Write}; use std::net::{SocketAddr, TcpListener, TcpStream, UdpSocket}; @@ -36,7 +40,27 @@ const MAX_SUPERVISOR_STDERR_BYTES: usize = 64 * 1024; const MAX_PRODUCT_EVENT_BYTES: usize = 64 * 1024; const MAX_BOOTSTRAP_CHECKPOINT_BYTES: u64 = 64 * 1024; -#[derive(Serialize)] +#[cfg(windows)] +const WINDOWS_CLAIM_UNVERIFIED_EXIT: i32 = 78; +#[cfg(windows)] +const WINDOWS_CLAIM_UNVERIFIED_REASON: &str = + "trusted Windows preflight could not observe all required native security signals"; + +#[cfg(windows)] +#[derive(Debug)] +struct WindowsClaimUnavailable; + +#[cfg(windows)] +impl Display for WindowsClaimUnavailable { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(WINDOWS_CLAIM_UNVERIFIED_REASON) + } +} + +#[cfg(windows)] +impl Error for WindowsClaimUnavailable {} + +#[derive(Clone, Serialize)] struct PreflightEvidence { schema_version: u32, backend: String, @@ -155,6 +179,36 @@ fn windows_preflight_observations( } } +#[derive(Debug, PartialEq, Eq)] +enum WindowsPreflightObservationState { + Verified, + Unverified, + Failed, +} + +fn classify_windows_preflight_observations( + observations: &WindowsPreflightObservations, +) -> WindowsPreflightObservationState { + let values = [ + observations.grandchild_in_job, + observations.active_process_zero, + observations.caller_se_impersonate_enabled, + observations.standard_handles_valid, + observations.explicit_handle_list, + ]; + if values.iter().any(Option::is_none) { + if values.iter().all(|value| *value != Some(false)) { + WindowsPreflightObservationState::Unverified + } else { + WindowsPreflightObservationState::Failed + } + } else if values.iter().all(|value| *value == Some(true)) { + WindowsPreflightObservationState::Verified + } else { + WindowsPreflightObservationState::Failed + } +} + #[derive(Debug, Deserialize, Serialize)] struct InboundProbeEvidence { bound_address: Option, @@ -640,6 +694,11 @@ impl SupervisorEventOwner { fn main() { if let Err(error) = run() { + #[cfg(windows)] + if error.downcast_ref::().is_some() { + eprintln!("cmux-tui startup sandbox preflight: {error:#}"); + std::process::exit(WINDOWS_CLAIM_UNVERIFIED_EXIT); + } eprintln!("cmux-tui startup sandbox preflight: {error:#}"); std::process::exit(1); } @@ -1443,6 +1502,10 @@ fn run_controller(values: &[String]) -> Result<()> { || !platform_proofs_pass(&evidence) || evidence.timing_records != 1 { + #[cfg(windows)] + if windows_claim_unverified_only(&evidence) { + return Err(WindowsClaimUnavailable.into()); + } bail!( "sandbox preflight invariant failed; see {}; supervisor stderr: {}", output.display(), @@ -1593,6 +1656,29 @@ fn sha256_file(path: &Path, name: &str) -> Result { Ok(format!("{:x}", Sha256::digest(bytes))) } +#[cfg(windows)] +fn windows_claim_unverified_only(evidence: &PreflightEvidence) -> bool { + let observations = WindowsPreflightObservations { + grandchild_in_job: evidence.windows_grandchild_in_job, + active_process_zero: evidence.windows_active_process_zero, + caller_se_impersonate_enabled: evidence.windows_caller_se_impersonate_enabled, + standard_handles_valid: evidence.windows_standard_handles_valid, + explicit_handle_list: evidence.windows_explicit_handle_list, + }; + if classify_windows_preflight_observations(&observations) + != WindowsPreflightObservationState::Unverified + { + return false; + } + let mut complete = evidence.clone(); + complete.windows_grandchild_in_job = Some(true); + complete.windows_active_process_zero = Some(true); + complete.windows_caller_se_impersonate_enabled = Some(true); + complete.windows_standard_handles_valid = Some(true); + complete.windows_explicit_handle_list = Some(true); + platform_proofs_pass(&complete) +} + fn platform_proofs_pass(evidence: &PreflightEvidence) -> bool { #[cfg(target_os = "linux")] { From 8b4221a9f1a2dc50260bb6272053ed2ea8a5103f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:44:04 -0700 Subject: [PATCH 05/23] test: require explicit skipped Windows benchmark claims --- .../scripts/test_startup_benchmark_claim.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 cmux-tui/scripts/test_startup_benchmark_claim.py diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py new file mode 100644 index 000000000000..cfdbabdb945d --- /dev/null +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Regression tests for explicit skipped Windows startup claims.""" + +from __future__ import annotations + +import importlib.util +import unittest +from pathlib import Path + + +SCRIPT = Path(__file__).with_name("startup_benchmark_claim.py") +SPEC = importlib.util.spec_from_file_location("startup_benchmark_claim", SCRIPT) +if SPEC is None or SPEC.loader is None: + raise RuntimeError(f"cannot load {SCRIPT}") +CLAIM = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(CLAIM) + + +class SkippedClaimTests(unittest.TestCase): + def test_skipped_report_has_explicit_unverified_status_and_reason(self) -> None: + report = CLAIM.build_skipped_report( + platform_label="windows-azure", + backend="windows-restricted-token-job", + trusted_sha="a" * 40, + baseline_sha="a" * 40, + candidate_sha="b" * 40, + supervisor_sha256="c" * 64, + preflight_sha256="d" * 64, + reason="required native Windows observations were unavailable", + ) + + self.assertEqual(report["status"], "skipped") + self.assertEqual(report["infrastructure"]["sandbox_claim_status"], "unverified") + self.assertIn("unavailable", report["skip_reason"]) + + def test_verified_status_or_empty_reason_cannot_be_encoded_as_a_skip(self) -> None: + with self.assertRaises(ValueError): + CLAIM.build_skipped_report( + platform_label="windows-azure", + backend="windows-restricted-token-job", + trusted_sha="a" * 40, + baseline_sha="a" * 40, + candidate_sha="b" * 40, + supervisor_sha256="c" * 64, + preflight_sha256="d" * 64, + reason="", + ) + + +if __name__ == "__main__": + unittest.main() From 154a7085e6dba9a7ae3fab66d29ae26d98dc099e Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:59:17 -0700 Subject: [PATCH 06/23] fix: publish explicit skipped Windows startup claims --- .../workflows/cmux-tui-startup-benchmark.yml | 55 ++++- .github/workflows/cmux-tui.yml | 32 ++- .../examples/startup_benchmark_preflight.rs | 5 +- cmux-tui/scripts/startup_benchmark_claim.py | 187 ++++++++++++++++ .../scripts/test_startup_benchmark_claim.py | 87 ++++++++ cmux-tui/scripts/verify-startup-benchmark.py | 201 +++++++++++++++++- 6 files changed, 552 insertions(+), 15 deletions(-) create mode 100644 cmux-tui/scripts/startup_benchmark_claim.py diff --git a/.github/workflows/cmux-tui-startup-benchmark.yml b/.github/workflows/cmux-tui-startup-benchmark.yml index aeaa71f43996..2e831b337588 100644 --- a/.github/workflows/cmux-tui-startup-benchmark.yml +++ b/.github/workflows/cmux-tui-startup-benchmark.yml @@ -1267,6 +1267,8 @@ jobs: fi "$preflight" "${args[@]}" } + claim_status="verified" + claim_reason="" if [[ "$RUNNER_OS" == "Windows" ]]; then appcontainer_evidence="$ARTIFACT_DIR/windows-appcontainer-feasibility.json" "$preflight" \ @@ -1275,8 +1277,21 @@ jobs: --output "$appcontainer_evidence" "$PYTHON_CMD" "$TRUSTED_SOURCE/cmux-tui/scripts/verify-startup-benchmark.py" \ --appcontainer-feasibility "$appcontainer_evidence" - fi - if [[ "$RUNNER_OS" == "Linux" ]]; then + if run_preflight "$evidence"; then + : + else + preflight_status=$? + if [[ "$preflight_status" -ne 78 ]]; then + echo "trusted Windows preflight failed with exit $preflight_status" >&2 + exit "$preflight_status" + fi + claim_status="unverified" + claim_reason="trusted Windows preflight could not observe all required native security signals" + { + printf '%s\n' "$claim_reason" + } >> "$ARTIFACT_DIR/windows-preflight-unverified.txt" + fi + elif [[ "$RUNNER_OS" == "Linux" ]]; then unprivileged_evidence="$ARTIFACT_DIR/sandbox-preflight-unprivileged.json" if run_preflight "$unprivileged_evidence"; then mv "$unprivileged_evidence" "$evidence" @@ -1320,10 +1335,39 @@ jobs: bootstrap_sha256="$("$PYTHON_CMD" -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest())' "$WINDOWS_BOOTSTRAP_BINARY")" test "$bootstrap_sha256" = "$WINDOWS_BOOTSTRAP_SHA256" fi - printf 'evidence=%s\n' "$evidence" >> "$GITHUB_OUTPUT" - printf 'evidence_sha256=%s\n' "$preflight_sha256" >> "$GITHUB_OUTPUT" + { + printf 'evidence=%s\n' "$evidence" + printf 'evidence_sha256=%s\n' "$preflight_sha256" + printf 'claim_status=%s\n' "$claim_status" + if [[ -n "$claim_reason" ]]; then + printf 'claim_reason=%s\n' "$claim_reason" + fi + } >> "$GITHUB_OUTPUT" + + - name: Write skipped Windows benchmark claim + if: runner.os == 'Windows' && steps.sandbox-preflight.outputs.claim_status == 'unverified' + shell: bash + env: + PYTHON_CMD: ${{ steps.trusted-python.outputs.command }} + CLAIM_REASON: ${{ steps.sandbox-preflight.outputs.claim_reason }} + PREFLIGHT_SHA256: ${{ steps.sandbox-preflight.outputs.evidence_sha256 }} + SUPERVISOR_SHA256: ${{ steps.trusted-build.outputs.supervisor_sha256 }} + run: | + set -euo pipefail + "$PYTHON_CMD" "$TRUSTED_SOURCE/cmux-tui/scripts/startup_benchmark_claim.py" \ + --output-dir "$ARTIFACT_DIR" \ + --fixture-parent-name "$(basename "$FIXTURE_PARENT")" \ + --platform-label "$PLATFORM_LABEL" \ + --backend "$SANDBOX_BACKEND" \ + --trusted-sha "$TRUSTED_SHA" \ + --baseline-sha "$BASELINE_SHA" \ + --candidate-sha "$CANDIDATE_SHA" \ + --supervisor-sha256 "$SUPERVISOR_SHA256" \ + --preflight-sha256 "$PREFLIGHT_SHA256" \ + --reason "$CLAIM_REASON" - name: Package profile attribution binaries + if: steps.sandbox-preflight.outputs.claim_status != 'unverified' shell: bash env: PYTHON_CMD: ${{ steps.trusted-python.outputs.command }} @@ -1560,6 +1604,7 @@ jobs: ) - name: Run paired startup benchmark + if: steps.sandbox-preflight.outputs.claim_status != 'unverified' shell: bash env: BASELINE_BINARY_SHA256: ${{ steps.trusted-build.outputs.binary_sha256 }} @@ -1898,7 +1943,7 @@ jobs: exit 0 - name: Capture Windows startup profiles - if: ${{ always() && runner.os == 'Windows' }} + if: ${{ always() && runner.os == 'Windows' && steps.sandbox-preflight.outcome == 'success' && steps.sandbox-preflight.outputs.claim_status != 'unverified' }} continue-on-error: true shell: pwsh env: diff --git a/.github/workflows/cmux-tui.yml b/.github/workflows/cmux-tui.yml index 8fbfc5b4e557..343a37eafab5 100644 --- a/.github/workflows/cmux-tui.yml +++ b/.github/workflows/cmux-tui.yml @@ -907,13 +907,28 @@ jobs: --fixture-parent "$CMUX_BENCH_TEST_FIXTURE_PARENT" \ --output "$appcontainer_evidence" test -s "$appcontainer_evidence" - "$preflight" \ - --supervisor "$supervisor" \ - --windows-bootstrap-binary "$CMUX_BENCH_TEST_WINDOWS_BOOTSTRAP" \ - --windows-bootstrap-sha256 "$CMUX_BENCH_TEST_WINDOWS_BOOTSTRAP_SHA256" \ - --fixture-parent "$CMUX_BENCH_TEST_FIXTURE_PARENT" \ - --output "$evidence" \ - --backend windows-restricted-token-job + if "$preflight" \ + --supervisor "$supervisor" \ + --windows-bootstrap-binary "$CMUX_BENCH_TEST_WINDOWS_BOOTSTRAP" \ + --windows-bootstrap-sha256 "$CMUX_BENCH_TEST_WINDOWS_BOOTSTRAP_SHA256" \ + --fixture-parent "$CMUX_BENCH_TEST_FIXTURE_PARENT" \ + --output "$evidence" \ + --backend windows-restricted-token-job; then + printf 'claim_status=verified\n' >> "$GITHUB_OUTPUT" + else + preflight_status=$? + if [[ "$preflight_status" -ne 78 ]]; then + echo "Windows startup containment preflight failed with exit $preflight_status" >&2 + exit "$preflight_status" + fi + printf 'claim_status=unverified\n' >> "$GITHUB_OUTPUT" + printf '%s\n' 'trusted Windows preflight could not observe all required native security signals' \ + > "$RUNNER_TEMP/startup-containment/windows-preflight-unverified.txt" + printf '%s\n' '{"schema_version":1,"status":"skipped","claim":"windows-startup-containment","reason":"trusted Windows preflight could not observe all required native security signals"}' \ + > "$RUNNER_TEMP/startup-containment/windows-preflight-claim.json" + printf '%s\n' 'Windows startup containment claim skipped: required native security signals were unavailable.' \ + >> "$GITHUB_STEP_SUMMARY" + fi test -s "$evidence" printf 'CMUX_BENCH_TEST_SUPERVISOR=%s\n' "$supervisor" >> "$GITHUB_ENV" @@ -938,10 +953,13 @@ jobs: path: | ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility.json ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility-failure.json + ${{ runner.temp }}/startup-containment/windows-preflight-unverified.txt + ${{ runner.temp }}/startup-containment/windows-preflight-claim.json if-no-files-found: warn retention-days: 7 - name: Run release startup harness gate + if: steps.windows-startup-containment-preflight.outputs.claim_status != 'unverified' working-directory: cmux-tui shell: bash env: diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index c6ca3c18c773..4588c007bc41 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -1503,7 +1503,10 @@ fn run_controller(values: &[String]) -> Result<()> { || evidence.timing_records != 1 { #[cfg(windows)] - if windows_claim_unverified_only(&evidence) { + if status.success() + && evidence.timing_records == 1 + && windows_claim_unverified_only(&evidence) + { return Err(WindowsClaimUnavailable.into()); } bail!( diff --git a/cmux-tui/scripts/startup_benchmark_claim.py b/cmux-tui/scripts/startup_benchmark_claim.py new file mode 100644 index 000000000000..635d3a561178 --- /dev/null +++ b/cmux-tui/scripts/startup_benchmark_claim.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Create a closed report when the Windows startup claim is unverified.""" + +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path + + +SKIPPED_SCHEMA_VERSION = 4 +PAIR_ORDER = "serial alternating baseline-first and candidate-first pairs" +WINDOWS_PLATFORM = "windows-azure" +WINDOWS_BACKEND = "windows-restricted-token-job" +SANDBOX_POLICY = "fixture-root-only-write" +SANDBOX_HANDSHAKE = "nonce-bound-ready-arm-with-pre-exec-t0" +SANDBOX_CLEANUP = "descendant-channel-eof-after-process-tree-empty" +PROFILE_PURPOSE = "offline attribution of native cmux-tui startup profiles" + + +def _require_sha(value: str, label: str, length: int) -> str: + if len(value) != length or any(character not in "0123456789abcdef" for character in value): + raise ValueError(f"{label} must be a lowercase {length}-character hexadecimal value") + return value + + +def _require_reason(reason: str) -> str: + if not isinstance(reason, str) or not reason.strip(): + raise ValueError("skip reason must be non-empty") + if len(reason.encode("utf-8")) > 4096: + raise ValueError("skip reason is too long") + return reason.strip() + + +def build_skipped_report( + *, + platform_label: str, + backend: str, + trusted_sha: str, + baseline_sha: str, + candidate_sha: str, + supervisor_sha256: str, + preflight_sha256: str, + reason: str, +) -> dict: + if platform_label != WINDOWS_PLATFORM: + raise ValueError("only the Windows benchmark can be skipped") + if backend != WINDOWS_BACKEND: + raise ValueError("Windows skip report has the wrong sandbox backend") + trusted_sha = _require_sha(trusted_sha, "trusted SHA", 40) + baseline_sha = _require_sha(baseline_sha, "baseline SHA", 40) + candidate_sha = _require_sha(candidate_sha, "candidate SHA", 40) + if trusted_sha != baseline_sha: + raise ValueError("trusted and baseline SHAs must match") + if candidate_sha == baseline_sha: + raise ValueError("baseline and candidate SHAs must differ") + supervisor_sha256 = _require_sha(supervisor_sha256, "supervisor SHA-256", 64) + preflight_sha256 = _require_sha(preflight_sha256, "preflight SHA-256", 64) + reason = _require_reason(reason) + infrastructure = { + "trusted_sha": trusted_sha, + "sandbox_backend": backend, + "sandbox_policy": SANDBOX_POLICY, + "sandbox_handshake": SANDBOX_HANDSHAKE, + "sandbox_cleanup": SANDBOX_CLEANUP, + "sandbox_claim_status": "unverified", + "sandbox_claim_reason": reason, + "expected_supervisor_sha256": supervisor_sha256, + "supervisor_sha256": supervisor_sha256, + "expected_preflight_sha256": preflight_sha256, + "preflight_sha256": preflight_sha256, + } + return { + "schema_version": SKIPPED_SCHEMA_VERSION, + "status": "skipped", + "skip_reason": reason, + "platform_label": platform_label, + "warmups": 10, + "paired_samples": 50, + "order": PAIR_ORDER, + "trusted_sha": trusted_sha, + "baseline_sha": baseline_sha, + "candidate_sha": candidate_sha, + "infrastructure": infrastructure, + "scenarios": [], + } + + +def _write_json(path: Path, value: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("x", encoding="utf-8") as destination: + json.dump(value, destination, indent=2, sort_keys=True) + destination.write("\n") + destination.flush() + os.fsync(destination.fileno()) + + +def _write_text(path: Path, value: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("x", encoding="utf-8") as destination: + destination.write(value) + destination.flush() + os.fsync(destination.fileno()) + + +def write_skipped_artifacts( + *, + output_dir: Path, + fixture_parent_name: str, + report: dict, +) -> None: + reason = report["skip_reason"] + output_dir.mkdir(parents=True, exist_ok=True) + _write_json(output_dir / "startup-benchmark.json", report) + _write_text( + output_dir / "startup-benchmark.md", + "# cmux-tui startup benchmark\n\n" + f"Platform: {report['platform_label']} \n" + "Status: skipped (unverified) \n" + f"Reason: {reason}\n\n" + "No Windows startup timing claim was published because the trusted preflight " + "did not observe every required native security signal.\n" + ) + _write_json( + output_dir / "startup-lifecycle.json", + { + "schema_version": 1, + "status": "skipped", + "reason": reason, + "fixture_parent_name": fixture_parent_name, + "report_written_before_reclamation": True, + "deferred_roots": [], + "pairs": [], + "fixtures": [], + "profiles": [], + }, + ) + _write_json( + output_dir / "profile-attribution.json", + { + "schema_version": 1, + "purpose": PROFILE_PURPOSE, + "status": "skipped", + "reason": reason, + "targets": {}, + }, + ) + + +def _parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--fixture-parent-name", required=True) + parser.add_argument("--platform-label", required=True) + parser.add_argument("--backend", required=True) + parser.add_argument("--trusted-sha", required=True) + parser.add_argument("--baseline-sha", required=True) + parser.add_argument("--candidate-sha", required=True) + parser.add_argument("--supervisor-sha256", required=True) + parser.add_argument("--preflight-sha256", required=True) + parser.add_argument("--reason", required=True) + return parser + + +def main() -> int: + args = _parser().parse_args() + report = build_skipped_report( + platform_label=args.platform_label, + backend=args.backend, + trusted_sha=args.trusted_sha, + baseline_sha=args.baseline_sha, + candidate_sha=args.candidate_sha, + supervisor_sha256=args.supervisor_sha256, + preflight_sha256=args.preflight_sha256, + reason=args.reason, + ) + write_skipped_artifacts( + output_dir=args.output_dir, + fixture_parent_name=args.fixture_parent_name, + report=report, + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index cfdbabdb945d..9a069a24e9a1 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -4,11 +4,18 @@ from __future__ import annotations import importlib.util +import hashlib +import json +import os +import subprocess +import sys +import tempfile import unittest from pathlib import Path SCRIPT = Path(__file__).with_name("startup_benchmark_claim.py") +VERIFIER = Path(__file__).with_name("verify-startup-benchmark.py") SPEC = importlib.util.spec_from_file_location("startup_benchmark_claim", SCRIPT) if SPEC is None or SPEC.loader is None: raise RuntimeError(f"cannot load {SCRIPT}") @@ -17,6 +24,70 @@ class SkippedClaimTests(unittest.TestCase): + @staticmethod + def _write_preflight(output_dir: Path, *, unsupported_value=None) -> str: + preflight = { + "schema_version": 8, + "backend": "windows-restricted-token-job", + "policy": "fixture-root-only-write", + "handshake": "nonce-bound-ready-arm-with-pre-exec-t0", + "cleanup": "descendant-channel-eof-after-process-tree-empty", + "windows_grandchild_in_job": True, + "windows_active_process_zero": unsupported_value, + "windows_caller_se_impersonate_enabled": None, + "windows_standard_handles_valid": None, + "windows_explicit_handle_list": None, + } + path = output_dir / "sandbox-preflight.json" + path.write_text(json.dumps(preflight, sort_keys=True) + "\n", encoding="utf-8") + return hashlib.sha256(path.read_bytes()).hexdigest() + + def _run_verifier( + self, *, runner_os: str, unsupported_value=None + ) -> subprocess.CompletedProcess[str]: + with tempfile.TemporaryDirectory() as temporary: + output_dir = Path(temporary) + preflight_sha256 = self._write_preflight( + output_dir, unsupported_value=unsupported_value + ) + trusted_sha = "a" * 40 + candidate_sha = "b" * 40 + supervisor_sha256 = "c" * 64 + report = CLAIM.build_skipped_report( + platform_label="windows-azure", + backend="windows-restricted-token-job", + trusted_sha=trusted_sha, + baseline_sha=trusted_sha, + candidate_sha=candidate_sha, + supervisor_sha256=supervisor_sha256, + preflight_sha256=preflight_sha256, + reason="required native Windows observations were unavailable", + ) + CLAIM.write_skipped_artifacts( + output_dir=output_dir, + fixture_parent_name="cbp-test", + report=report, + ) + environment = os.environ.copy() + environment.update( + { + "RUNNER_OS": runner_os, + "PLATFORM_LABEL": "windows-azure", + "TRUSTED_SHA": trusted_sha, + "BASELINE_SHA": trusted_sha, + "CANDIDATE_SHA": candidate_sha, + "SUPERVISOR_BINARY_SHA256": supervisor_sha256, + "SANDBOX_PREFLIGHT_SHA256": preflight_sha256, + } + ) + return subprocess.run( + [sys.executable, str(VERIFIER), str(output_dir / "startup-benchmark.json")], + env=environment, + capture_output=True, + text=True, + check=False, + ) + def test_skipped_report_has_explicit_unverified_status_and_reason(self) -> None: report = CLAIM.build_skipped_report( platform_label="windows-azure", @@ -46,6 +117,22 @@ def test_verified_status_or_empty_reason_cannot_be_encoded_as_a_skip(self) -> No reason="", ) + def test_unavailable_only_windows_claim_is_accepted_as_skipped(self) -> None: + result = self._run_verifier(runner_os="Windows") + + self.assertEqual(result.returncode, 0, result.stderr) + + def test_false_windows_observation_is_rejected(self) -> None: + result = self._run_verifier(runner_os="Windows", unsupported_value=False) + + self.assertNotEqual(result.returncode, 0) + + def test_skipped_claim_is_rejected_on_linux_and_macos(self) -> None: + for runner_os in ("Linux", "macOS"): + with self.subTest(runner_os=runner_os): + result = self._run_verifier(runner_os=runner_os) + self.assertNotEqual(result.returncode, 0) + if __name__ == "__main__": unittest.main() diff --git a/cmux-tui/scripts/verify-startup-benchmark.py b/cmux-tui/scripts/verify-startup-benchmark.py index e366132d21c0..b9d65611a8f4 100644 --- a/cmux-tui/scripts/verify-startup-benchmark.py +++ b/cmux-tui/scripts/verify-startup-benchmark.py @@ -9,6 +9,16 @@ import sys FULL_SHA_PATTERN = re.compile(r"[0-9a-f]{40}") +FULL_SHA256_PATTERN = re.compile(r"[0-9a-f]{64}") +SKIPPED_REPORT_SCHEMA = 4 +SKIPPED_PLATFORM = "windows-azure" +SKIPPED_BACKEND = "windows-restricted-token-job" +SKIPPED_PREFLIGHT_FIELDS = ( + "windows_active_process_zero", + "windows_caller_se_impersonate_enabled", + "windows_standard_handles_valid", + "windows_explicit_handle_list", +) def require_full_sha(value, label): @@ -133,6 +143,174 @@ def validate_raw_distributions(artifact_root): ) +def validate_skipped_report(document, artifact_root): + require_exact_object( + document, + { + "schema_version", + "status", + "skip_reason", + "platform_label", + "warmups", + "paired_samples", + "order", + "trusted_sha", + "baseline_sha", + "candidate_sha", + "infrastructure", + "scenarios", + }, + "skipped startup benchmark report", + ) + reason = document["skip_reason"] + if ( + document["schema_version"] != SKIPPED_REPORT_SCHEMA + or document["status"] != "skipped" + or document["platform_label"] != SKIPPED_PLATFORM + or document["warmups"] != 10 + or document["paired_samples"] != 50 + or document["order"] != "serial alternating baseline-first and candidate-first pairs" + or not isinstance(reason, str) + or not reason.strip() + or len(reason.encode("utf-8")) > 4096 + or document["scenarios"] != [] + ): + raise SystemExit("skipped startup benchmark report has invalid status or identity") + for key in ("trusted_sha", "baseline_sha", "candidate_sha"): + require_full_sha(document[key], f"skipped report {key}") + if document["trusted_sha"] != document["baseline_sha"]: + raise SystemExit("skipped report trusted and baseline SHAs differ") + if document["candidate_sha"] == document["baseline_sha"]: + raise SystemExit("skipped report baseline and candidate SHAs must differ") + infrastructure = document["infrastructure"] + require_exact_object( + infrastructure, + { + "trusted_sha", + "sandbox_backend", + "sandbox_policy", + "sandbox_handshake", + "sandbox_cleanup", + "sandbox_claim_status", + "sandbox_claim_reason", + "expected_supervisor_sha256", + "supervisor_sha256", + "expected_preflight_sha256", + "preflight_sha256", + }, + "skipped startup infrastructure", + ) + if ( + infrastructure["trusted_sha"] != document["trusted_sha"] + or infrastructure["sandbox_backend"] != SKIPPED_BACKEND + or infrastructure["sandbox_policy"] != "fixture-root-only-write" + or infrastructure["sandbox_handshake"] != "nonce-bound-ready-arm-with-pre-exec-t0" + or infrastructure["sandbox_cleanup"] != "descendant-channel-eof-after-process-tree-empty" + or infrastructure["sandbox_claim_status"] != "unverified" + or infrastructure["sandbox_claim_reason"] != reason + or not isinstance(infrastructure["expected_supervisor_sha256"], str) + or FULL_SHA256_PATTERN.fullmatch(infrastructure["expected_supervisor_sha256"]) is None + or infrastructure["supervisor_sha256"] + != infrastructure["expected_supervisor_sha256"] + or not isinstance(infrastructure["expected_preflight_sha256"], str) + or FULL_SHA256_PATTERN.fullmatch(infrastructure["expected_preflight_sha256"]) is None + or infrastructure["preflight_sha256"] != infrastructure["expected_preflight_sha256"] + ): + raise SystemExit("skipped startup infrastructure has invalid claim metadata") + + expected_platform = os.environ.get("PLATFORM_LABEL") + if expected_platform is not None and expected_platform != document["platform_label"]: + raise SystemExit("skipped report has the wrong platform label") + for environment_name, field in ( + ("TRUSTED_SHA", "trusted_sha"), + ("BASELINE_SHA", "baseline_sha"), + ("CANDIDATE_SHA", "candidate_sha"), + ): + expected = os.environ.get(environment_name) + if expected is not None and expected != document[field]: + raise SystemExit(f"skipped report has the wrong {field}") + for environment_name, field in ( + ("SUPERVISOR_BINARY_SHA256", "expected_supervisor_sha256"), + ("SANDBOX_PREFLIGHT_SHA256", "expected_preflight_sha256"), + ): + expected = os.environ.get(environment_name) + if expected is not None and expected != infrastructure[field]: + raise SystemExit(f"skipped report has the wrong {field}") + if os.environ.get("RUNNER_OS") not in (None, "Windows"): + raise SystemExit("skipped startup report is only valid on Windows") + + preflight_path = artifact_root / "sandbox-preflight.json" + preflight = load_json_object(preflight_path, "sandbox preflight evidence") + if ( + preflight.get("schema_version") != 8 + or preflight.get("backend") != SKIPPED_BACKEND + or "windows_grandchild_in_job" not in preflight + or ( + preflight.get("windows_grandchild_in_job") is not None + and preflight.get("windows_grandchild_in_job") is not True + ) + or any(field not in preflight for field in SKIPPED_PREFLIGHT_FIELDS) + or any(preflight[field] is not None for field in SKIPPED_PREFLIGHT_FIELDS) + or preflight.get("policy") != infrastructure["sandbox_policy"] + or preflight.get("handshake") != infrastructure["sandbox_handshake"] + or preflight.get("cleanup") != infrastructure["sandbox_cleanup"] + or file_sha256(preflight_path) != infrastructure["preflight_sha256"] + ): + raise SystemExit("skipped report does not prove unavailable Windows observations") + + markdown_path = artifact_root / "startup-benchmark.md" + markdown = markdown_path.read_text(encoding="utf-8") + if "Status: skipped (unverified)" not in markdown or reason not in markdown: + raise SystemExit("skipped startup markdown does not state the claim reason") + + lifecycle = load_json_object(artifact_root / "startup-lifecycle.json", "startup lifecycle") + require_exact_object( + lifecycle, + { + "schema_version", + "status", + "reason", + "fixture_parent_name", + "report_written_before_reclamation", + "deferred_roots", + "pairs", + "fixtures", + "profiles", + }, + "skipped startup lifecycle", + ) + if ( + lifecycle["schema_version"] != 1 + or lifecycle["status"] != "skipped" + or lifecycle["reason"] != reason + or not isinstance(lifecycle["fixture_parent_name"], str) + or not lifecycle["fixture_parent_name"] + or lifecycle["report_written_before_reclamation"] is not True + or lifecycle["deferred_roots"] != [] + or lifecycle["pairs"] != [] + or lifecycle["fixtures"] != [] + or lifecycle["profiles"] != [] + ): + raise SystemExit("skipped startup lifecycle is invalid") + + attribution = load_json_object( + artifact_root / "profile-attribution.json", "profile attribution" + ) + require_exact_object( + attribution, + {"schema_version", "purpose", "status", "reason", "targets"}, + "skipped profile attribution", + ) + if ( + attribution["schema_version"] != 1 + or attribution["purpose"] != "offline attribution of native cmux-tui startup profiles" + or attribution["status"] != "skipped" + or attribution["reason"] != reason + or attribution["targets"] != {} + ): + raise SystemExit("skipped profile attribution is invalid") + + def validate_harness_test_evidence(artifact_root): evidence = load_json_object( artifact_root / "harness-tests.json", "harness test evidence" @@ -333,9 +511,15 @@ def close_artifact(artifact_root): "runner-os.txt", ): require_nonempty_artifact(artifact_root / required, required) - validate_raw_distributions(artifact_root) + report = load_json_object(artifact_root / "startup-benchmark.json", "startup benchmark report") + skipped = report.get("status") == "skipped" + if skipped: + validate_skipped_report(report, artifact_root) + else: + validate_raw_distributions(artifact_root) validate_harness_test_evidence(artifact_root) - validate_required_native_profiles(artifact_root) + if not skipped: + validate_required_native_profiles(artifact_root) records = collect_artifact_records(artifact_root) manifest = { "schema_version": 1, @@ -687,6 +871,9 @@ def validate_appcontainer_feasibility_failure(path): path = pathlib.Path(sys.argv[1]) document = json.loads(path.read_text(encoding="utf-8")) +if isinstance(document, dict) and document.get("status") == "skipped": + validate_skipped_report(document, path.parent) + raise SystemExit(0) expected_warmups = int(os.environ["WARMUPS"]) expected_samples = int(os.environ["SAMPLES"]) if expected_warmups != 10 or expected_samples != 50: @@ -831,6 +1018,11 @@ def get(dotted): elif appcontainer_feasibility_path.exists(): raise SystemExit("non-Windows evidence contains an AppContainer feasibility record") windows_preflight_fields = ( + "windows_grandchild_in_job", + "windows_active_process_zero", + "windows_caller_se_impersonate_enabled", + "windows_standard_handles_valid", + "windows_explicit_handle_list", "windows_bootstrap_sha256", "windows_bootstrap_config_nonce", "windows_bootstrap_config_consumed", @@ -948,6 +1140,11 @@ def get(dotted): or isinstance(ready_elapsed_ms, bool) or not 0 <= ready_elapsed_ms <= 30_000 or preflight["windows_bootstrap_exact_job"] is not True + or preflight["windows_grandchild_in_job"] is not True + or preflight["windows_active_process_zero"] is not True + or preflight["windows_caller_se_impersonate_enabled"] is not True + or preflight["windows_standard_handles_valid"] is not True + or preflight["windows_explicit_handle_list"] is not True or preflight["windows_bootstrap_trusted_path_write_denied"] is not True or preflight["windows_bootstrap_self_write_denied"] is not True or not isinstance(restricting_sid, str) From f549ddfdfeb581ca50dac594af2f0ddecd4b82c9 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:01:55 -0700 Subject: [PATCH 07/23] test: reject core failures as skipped Windows claims --- .../examples/startup_benchmark_preflight.rs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index 4588c007bc41..05a68a6cbaa5 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -61,6 +61,7 @@ impl Display for WindowsClaimUnavailable { impl Error for WindowsClaimUnavailable {} #[derive(Clone, Serialize)] +#[cfg_attr(test, derive(Default))] struct PreflightEvidence { schema_version: u32, backend: String, @@ -2276,4 +2277,60 @@ mod tests { WindowsPreflightObservationState::Failed, ); } + + #[cfg(windows)] + fn complete_windows_evidence() -> PreflightEvidence { + let mut evidence = PreflightEvidence::default(); + evidence.windows_low_integrity = Some(true); + evidence.windows_no_enabled_privileges = Some(true); + evidence.windows_registry_write_denied = Some(true); + evidence.windows_breakaway_denied = Some(true); + evidence.windows_bootstrap_sha256 = Some("a".repeat(64)); + evidence.windows_bootstrap_config_nonce = Some("b".repeat(64)); + evidence.windows_bootstrap_config_consumed = Some(true); + evidence.windows_bootstrap_resume_previous_count = Some(1); + evidence.windows_bootstrap_ready_elapsed_ms = Some(1); + evidence.windows_bootstrap_exact_job = Some(true); + evidence.windows_bootstrap_trusted_path_write_denied = Some(true); + evidence.windows_bootstrap_self_write_denied = Some(true); + evidence.windows_restricting_sid = Some("S-1-5-21".into()); + let authentication_id = Some("0123456789abcdef".into()); + evidence.windows_broker_authentication_id = authentication_id.clone(); + evidence.windows_restricted_authentication_id = authentication_id.clone(); + evidence.windows_product_authentication_id = authentication_id; + evidence.windows_restricted_authentication_matches_broker = Some(true); + evidence.windows_product_authentication_matches_broker = Some(true); + evidence.windows_se_increase_quota_present = Some(true); + evidence.windows_se_increase_quota_enabled = Some(true); + evidence.windows_create_process_as_user_succeeded = Some(true); + evidence.windows_restricted_token_write_restricted = Some(true); + evidence.windows_restricted_token_restricting_sid_match = Some(true); + evidence.windows_restricted_token_low_integrity = Some(true); + evidence.windows_restricted_token_no_enabled_privileges = Some(true); + evidence.windows_product_write_restricted = Some(true); + evidence.windows_product_restricting_sid_match = Some(true); + evidence.windows_product_low_integrity = Some(true); + evidence.windows_product_no_enabled_privileges = Some(true); + evidence.windows_product_exact_job = Some(true); + evidence.windows_product_resume_previous_count = Some(1); + evidence.windows_product_process_id = Some(1); + evidence.windows_product_primary_thread_id = Some(1); + evidence.windows_private_desktop = Some(format!("cmuxb-{}\\desk-", "a".repeat(48))); + evidence.windows_private_window_station_created = Some(true); + evidence.windows_private_desktop_created = Some(true); + evidence.windows_private_desktop_broker_assigned = Some(true); + evidence.windows_private_desktop_product_assigned = Some(true); + evidence.windows_private_desktop_closed_after_job_empty = Some(true); + evidence.timing_records = 1; + evidence + } + + #[cfg(windows)] + #[test] + fn core_failure_with_missing_windows_observations_is_not_skippable() { + let mut evidence = complete_windows_evidence(); + evidence.inside_write = false; + + assert!(!windows_claim_unverified_only(&evidence)); + } } From 50bc6840faedcaa5a5ef0055b101d9f88e51ab83 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:09:42 -0700 Subject: [PATCH 08/23] test: define shared startup preflight contract --- .../examples/startup_benchmark_preflight.rs | 27 ++ .../test_startup_benchmark_contract.py | 258 ++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 cmux-tui/scripts/test_startup_benchmark_contract.py diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index 05a68a6cbaa5..fa342d9890b6 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -2278,6 +2278,33 @@ mod tests { ); } + #[test] + fn preflight_cleanup_removes_root_and_sentinels() { + let unique = format!( + "cmux-preflight-cleanup-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let parent = std::env::temp_dir().join(unique); + let root = parent.join("preflight-root"); + let adjacent = parent.join("protected-adjacent"); + let child_adjacent = parent.join("protected-child-adjacent"); + fs::create_dir_all(&root).unwrap(); + fs::write(&root.join("evidence"), b"evidence").unwrap(); + fs::write(&adjacent, b"protected").unwrap(); + fs::write(&child_adjacent, b"protected").unwrap(); + + cleanup_preflight_state(&root, &adjacent, &child_adjacent).unwrap(); + + assert!(!root.exists()); + assert!(!adjacent.exists()); + assert!(!child_adjacent.exists()); + fs::remove_dir(&parent).unwrap(); + } + #[cfg(windows)] fn complete_windows_evidence() -> PreflightEvidence { let mut evidence = PreflightEvidence::default(); diff --git a/cmux-tui/scripts/test_startup_benchmark_contract.py b/cmux-tui/scripts/test_startup_benchmark_contract.py new file mode 100644 index 000000000000..19eb92f5d1f4 --- /dev/null +++ b/cmux-tui/scripts/test_startup_benchmark_contract.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +"""Behavior tests for the shared startup preflight evidence contract.""" + +from __future__ import annotations + +import importlib.util +import unittest +from pathlib import Path + + +SCRIPT = Path(__file__).with_name("startup_benchmark_contract.py") +SPEC = importlib.util.spec_from_file_location("startup_benchmark_contract", SCRIPT) +if SPEC is None or SPEC.loader is None: + raise RuntimeError(f"cannot load {SCRIPT}") +CONTRACT = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(CONTRACT) + + +EXPECTED_FIELDS = { + "schema_version", + "backend", + "policy", + "handshake", + "cleanup", + "inside_write", + "adjacent_write_denied", + "descendant_adjacent_write_denied", + "descendant_contained", + "network_denied", + "inbound_network_denied", + "linux_no_new_privs", + "linux_effective_capabilities_zero", + "linux_sudo_bwrap", + "linux_bwrap_version", + "linux_unprivileged_userns_clone", + "linux_max_user_namespaces", + "windows_low_integrity", + "windows_no_enabled_privileges", + "windows_registry_write_denied", + "windows_grandchild_in_job", + "windows_breakaway_denied", + "windows_active_process_zero", + "windows_caller_se_impersonate_enabled", + "windows_standard_handles_valid", + "windows_explicit_handle_list", + "windows_bootstrap_sha256", + "windows_bootstrap_config_nonce", + "windows_bootstrap_config_consumed", + "windows_bootstrap_resume_previous_count", + "windows_bootstrap_ready_elapsed_ms", + "windows_bootstrap_exact_job", + "windows_bootstrap_trusted_path_write_denied", + "windows_bootstrap_self_write_denied", + "windows_restricting_sid", + "windows_broker_authentication_id", + "windows_restricted_authentication_id", + "windows_product_authentication_id", + "windows_restricted_authentication_matches_broker", + "windows_product_authentication_matches_broker", + "windows_se_increase_quota_present", + "windows_se_increase_quota_enabled", + "windows_create_process_as_user_succeeded", + "windows_restricted_token_write_restricted", + "windows_restricted_token_restricting_sid_match", + "windows_restricted_token_low_integrity", + "windows_restricted_token_no_enabled_privileges", + "windows_product_write_restricted", + "windows_product_restricting_sid_match", + "windows_product_low_integrity", + "windows_product_no_enabled_privileges", + "windows_product_exact_job", + "windows_product_resume_previous_count", + "windows_product_process_id", + "windows_product_primary_thread_id", + "windows_private_desktop", + "windows_private_window_station_created", + "windows_private_desktop_created", + "windows_private_desktop_broker_assigned", + "windows_private_desktop_product_assigned", + "windows_private_desktop_closed_after_job_empty", + "supervisor_ready", + "timing_records", + "supervisor_sha256", +} + +CORE_FIELDS = ( + "inside_write", + "adjacent_write_denied", + "descendant_adjacent_write_denied", + "descendant_contained", + "network_denied", + "inbound_network_denied", + "supervisor_ready", + "windows_low_integrity", + "windows_no_enabled_privileges", + "windows_registry_write_denied", + "windows_breakaway_denied", +) +UNAVAILABLE_FIELDS = ( + "windows_active_process_zero", + "windows_caller_se_impersonate_enabled", + "windows_standard_handles_valid", + "windows_explicit_handle_list", +) + + +def windows_evidence() -> dict: + nonce = "b" * 64 + authentication_id = "0123456789abcdef" + evidence = {field: None for field in EXPECTED_FIELDS} + evidence.update( + { + "schema_version": 8, + "backend": "windows-restricted-token-job", + "policy": "fixture-root-only-write", + "handshake": "nonce-bound-ready-arm-with-pre-exec-t0", + "cleanup": "descendant-channel-eof-after-process-tree-empty", + "linux_unprivileged_userns_clone": None, + "linux_max_user_namespaces": None, + "windows_low_integrity": True, + "windows_no_enabled_privileges": True, + "windows_registry_write_denied": True, + "windows_grandchild_in_job": True, + "windows_breakaway_denied": True, + "windows_bootstrap_sha256": "a" * 64, + "windows_bootstrap_config_nonce": nonce, + "windows_bootstrap_config_consumed": True, + "windows_bootstrap_resume_previous_count": 1, + "windows_bootstrap_ready_elapsed_ms": 1, + "windows_bootstrap_exact_job": True, + "windows_bootstrap_trusted_path_write_denied": True, + "windows_bootstrap_self_write_denied": True, + "windows_restricting_sid": "S-1-5-21", + "windows_broker_authentication_id": authentication_id, + "windows_restricted_authentication_id": authentication_id, + "windows_product_authentication_id": authentication_id, + "windows_restricted_authentication_matches_broker": True, + "windows_product_authentication_matches_broker": True, + "windows_se_increase_quota_present": True, + "windows_se_increase_quota_enabled": True, + "windows_create_process_as_user_succeeded": True, + "windows_restricted_token_write_restricted": True, + "windows_restricted_token_restricting_sid_match": True, + "windows_restricted_token_low_integrity": True, + "windows_restricted_token_no_enabled_privileges": True, + "windows_product_write_restricted": True, + "windows_product_restricting_sid_match": True, + "windows_product_low_integrity": True, + "windows_product_no_enabled_privileges": True, + "windows_product_exact_job": True, + "windows_product_resume_previous_count": 1, + "windows_product_process_id": 1, + "windows_product_primary_thread_id": 1, + "windows_private_desktop": f"cmuxb-{nonce[:24]}\\desk-{nonce[24:48]}", + "windows_private_window_station_created": True, + "windows_private_desktop_created": True, + "windows_private_desktop_broker_assigned": True, + "windows_private_desktop_product_assigned": True, + "windows_private_desktop_closed_after_job_empty": True, + "supervisor_ready": True, + "timing_records": 1, + "supervisor_sha256": "c" * 64, + } + ) + return evidence + + +class StartupBenchmarkContractTests(unittest.TestCase): + def test_contract_field_set_matches_serialized_preflight_schema(self) -> None: + self.assertEqual(set(CONTRACT.EXPECTED_PREFLIGHT_FIELDS), EXPECTED_FIELDS) + + def test_unavailable_only_windows_fields_are_skippable_but_not_verified(self) -> None: + evidence = windows_evidence() + evidence["windows_grandchild_in_job"] = None + for field in UNAVAILABLE_FIELDS: + evidence[field] = None + + self.assertEqual( + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=True, + ), + "unverified", + ) + with self.assertRaises(ValueError): + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=False, + ) + + def test_common_or_observed_false_proofs_reject_skipped_and_normal_paths(self) -> None: + for field in (*CORE_FIELDS, "windows_low_integrity"): + with self.subTest(field=field): + evidence = windows_evidence() + evidence[field] = False + for allow_unverified_windows in (True, False): + with self.subTest(allow_unverified_windows=allow_unverified_windows): + with self.assertRaises(ValueError): + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=allow_unverified_windows, + ) + + def test_missing_common_field_rejects_both_validator_modes(self) -> None: + evidence = windows_evidence() + del evidence["inside_write"] + + for allow_unverified_windows in (True, False): + with self.subTest(allow_unverified_windows=allow_unverified_windows): + with self.assertRaises(ValueError): + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=allow_unverified_windows, + ) + + def test_linux_and_macos_contracts_remain_strict(self) -> None: + for backend in ("linux-bwrap", "macos-seatbelt"): + with self.subTest(backend=backend): + evidence = windows_evidence() + evidence["backend"] = backend + for field in ( + "windows_low_integrity", + "windows_no_enabled_privileges", + "windows_registry_write_denied", + "windows_grandchild_in_job", + "windows_breakaway_denied", + ): + evidence[field] = None + for field in UNAVAILABLE_FIELDS: + evidence[field] = None + for field in EXPECTED_FIELDS: + if field.startswith("windows_"): + evidence[field] = None + if backend == "linux-bwrap": + evidence.update( + { + "linux_no_new_privs": True, + "linux_effective_capabilities_zero": True, + "linux_sudo_bwrap": True, + "linux_bwrap_version": "0.9.0", + } + ) + self.assertEqual( + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=False, + ), + "verified", + ) + + +if __name__ == "__main__": + unittest.main() From 218ca5ba2d29abd9ddf34c007437d74ef4012b97 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:25:40 -0700 Subject: [PATCH 09/23] test: cover startup evidence contract edge cases --- .../scripts/test_startup_benchmark_claim.py | 181 ++++++++++++++++-- .../test_startup_benchmark_contract.py | 48 +++++ 2 files changed, 214 insertions(+), 15 deletions(-) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index 9a069a24e9a1..a428fa91d9fe 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -15,41 +15,165 @@ SCRIPT = Path(__file__).with_name("startup_benchmark_claim.py") +CONTRACT_SCRIPT = Path(__file__).with_name("startup_benchmark_contract.py") VERIFIER = Path(__file__).with_name("verify-startup-benchmark.py") SPEC = importlib.util.spec_from_file_location("startup_benchmark_claim", SCRIPT) if SPEC is None or SPEC.loader is None: raise RuntimeError(f"cannot load {SCRIPT}") CLAIM = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(CLAIM) +CONTRACT_SPEC = importlib.util.spec_from_file_location( + "startup_benchmark_contract", CONTRACT_SCRIPT +) +if CONTRACT_SPEC is None or CONTRACT_SPEC.loader is None: + raise RuntimeError(f"cannot load {CONTRACT_SCRIPT}") +CONTRACT = importlib.util.module_from_spec(CONTRACT_SPEC) +CONTRACT_SPEC.loader.exec_module(CONTRACT) class SkippedClaimTests(unittest.TestCase): @staticmethod - def _write_preflight(output_dir: Path, *, unsupported_value=None) -> str: - preflight = { - "schema_version": 8, - "backend": "windows-restricted-token-job", - "policy": "fixture-root-only-write", - "handshake": "nonce-bound-ready-arm-with-pre-exec-t0", - "cleanup": "descendant-channel-eof-after-process-tree-empty", - "windows_grandchild_in_job": True, - "windows_active_process_zero": unsupported_value, - "windows_caller_se_impersonate_enabled": None, - "windows_standard_handles_valid": None, - "windows_explicit_handle_list": None, - } + def _write_preflight( + output_dir: Path, + *, + unsupported_value=None, + false_field=None, + missing_field=None, + bootstrap_value=b"trusted Windows bootstrap", + ) -> str: + nonce = "b" * 64 + authentication_id = "0123456789abcdef" + bootstrap_path = output_dir / "trusted-bootstrap.exe" + bootstrap_path.write_bytes(bootstrap_value) + bootstrap_sha256 = hashlib.sha256(bootstrap_value).hexdigest() + preflight = {field: None for field in CONTRACT.EXPECTED_PREFLIGHT_FIELDS} + preflight.update( + { + "schema_version": 8, + "backend": "windows-restricted-token-job", + "policy": "fixture-root-only-write", + "handshake": "nonce-bound-ready-arm-with-pre-exec-t0", + "cleanup": "descendant-channel-eof-after-process-tree-empty", + "inside_write": True, + "adjacent_write_denied": True, + "descendant_adjacent_write_denied": True, + "descendant_contained": True, + "network_denied": True, + "inbound_network_denied": True, + "windows_low_integrity": True, + "windows_no_enabled_privileges": True, + "windows_registry_write_denied": True, + "windows_grandchild_in_job": True, + "windows_breakaway_denied": True, + "windows_active_process_zero": unsupported_value, + "windows_bootstrap_sha256": bootstrap_sha256, + "windows_bootstrap_config_nonce": nonce, + "windows_bootstrap_config_consumed": True, + "windows_bootstrap_resume_previous_count": 1, + "windows_bootstrap_ready_elapsed_ms": 1, + "windows_bootstrap_exact_job": True, + "windows_bootstrap_trusted_path_write_denied": True, + "windows_bootstrap_self_write_denied": True, + "windows_restricting_sid": "S-1-5-21", + "windows_broker_authentication_id": authentication_id, + "windows_restricted_authentication_id": authentication_id, + "windows_product_authentication_id": authentication_id, + "windows_restricted_authentication_matches_broker": True, + "windows_product_authentication_matches_broker": True, + "windows_se_increase_quota_present": True, + "windows_se_increase_quota_enabled": True, + "windows_create_process_as_user_succeeded": True, + "windows_restricted_token_write_restricted": True, + "windows_restricted_token_restricting_sid_match": True, + "windows_restricted_token_low_integrity": True, + "windows_restricted_token_no_enabled_privileges": True, + "windows_product_write_restricted": True, + "windows_product_restricting_sid_match": True, + "windows_product_low_integrity": True, + "windows_product_no_enabled_privileges": True, + "windows_product_exact_job": True, + "windows_product_resume_previous_count": 1, + "windows_product_process_id": 1, + "windows_product_primary_thread_id": 1, + "windows_private_desktop": f"cmuxb-{nonce[:24]}\\desk-{nonce[24:48]}", + "windows_private_window_station_created": True, + "windows_private_desktop_created": True, + "windows_private_desktop_broker_assigned": True, + "windows_private_desktop_product_assigned": True, + "windows_private_desktop_closed_after_job_empty": True, + "supervisor_ready": True, + "timing_records": 1, + "supervisor_sha256": "c" * 64, + } + ) + if false_field is not None: + preflight[false_field] = False + if missing_field is not None: + del preflight[missing_field] path = output_dir / "sandbox-preflight.json" path.write_text(json.dumps(preflight, sort_keys=True) + "\n", encoding="utf-8") + (output_dir / "windows-bootstrap-imports.json").write_text( + json.dumps( + { + "schema_version": 1, + "bootstrap_sha256": bootstrap_sha256, + "dependencies": ["advapi32.dll", "bcrypt.dll", "kernel32.dll"], + } + ) + + "\n", + encoding="utf-8", + ) + (output_dir / "startup-integrity-before.json").write_text( + json.dumps( + { + "files": { + "trusted_windows_bootstrap": { + "path": str(bootstrap_path.resolve()), + "sha256": bootstrap_sha256, + "size_bytes": len(bootstrap_value), + } + } + } + ) + + "\n", + encoding="utf-8", + ) return hashlib.sha256(path.read_bytes()).hexdigest() def _run_verifier( - self, *, runner_os: str, unsupported_value=None + self, + *, + runner_os: str, + unsupported_value=None, + false_field=None, + missing_field=None, + bootstrap_value=b"trusted Windows bootstrap", + tamper_bootstrap=False, + tamper_imports=False, ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as temporary: output_dir = Path(temporary) preflight_sha256 = self._write_preflight( - output_dir, unsupported_value=unsupported_value + output_dir, + unsupported_value=unsupported_value, + false_field=false_field, + missing_field=missing_field, + bootstrap_value=bootstrap_value, ) + if tamper_bootstrap: + (output_dir / "trusted-bootstrap.exe").write_bytes(b"tampered") + if tamper_imports: + (output_dir / "windows-bootstrap-imports.json").write_text( + json.dumps( + { + "schema_version": 1, + "bootstrap_sha256": "0" * 64, + "dependencies": ["evil.dll"], + } + ) + + "\n", + encoding="utf-8", + ) trusted_sha = "a" * 40 candidate_sha = "b" * 40 supervisor_sha256 = "c" * 64 @@ -127,6 +251,33 @@ def test_false_windows_observation_is_rejected(self) -> None: self.assertNotEqual(result.returncode, 0) + def test_false_common_or_observed_proof_is_rejected(self) -> None: + for field in ( + "inside_write", + "adjacent_write_denied", + "descendant_adjacent_write_denied", + "descendant_contained", + "network_denied", + "inbound_network_denied", + "windows_low_integrity", + "windows_no_enabled_privileges", + "windows_registry_write_denied", + "windows_breakaway_denied", + ): + with self.subTest(field=field): + result = self._run_verifier(runner_os="Windows", false_field=field) + self.assertNotEqual(result.returncode, 0) + + def test_missing_common_proof_is_rejected(self) -> None: + result = self._run_verifier(runner_os="Windows", missing_field="inside_write") + self.assertNotEqual(result.returncode, 0) + + def test_bootstrap_hash_or_imports_are_required_for_skips(self) -> None: + for option in ("tamper_bootstrap", "tamper_imports"): + with self.subTest(option=option): + result = self._run_verifier(runner_os="Windows", **{option: True}) + self.assertNotEqual(result.returncode, 0) + def test_skipped_claim_is_rejected_on_linux_and_macos(self) -> None: for runner_os in ("Linux", "macOS"): with self.subTest(runner_os=runner_os): diff --git a/cmux-tui/scripts/test_startup_benchmark_contract.py b/cmux-tui/scripts/test_startup_benchmark_contract.py index 19eb92f5d1f4..75bb5ce9b41e 100644 --- a/cmux-tui/scripts/test_startup_benchmark_contract.py +++ b/cmux-tui/scripts/test_startup_benchmark_contract.py @@ -115,6 +115,12 @@ def windows_evidence() -> dict: "policy": "fixture-root-only-write", "handshake": "nonce-bound-ready-arm-with-pre-exec-t0", "cleanup": "descendant-channel-eof-after-process-tree-empty", + "inside_write": True, + "adjacent_write_denied": True, + "descendant_adjacent_write_denied": True, + "descendant_contained": True, + "network_denied": True, + "inbound_network_denied": True, "linux_unprivileged_userns_clone": None, "linux_max_user_namespaces": None, "windows_low_integrity": True, @@ -208,6 +214,18 @@ def test_missing_common_field_rejects_both_validator_modes(self) -> None: evidence = windows_evidence() del evidence["inside_write"] + for allow_unverified_windows in (True, False): + with self.subTest(allow_unverified_windows=allow_unverified_windows): + with self.assertRaises(ValueError): + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=allow_unverified_windows, + ) + + def test_unknown_field_rejects_both_validator_modes(self) -> None: + evidence = windows_evidence() + evidence["windows_inferred_claim"] = True for allow_unverified_windows in (True, False): with self.subTest(allow_unverified_windows=allow_unverified_windows): with self.assertRaises(ValueError): @@ -217,6 +235,26 @@ def test_missing_common_field_rejects_both_validator_modes(self) -> None: allow_unverified_windows=allow_unverified_windows, ) + def test_resume_counts_reject_bool_and_wrong_numeric_values(self) -> None: + for field in ( + "windows_bootstrap_resume_previous_count", + "windows_product_resume_previous_count", + ): + for value in (True, 0, 2): + with self.subTest(field=field, value=value): + evidence = windows_evidence() + evidence[field] = value + with self.assertRaises(ValueError): + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=True, + ) + + def test_status_contract_constants_are_explicit(self) -> None: + self.assertEqual(CONTRACT.PREFLIGHT_STATUS_VERIFIED, "verified") + self.assertEqual(CONTRACT.PREFLIGHT_STATUS_UNVERIFIED, "unverified") + def test_linux_and_macos_contracts_remain_strict(self) -> None: for backend in ("linux-bwrap", "macos-seatbelt"): with self.subTest(backend=backend): @@ -252,6 +290,16 @@ def test_linux_and_macos_contracts_remain_strict(self) -> None: ), "verified", ) + if backend == "linux-bwrap": + evidence["linux_sudo_bwrap"] = False + self.assertEqual( + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=False, + ), + "verified", + ) if __name__ == "__main__": From c1c8f1e6ae8d18c1567804851f09ef2f075c8339 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:30:33 -0700 Subject: [PATCH 10/23] test: reject false optional Windows observations --- .../scripts/test_startup_benchmark_claim.py | 22 ++++++++++++++----- .../test_startup_benchmark_contract.py | 6 ++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index a428fa91d9fe..0ccb163bef1a 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -17,11 +17,6 @@ SCRIPT = Path(__file__).with_name("startup_benchmark_claim.py") CONTRACT_SCRIPT = Path(__file__).with_name("startup_benchmark_contract.py") VERIFIER = Path(__file__).with_name("verify-startup-benchmark.py") -SPEC = importlib.util.spec_from_file_location("startup_benchmark_claim", SCRIPT) -if SPEC is None or SPEC.loader is None: - raise RuntimeError(f"cannot load {SCRIPT}") -CLAIM = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(CLAIM) CONTRACT_SPEC = importlib.util.spec_from_file_location( "startup_benchmark_contract", CONTRACT_SCRIPT ) @@ -29,6 +24,12 @@ raise RuntimeError(f"cannot load {CONTRACT_SCRIPT}") CONTRACT = importlib.util.module_from_spec(CONTRACT_SPEC) CONTRACT_SPEC.loader.exec_module(CONTRACT) +sys.modules["startup_benchmark_contract"] = CONTRACT +SPEC = importlib.util.spec_from_file_location("startup_benchmark_claim", SCRIPT) +if SPEC is None or SPEC.loader is None: + raise RuntimeError(f"cannot load {SCRIPT}") +CLAIM = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(CLAIM) class SkippedClaimTests(unittest.TestCase): @@ -263,6 +264,11 @@ def test_false_common_or_observed_proof_is_rejected(self) -> None: "windows_no_enabled_privileges", "windows_registry_write_denied", "windows_breakaway_denied", + "windows_grandchild_in_job", + "windows_active_process_zero", + "windows_caller_se_impersonate_enabled", + "windows_standard_handles_valid", + "windows_explicit_handle_list", ): with self.subTest(field=field): result = self._run_verifier(runner_os="Windows", false_field=field) @@ -272,6 +278,12 @@ def test_missing_common_proof_is_rejected(self) -> None: result = self._run_verifier(runner_os="Windows", missing_field="inside_write") self.assertNotEqual(result.returncode, 0) + def test_missing_available_proof_is_rejected(self) -> None: + result = self._run_verifier( + runner_os="Windows", missing_field="windows_bootstrap_sha256" + ) + self.assertNotEqual(result.returncode, 0) + def test_bootstrap_hash_or_imports_are_required_for_skips(self) -> None: for option in ("tamper_bootstrap", "tamper_imports"): with self.subTest(option=option): diff --git a/cmux-tui/scripts/test_startup_benchmark_contract.py b/cmux-tui/scripts/test_startup_benchmark_contract.py index 75bb5ce9b41e..a17eb42b12e8 100644 --- a/cmux-tui/scripts/test_startup_benchmark_contract.py +++ b/cmux-tui/scripts/test_startup_benchmark_contract.py @@ -197,7 +197,11 @@ def test_unavailable_only_windows_fields_are_skippable_but_not_verified(self) -> ) def test_common_or_observed_false_proofs_reject_skipped_and_normal_paths(self) -> None: - for field in (*CORE_FIELDS, "windows_low_integrity"): + for field in ( + *CORE_FIELDS, + *UNAVAILABLE_FIELDS, + "windows_grandchild_in_job", + ): with self.subTest(field=field): evidence = windows_evidence() evidence[field] = False From 4d86e2ccfeb14b4d0270bfca856137b834174e84 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:32:25 -0700 Subject: [PATCH 11/23] test: accept optional Windows child observation --- .../scripts/test_startup_benchmark_claim.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index 0ccb163bef1a..e5b2a931b63f 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -41,6 +41,7 @@ def _write_preflight( false_field=None, missing_field=None, bootstrap_value=b"trusted Windows bootstrap", + grandchild_value=True, ) -> str: nonce = "b" * 64 authentication_id = "0123456789abcdef" @@ -64,7 +65,7 @@ def _write_preflight( "windows_low_integrity": True, "windows_no_enabled_privileges": True, "windows_registry_write_denied": True, - "windows_grandchild_in_job": True, + "windows_grandchild_in_job": grandchild_value, "windows_breakaway_denied": True, "windows_active_process_zero": unsupported_value, "windows_bootstrap_sha256": bootstrap_sha256, @@ -113,7 +114,8 @@ def _write_preflight( del preflight[missing_field] path = output_dir / "sandbox-preflight.json" path.write_text(json.dumps(preflight, sort_keys=True) + "\n", encoding="utf-8") - (output_dir / "windows-bootstrap-imports.json").write_text( + imports_path = output_dir / "windows-bootstrap-imports.json" + imports_path.write_text( json.dumps( { "schema_version": 1, @@ -124,15 +126,22 @@ def _write_preflight( + "\n", encoding="utf-8", ) + imports_sha256 = hashlib.sha256(imports_path.read_bytes()).hexdigest() (output_dir / "startup-integrity-before.json").write_text( json.dumps( { + "trusted_sha": "a" * 40, "files": { "trusted_windows_bootstrap": { "path": str(bootstrap_path.resolve()), "sha256": bootstrap_sha256, "size_bytes": len(bootstrap_value), - } + }, + "trusted_windows_bootstrap_imports": { + "path": str(imports_path.resolve()), + "sha256": imports_sha256, + "size_bytes": imports_path.stat().st_size, + }, } } ) @@ -149,6 +158,7 @@ def _run_verifier( false_field=None, missing_field=None, bootstrap_value=b"trusted Windows bootstrap", + grandchild_value=True, tamper_bootstrap=False, tamper_imports=False, ) -> subprocess.CompletedProcess[str]: @@ -160,6 +170,7 @@ def _run_verifier( false_field=false_field, missing_field=missing_field, bootstrap_value=bootstrap_value, + grandchild_value=grandchild_value, ) if tamper_bootstrap: (output_dir / "trusted-bootstrap.exe").write_bytes(b"tampered") @@ -247,6 +258,11 @@ def test_unavailable_only_windows_claim_is_accepted_as_skipped(self) -> None: self.assertEqual(result.returncode, 0, result.stderr) + optional_result = self._run_verifier( + runner_os="Windows", grandchild_value=None + ) + self.assertEqual(optional_result.returncode, 0, optional_result.stderr) + def test_false_windows_observation_is_rejected(self) -> None: result = self._run_verifier(runner_os="Windows", unsupported_value=False) From f25516f3ae5b80b6bf7bbce5294c3d95e3343647 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:35:17 -0700 Subject: [PATCH 12/23] fix: enforce shared startup preflight evidence contract --- .github/workflows/cmux-tui-spec.yml | 6 + .../workflows/cmux-tui-startup-benchmark.yml | 2 + .github/workflows/cmux-tui.yml | 3 - .../examples/startup_benchmark_preflight.rs | 101 +++- cmux-tui/scripts/startup_benchmark_claim.py | 15 +- .../scripts/startup_benchmark_contract.py | 372 +++++++++++++++ cmux-tui/scripts/verify-startup-benchmark.py | 433 ++++++++++-------- 7 files changed, 721 insertions(+), 211 deletions(-) create mode 100644 cmux-tui/scripts/startup_benchmark_contract.py diff --git a/.github/workflows/cmux-tui-spec.yml b/.github/workflows/cmux-tui-spec.yml index 73f1974387d3..cce90509b887 100644 --- a/.github/workflows/cmux-tui-spec.yml +++ b/.github/workflows/cmux-tui-spec.yml @@ -32,6 +32,12 @@ jobs: - name: Test inventory checker run: python3 cmux-tui/scripts/test_check_spec_inventory.py + - name: Test startup benchmark evidence contract + run: >- + python3 -m unittest -v + cmux-tui/scripts/test_startup_benchmark_contract.py + cmux-tui/scripts/test_startup_benchmark_claim.py + - name: Check protocol and TUI action inventory run: python3 cmux-tui/scripts/check-spec-inventory.py diff --git a/.github/workflows/cmux-tui-startup-benchmark.yml b/.github/workflows/cmux-tui-startup-benchmark.yml index 2e831b337588..03d649455ba1 100644 --- a/.github/workflows/cmux-tui-startup-benchmark.yml +++ b/.github/workflows/cmux-tui-startup-benchmark.yml @@ -1481,6 +1481,8 @@ jobs: "trusted_supervisor": release("TRUSTED_TARGET_ROOT") / "examples" / f"startup_benchmark_supervisor{suffix}", "trusted_preflight": release("TRUSTED_TARGET_ROOT") / "examples" / f"startup_benchmark_preflight{suffix}", "trusted_verifier": pathlib.Path(os.environ["TRUSTED_SOURCE"]) / "cmux-tui/scripts/verify-startup-benchmark.py", + "trusted_evidence_contract": pathlib.Path(os.environ["TRUSTED_SOURCE"]) / "cmux-tui/scripts/startup_benchmark_contract.py", + "trusted_claim_helper": pathlib.Path(os.environ["TRUSTED_SOURCE"]) / "cmux-tui/scripts/startup_benchmark_claim.py", "trusted_workflow": pathlib.Path(os.environ["TRUSTED_SOURCE"]) / ".github/workflows/cmux-tui-startup-benchmark.yml", "candidate_manifest": pathlib.Path(os.environ["ARTIFACT_DIR"]) / "candidate-product-manifest.json", "candidate_validation": pathlib.Path(os.environ["ARTIFACT_DIR"]) / "candidate-product-validation.json", diff --git a/.github/workflows/cmux-tui.yml b/.github/workflows/cmux-tui.yml index 343a37eafab5..34532bbbc09e 100644 --- a/.github/workflows/cmux-tui.yml +++ b/.github/workflows/cmux-tui.yml @@ -924,8 +924,6 @@ jobs: printf 'claim_status=unverified\n' >> "$GITHUB_OUTPUT" printf '%s\n' 'trusted Windows preflight could not observe all required native security signals' \ > "$RUNNER_TEMP/startup-containment/windows-preflight-unverified.txt" - printf '%s\n' '{"schema_version":1,"status":"skipped","claim":"windows-startup-containment","reason":"trusted Windows preflight could not observe all required native security signals"}' \ - > "$RUNNER_TEMP/startup-containment/windows-preflight-claim.json" printf '%s\n' 'Windows startup containment claim skipped: required native security signals were unavailable.' \ >> "$GITHUB_STEP_SUMMARY" fi @@ -954,7 +952,6 @@ jobs: ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility.json ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility-failure.json ${{ runner.temp }}/startup-containment/windows-preflight-unverified.txt - ${{ runner.temp }}/startup-containment/windows-preflight-claim.json if-no-files-found: warn retention-days: 7 diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index fa342d9890b6..d72f45624c81 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -950,11 +950,12 @@ fn run_controller(values: &[String]) -> Result<()> { bail!("preflight backend must be {} on this platform", expected_backend()); } let root = fixture_parent.join(format!("preflight-{}", std::process::id())); + let adjacent = fixture_parent.join("protected-adjacent"); + let child_adjacent = fixture_parent.join("protected-child-adjacent"); + let cleanup = PreflightCleanupGuard::new(&root, &adjacent, &child_adjacent); fs::create_dir(&root).context("create sandbox preflight root")?; let inside = root.join("inside-write"); let probe_result = root.join("probe-result.json"); - let adjacent = fixture_parent.join("protected-adjacent"); - let child_adjacent = fixture_parent.join("protected-child-adjacent"); fs::write(&adjacent, b"protected").context("stage protected parent sentinel")?; fs::write(&child_adjacent, b"protected").context("stage protected descendant sentinel")?; make_write_probe_permissive(&adjacent).context("prepare protected parent sentinel")?; @@ -1508,6 +1509,8 @@ fn run_controller(values: &[String]) -> Result<()> { && evidence.timing_records == 1 && windows_claim_unverified_only(&evidence) { + drop(timing); + cleanup.cleanup()?; return Err(WindowsClaimUnavailable.into()); } bail!( @@ -1517,12 +1520,51 @@ fn run_controller(values: &[String]) -> Result<()> { ); } drop(timing); - fs::remove_dir_all(&root).context("remove successful sandbox preflight root")?; - fs::remove_file(&adjacent).context("remove successful parent sentinel")?; - fs::remove_file(&child_adjacent).context("remove successful descendant sentinel")?; + cleanup.cleanup()?; + Ok(()) +} + +fn cleanup_preflight_state(root: &Path, adjacent: &Path, child_adjacent: &Path) -> Result<()> { + fs::remove_dir_all(root).context("remove sandbox preflight root")?; + fs::remove_file(adjacent).context("remove protected parent sentinel")?; + fs::remove_file(child_adjacent).context("remove protected descendant sentinel")?; Ok(()) } +struct PreflightCleanupGuard { + root: PathBuf, + adjacent: PathBuf, + child_adjacent: PathBuf, + armed: bool, +} + +impl PreflightCleanupGuard { + fn new(root: &Path, adjacent: &Path, child_adjacent: &Path) -> Self { + Self { + root: root.to_path_buf(), + adjacent: adjacent.to_path_buf(), + child_adjacent: child_adjacent.to_path_buf(), + armed: true, + } + } + + fn cleanup(mut self) -> Result<()> { + cleanup_preflight_state(&self.root, &self.adjacent, &self.child_adjacent)?; + self.armed = false; + Ok(()) + } +} + +impl Drop for PreflightCleanupGuard { + fn drop(&mut self) { + if self.armed { + let _ = fs::remove_dir_all(&self.root); + let _ = fs::remove_file(&self.adjacent); + let _ = fs::remove_file(&self.child_adjacent); + } + } +} + fn display_paths(paths: &[PathBuf]) -> String { paths.iter().map(|path| path.display().to_string()).collect::>().join(", ") } @@ -1662,6 +1704,17 @@ fn sha256_file(path: &Path, name: &str) -> Result { #[cfg(windows)] fn windows_claim_unverified_only(evidence: &PreflightEvidence) -> bool { + if !evidence.inside_write + || !evidence.adjacent_write_denied + || !evidence.descendant_adjacent_write_denied + || !evidence.descendant_contained + || !evidence.network_denied + || !evidence.inbound_network_denied + || !evidence.supervisor_ready + || evidence.timing_records != 1 + { + return false; + } let observations = WindowsPreflightObservations { grandchild_in_job: evidence.windows_grandchild_in_job, active_process_zero: evidence.windows_active_process_zero, @@ -1674,6 +1727,9 @@ fn windows_claim_unverified_only(evidence: &PreflightEvidence) -> bool { { return false; } + // A Windows child-membership probe can legitimately return None. The shared + // Python contract records that as unverified, while every other native and + // common proof must remain true before this exit-78 path is eligible. let mut complete = evidence.clone(); complete.windows_grandchild_in_job = Some(true); complete.windows_active_process_zero = Some(true); @@ -2305,9 +2361,44 @@ mod tests { fs::remove_dir(&parent).unwrap(); } + #[test] + fn preflight_cleanup_guard_removes_state_when_controller_unwinds() { + let unique = format!( + "cmux-preflight-guard-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let parent = std::env::temp_dir().join(unique); + let root = parent.join("preflight-root"); + let adjacent = parent.join("protected-adjacent"); + let child_adjacent = parent.join("protected-child-adjacent"); + fs::create_dir_all(&root).unwrap(); + fs::write(&adjacent, b"protected").unwrap(); + fs::write(&child_adjacent, b"protected").unwrap(); + + { + let _guard = PreflightCleanupGuard::new(&root, &adjacent, &child_adjacent); + } + + assert!(!root.exists()); + assert!(!adjacent.exists()); + assert!(!child_adjacent.exists()); + fs::remove_dir(&parent).unwrap(); + } + #[cfg(windows)] fn complete_windows_evidence() -> PreflightEvidence { let mut evidence = PreflightEvidence::default(); + evidence.inside_write = true; + evidence.adjacent_write_denied = true; + evidence.descendant_adjacent_write_denied = true; + evidence.descendant_contained = true; + evidence.network_denied = true; + evidence.inbound_network_denied = true; + evidence.supervisor_ready = true; evidence.windows_low_integrity = Some(true); evidence.windows_no_enabled_privileges = Some(true); evidence.windows_registry_write_denied = Some(true); diff --git a/cmux-tui/scripts/startup_benchmark_claim.py b/cmux-tui/scripts/startup_benchmark_claim.py index 635d3a561178..daf484ada355 100644 --- a/cmux-tui/scripts/startup_benchmark_claim.py +++ b/cmux-tui/scripts/startup_benchmark_claim.py @@ -8,14 +8,19 @@ import os from pathlib import Path +from startup_benchmark_contract import ( + PREFLIGHT_STATUS_UNVERIFIED, + SANDBOX_CLEANUP, + SANDBOX_HANDSHAKE, + SANDBOX_POLICY, + WINDOWS_BACKEND as CONTRACT_WINDOWS_BACKEND, +) + SKIPPED_SCHEMA_VERSION = 4 PAIR_ORDER = "serial alternating baseline-first and candidate-first pairs" WINDOWS_PLATFORM = "windows-azure" -WINDOWS_BACKEND = "windows-restricted-token-job" -SANDBOX_POLICY = "fixture-root-only-write" -SANDBOX_HANDSHAKE = "nonce-bound-ready-arm-with-pre-exec-t0" -SANDBOX_CLEANUP = "descendant-channel-eof-after-process-tree-empty" +WINDOWS_BACKEND = CONTRACT_WINDOWS_BACKEND PROFILE_PURPOSE = "offline attribution of native cmux-tui startup profiles" @@ -64,7 +69,7 @@ def build_skipped_report( "sandbox_policy": SANDBOX_POLICY, "sandbox_handshake": SANDBOX_HANDSHAKE, "sandbox_cleanup": SANDBOX_CLEANUP, - "sandbox_claim_status": "unverified", + "sandbox_claim_status": PREFLIGHT_STATUS_UNVERIFIED, "sandbox_claim_reason": reason, "expected_supervisor_sha256": supervisor_sha256, "supervisor_sha256": supervisor_sha256, diff --git a/cmux-tui/scripts/startup_benchmark_contract.py b/cmux-tui/scripts/startup_benchmark_contract.py new file mode 100644 index 000000000000..9e6b026ebdf5 --- /dev/null +++ b/cmux-tui/scripts/startup_benchmark_contract.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 +"""Shared schema and validation for startup sandbox preflight evidence. + +The validator returns ``verified`` for a complete platform claim. Windows also +has an explicit ``unverified`` state, which is allowed only when every common +and observed proof passes and each unsupported native observation is either +true or unavailable. Any false, missing, malformed, or foreign-platform field +is a hard failure. ``windows_grandchild_in_job`` is the one optional native +observation, so ``None`` is accepted only for the unverified Windows state. +Linux and macOS never return ``unverified``. +""" + +from __future__ import annotations + +import re + + +PREFLIGHT_SCHEMA_VERSION = 8 +PREFLIGHT_STATUS_VERIFIED = "verified" +PREFLIGHT_STATUS_UNVERIFIED = "unverified" +WINDOWS_BACKEND = "windows-restricted-token-job" +LINUX_BACKEND = "linux-bwrap" +MACOS_BACKEND = "macos-seatbelt" +SANDBOX_POLICY = "fixture-root-only-write" +SANDBOX_HANDSHAKE = "nonce-bound-ready-arm-with-pre-exec-t0" +SANDBOX_CLEANUP = "descendant-channel-eof-after-process-tree-empty" +SHA256_PATTERN = re.compile(r"[0-9a-f]{64}") +HEX64_PATTERN = re.compile(r"[0-9a-fA-F]{64}") +SID_PATTERN = re.compile(r"S-1(?:-\d+)+") +AUTHENTICATION_ID_PATTERN = re.compile(r"[0-9a-fA-F]{16}") + +COMMON_BOOLEAN_FIELDS = ( + "inside_write", + "adjacent_write_denied", + "descendant_adjacent_write_denied", + "descendant_contained", + "network_denied", + "inbound_network_denied", + "supervisor_ready", +) +LINUX_FIELDS = ( + "linux_no_new_privs", + "linux_effective_capabilities_zero", + "linux_sudo_bwrap", + "linux_bwrap_version", + "linux_unprivileged_userns_clone", + "linux_max_user_namespaces", +) +WINDOWS_CORE_BOOLEAN_FIELDS = ( + "windows_low_integrity", + "windows_no_enabled_privileges", + "windows_registry_write_denied", + "windows_breakaway_denied", +) +WINDOWS_OPTIONAL_FIELDS = ("windows_grandchild_in_job",) +WINDOWS_UNAVAILABLE_FIELDS = ( + "windows_active_process_zero", + "windows_caller_se_impersonate_enabled", + "windows_standard_handles_valid", + "windows_explicit_handle_list", +) +WINDOWS_REQUIRED_FIELDS = ( + "windows_bootstrap_sha256", + "windows_bootstrap_config_nonce", + "windows_bootstrap_config_consumed", + "windows_bootstrap_resume_previous_count", + "windows_bootstrap_ready_elapsed_ms", + "windows_bootstrap_exact_job", + "windows_bootstrap_trusted_path_write_denied", + "windows_bootstrap_self_write_denied", + "windows_restricting_sid", + "windows_broker_authentication_id", + "windows_restricted_authentication_id", + "windows_product_authentication_id", + "windows_restricted_authentication_matches_broker", + "windows_product_authentication_matches_broker", + "windows_se_increase_quota_present", + "windows_se_increase_quota_enabled", + "windows_create_process_as_user_succeeded", + "windows_restricted_token_write_restricted", + "windows_restricted_token_restricting_sid_match", + "windows_restricted_token_low_integrity", + "windows_restricted_token_no_enabled_privileges", + "windows_product_write_restricted", + "windows_product_restricting_sid_match", + "windows_product_low_integrity", + "windows_product_no_enabled_privileges", + "windows_product_exact_job", + "windows_product_resume_previous_count", + "windows_product_process_id", + "windows_product_primary_thread_id", + "windows_private_desktop", + "windows_private_window_station_created", + "windows_private_desktop_created", + "windows_private_desktop_broker_assigned", + "windows_private_desktop_product_assigned", + "windows_private_desktop_closed_after_job_empty", +) +WINDOWS_REQUIRED_BOOLEAN_FIELDS = ( + "windows_bootstrap_config_consumed", + "windows_bootstrap_exact_job", + "windows_bootstrap_trusted_path_write_denied", + "windows_bootstrap_self_write_denied", + "windows_restricted_authentication_matches_broker", + "windows_product_authentication_matches_broker", + "windows_se_increase_quota_present", + "windows_se_increase_quota_enabled", + "windows_create_process_as_user_succeeded", + "windows_restricted_token_write_restricted", + "windows_restricted_token_restricting_sid_match", + "windows_restricted_token_low_integrity", + "windows_restricted_token_no_enabled_privileges", + "windows_product_write_restricted", + "windows_product_restricting_sid_match", + "windows_product_low_integrity", + "windows_product_no_enabled_privileges", + "windows_product_exact_job", + "windows_private_window_station_created", + "windows_private_desktop_created", + "windows_private_desktop_broker_assigned", + "windows_private_desktop_product_assigned", + "windows_private_desktop_closed_after_job_empty", +) +WINDOWS_FIELDS = ( + *WINDOWS_CORE_BOOLEAN_FIELDS, + *WINDOWS_OPTIONAL_FIELDS, + *WINDOWS_UNAVAILABLE_FIELDS, + *WINDOWS_REQUIRED_FIELDS, +) +EXPECTED_PREFLIGHT_FIELDS = ( + "schema_version", + "backend", + "policy", + "handshake", + "cleanup", + *COMMON_BOOLEAN_FIELDS, + *LINUX_FIELDS, + *WINDOWS_FIELDS, + "timing_records", + "supervisor_sha256", +) + + +def _require(condition: bool, message: str) -> None: + if not condition: + raise ValueError(message) + + +def _require_true_fields(evidence: dict, fields: tuple[str, ...], label: str) -> None: + _require( + all(evidence[field] is True for field in fields), + f"{label} must be true", + ) + + +def _require_optional_boolean_fields( + evidence: dict, fields: tuple[str, ...], label: str +) -> None: + _require( + all( + evidence[field] is None or isinstance(evidence[field], bool) + for field in fields + ), + f"{label} must be boolean or unavailable", + ) + + +def _validate_identity(evidence: dict, expected_supervisor_sha256: str) -> None: + _require( + isinstance(evidence, dict), + "preflight evidence must be an object", + ) + actual_fields = set(evidence) + expected_fields = set(EXPECTED_PREFLIGHT_FIELDS) + _require( + actual_fields == expected_fields, + "preflight evidence has unknown or missing fields", + ) + _require( + evidence["schema_version"] == PREFLIGHT_SCHEMA_VERSION, + "preflight evidence has the wrong schema version", + ) + _require( + evidence["policy"] == SANDBOX_POLICY + and evidence["handshake"] == SANDBOX_HANDSHAKE + and evidence["cleanup"] == SANDBOX_CLEANUP, + "preflight evidence has the wrong sandbox contract", + ) + _require( + isinstance(expected_supervisor_sha256, str) + and SHA256_PATTERN.fullmatch(expected_supervisor_sha256) is not None, + "expected supervisor SHA-256 is invalid", + ) + _require( + evidence["supervisor_sha256"] == expected_supervisor_sha256, + "preflight evidence has the wrong supervisor SHA-256", + ) + _require( + isinstance(evidence["timing_records"], int) + and not isinstance(evidence["timing_records"], bool) + and evidence["timing_records"] == 1, + "preflight evidence has incomplete timing records", + ) + _require_true_fields(evidence, COMMON_BOOLEAN_FIELDS, "common preflight proofs") + + +def _validate_linux(evidence: dict) -> str: + _require(evidence["backend"] == LINUX_BACKEND, "Linux preflight backend is invalid") + _require_true_fields( + evidence, + ("linux_no_new_privs", "linux_effective_capabilities_zero"), + "Linux preflight proofs", + ) + _require( + isinstance(evidence["linux_sudo_bwrap"], bool), + "Linux sudo bubblewrap mode is unavailable", + ) + _require( + isinstance(evidence["linux_bwrap_version"], str) + and bool(evidence["linux_bwrap_version"]), + "Linux bubblewrap version is unavailable", + ) + for field in ("linux_unprivileged_userns_clone", "linux_max_user_namespaces"): + value = evidence[field] + _require( + value is None + or ( + isinstance(value, int) + and not isinstance(value, bool) + and value >= 0 + ), + f"{field} is invalid", + ) + _require_optional_boolean_fields( + evidence, + ("linux_no_new_privs", "linux_effective_capabilities_zero", "linux_sudo_bwrap"), + "Linux boolean fields", + ) + _require( + all(evidence[field] is None for field in WINDOWS_FIELDS), + "Linux preflight contains Windows proof fields", + ) + return PREFLIGHT_STATUS_VERIFIED + + +def _validate_macos(evidence: dict) -> str: + _require(evidence["backend"] == MACOS_BACKEND, "macOS preflight backend is invalid") + _require( + all(evidence[field] is None for field in (*LINUX_FIELDS, *WINDOWS_FIELDS)), + "macOS preflight contains foreign-platform proof fields", + ) + return PREFLIGHT_STATUS_VERIFIED + + +def _validate_windows(evidence: dict, allow_unverified_windows: bool) -> str: + _require( + evidence["backend"] == WINDOWS_BACKEND, + "Windows preflight backend is invalid", + ) + _require( + all(evidence[field] is None for field in LINUX_FIELDS), + "Windows preflight contains Linux proof fields", + ) + _require_true_fields(evidence, WINDOWS_CORE_BOOLEAN_FIELDS, "Windows core proofs") + _require_optional_boolean_fields( + evidence, + (*WINDOWS_CORE_BOOLEAN_FIELDS, *WINDOWS_OPTIONAL_FIELDS, *WINDOWS_UNAVAILABLE_FIELDS), + "Windows observation fields", + ) + + grandchild = evidence["windows_grandchild_in_job"] + _require( + grandchild is None or grandchild is True, + "Windows job-membership proof is false", + ) + unavailable = [evidence[field] for field in WINDOWS_UNAVAILABLE_FIELDS] + _require( + all(value is None or value is True for value in unavailable), + "Windows unavailable proof fields contain a failure", + ) + + _require( + all(evidence[field] is not None for field in WINDOWS_REQUIRED_FIELDS), + "Windows available proof fields are incomplete", + ) + _require_true_fields( + evidence, + WINDOWS_REQUIRED_BOOLEAN_FIELDS, + "Windows available boolean proofs", + ) + + bootstrap_sha256 = evidence["windows_bootstrap_sha256"] + bootstrap_nonce = evidence["windows_bootstrap_config_nonce"] + _require( + isinstance(bootstrap_sha256, str) + and SHA256_PATTERN.fullmatch(bootstrap_sha256) is not None, + "Windows bootstrap SHA-256 is invalid", + ) + _require( + isinstance(bootstrap_nonce, str) + and HEX64_PATTERN.fullmatch(bootstrap_nonce) is not None, + "Windows bootstrap nonce is invalid", + ) + _require( + isinstance(evidence["windows_bootstrap_resume_previous_count"], int) + and not isinstance(evidence["windows_bootstrap_resume_previous_count"], bool) + and evidence["windows_bootstrap_resume_previous_count"] == 1 + and isinstance(evidence["windows_bootstrap_ready_elapsed_ms"], int) + and not isinstance(evidence["windows_bootstrap_ready_elapsed_ms"], bool) + and 0 <= evidence["windows_bootstrap_ready_elapsed_ms"] <= 30_000, + "Windows bootstrap timing evidence is invalid", + ) + restricting_sid = evidence["windows_restricting_sid"] + authentication_id = evidence["windows_broker_authentication_id"] + _require( + isinstance(restricting_sid, str) + and SID_PATTERN.fullmatch(restricting_sid) is not None + and len(restricting_sid) <= 184, + "Windows restricting SID is invalid", + ) + _require( + isinstance(authentication_id, str) + and AUTHENTICATION_ID_PATTERN.fullmatch(authentication_id) is not None + and evidence["windows_restricted_authentication_id"] == authentication_id + and evidence["windows_product_authentication_id"] == authentication_id, + "Windows authentication evidence is invalid", + ) + _require( + isinstance(evidence["windows_product_resume_previous_count"], int) + and not isinstance(evidence["windows_product_resume_previous_count"], bool) + and evidence["windows_product_resume_previous_count"] == 1, + "Windows product resume evidence is invalid", + ) + for field in ("windows_product_process_id", "windows_product_primary_thread_id"): + _require( + isinstance(evidence[field], int) + and not isinstance(evidence[field], bool) + and 0 < evidence[field] <= 0xFFFFFFFF, + f"{field} is invalid", + ) + expected_private_desktop = ( + f"cmuxb-{bootstrap_nonce[:24]}\\desk-{bootstrap_nonce[24:48]}" + ) + _require( + evidence["windows_private_desktop"] == expected_private_desktop, + "Windows private desktop evidence is invalid", + ) + + if all(value is True for value in unavailable): + return PREFLIGHT_STATUS_VERIFIED + if not allow_unverified_windows: + raise ValueError("Windows native observations are unavailable") + return PREFLIGHT_STATUS_UNVERIFIED + + +def validate_preflight_evidence( + evidence: dict, + *, + expected_supervisor_sha256: str, + allow_unverified_windows: bool, +) -> str: + """Validate all preflight fields and return an explicit claim status.""" + + _validate_identity(evidence, expected_supervisor_sha256) + backend = evidence["backend"] + if backend == LINUX_BACKEND: + return _validate_linux(evidence) + if backend == MACOS_BACKEND: + return _validate_macos(evidence) + if backend == WINDOWS_BACKEND: + return _validate_windows(evidence, allow_unverified_windows) + raise ValueError(f"unsupported preflight backend: {backend!r}") diff --git a/cmux-tui/scripts/verify-startup-benchmark.py b/cmux-tui/scripts/verify-startup-benchmark.py index b9d65611a8f4..3aefa2c1005f 100644 --- a/cmux-tui/scripts/verify-startup-benchmark.py +++ b/cmux-tui/scripts/verify-startup-benchmark.py @@ -8,16 +8,31 @@ import re import sys +from startup_benchmark_contract import ( + PREFLIGHT_STATUS_UNVERIFIED, + PREFLIGHT_STATUS_VERIFIED, + validate_preflight_evidence, +) + FULL_SHA_PATTERN = re.compile(r"[0-9a-f]{40}") FULL_SHA256_PATTERN = re.compile(r"[0-9a-f]{64}") SKIPPED_REPORT_SCHEMA = 4 SKIPPED_PLATFORM = "windows-azure" SKIPPED_BACKEND = "windows-restricted-token-job" -SKIPPED_PREFLIGHT_FIELDS = ( - "windows_active_process_zero", - "windows_caller_se_impersonate_enabled", - "windows_standard_handles_valid", - "windows_explicit_handle_list", +WINDOWS_BOOTSTRAP_IMPORT_SCHEMA = 1 +APPROVED_WINDOWS_BOOTSTRAP_DEPENDENCIES = { + "advapi32.dll", + "bcrypt.dll", + "kernel32.dll", +} +WINDOWS_API_SET_PATTERN = re.compile( + r"(?i:(?:api|ext)-[a-z0-9-]+-l[0-9]+-[0-9]+-[0-9]+\.dll)" +) +WINDOWS_BOOTSTRAP_REPORT_FIELDS = ( + "infrastructure.windows_bootstrap_binary", + "infrastructure.expected_windows_bootstrap_sha256", + "infrastructure.windows_bootstrap_sha256", + "infrastructure.windows_bootstrap_bytes", ) @@ -59,6 +74,164 @@ def load_json_object(path, label): return value +def load_json_with_optional_bom(path, label): + if path.is_symlink() or not path.is_file(): + raise SystemExit(f"{label} is missing or is not a regular file: {path}") + try: + value = json.loads(path.read_text(encoding="utf-8-sig")) + except (OSError, UnicodeError, json.JSONDecodeError) as error: + raise SystemExit(f"{label} is invalid: {error}") from error + if not isinstance(value, dict): + raise SystemExit(f"{label} must be a JSON object") + return value + + +def validate_windows_bootstrap_attestation( + artifact_root, + preflight, + *, + expected_trusted_sha=None, + expected_bootstrap_path=None, +): + """Validate the trusted bootstrap binary and its audited imports. + + The skipped Windows path still carries the bootstrap identity in the + preflight evidence. Requiring the immutable-input record and import audit + here prevents an unavailable observation from becoming an unlinked claim. + """ + integrity = load_json_object( + artifact_root / "startup-integrity-before.json", + "startup integrity record", + ) + files = integrity.get("files") + if not isinstance(files, dict): + raise SystemExit("startup integrity record has no file map") + if expected_trusted_sha is not None and integrity.get("trusted_sha") != expected_trusted_sha: + raise SystemExit("startup integrity record has the wrong trusted SHA") + record = files.get("trusted_windows_bootstrap") + if not isinstance(record, dict) or set(record) != {"path", "sha256", "size_bytes"}: + raise SystemExit("startup integrity record has no trusted Windows bootstrap") + path_value = record["path"] + expected_sha256 = record["sha256"] + expected_size = record["size_bytes"] + if ( + not isinstance(path_value, str) + or not path_value + or not pathlib.Path(path_value).is_absolute() + or not isinstance(expected_sha256, str) + or FULL_SHA256_PATTERN.fullmatch(expected_sha256) is None + or not isinstance(expected_size, int) + or isinstance(expected_size, bool) + or expected_size <= 0 + ): + raise SystemExit("trusted Windows bootstrap integrity metadata is invalid") + bootstrap_path = pathlib.Path(path_value) + if ( + expected_bootstrap_path is not None + and ( + not isinstance(expected_bootstrap_path, str) + or pathlib.Path(expected_bootstrap_path).resolve() != bootstrap_path.resolve() + ) + ): + raise SystemExit("startup report is not linked to the attested Windows bootstrap") + if bootstrap_path.is_symlink() or not bootstrap_path.is_file(): + raise SystemExit("trusted Windows bootstrap binary is missing") + actual_size = bootstrap_path.stat().st_size + actual_sha256 = file_sha256(bootstrap_path) + if actual_size != expected_size or actual_sha256 != expected_sha256: + raise SystemExit("trusted Windows bootstrap changed after attestation") + evidence_sha256 = preflight.get("windows_bootstrap_sha256") + if evidence_sha256 != actual_sha256: + raise SystemExit("preflight does not identify the attested Windows bootstrap") + + import_path = artifact_root / "windows-bootstrap-imports.json" + import_record = files.get("trusted_windows_bootstrap_imports") + if not isinstance(import_record, dict) or set(import_record) != {"path", "sha256", "size_bytes"}: + raise SystemExit("startup integrity record has no bootstrap import audit") + if ( + import_record["path"] != str(import_path.resolve()) + or import_path.is_symlink() + or not import_path.is_file() + or not isinstance(import_record["sha256"], str) + or FULL_SHA256_PATTERN.fullmatch(import_record["sha256"]) is None + or not isinstance(import_record["size_bytes"], int) + or isinstance(import_record["size_bytes"], bool) + or import_record["size_bytes"] <= 0 + or file_sha256(import_path) != import_record["sha256"] + or import_path.stat().st_size != import_record["size_bytes"] + ): + raise SystemExit("trusted Windows bootstrap import audit changed after attestation") + import_evidence = load_json_with_optional_bom( + import_path, + "trusted Windows bootstrap import evidence", + ) + if set(import_evidence) != {"schema_version", "bootstrap_sha256", "dependencies"}: + raise SystemExit("trusted Windows bootstrap import evidence has unknown fields") + dependencies = import_evidence["dependencies"] + if ( + import_evidence["schema_version"] != WINDOWS_BOOTSTRAP_IMPORT_SCHEMA + or import_evidence["bootstrap_sha256"] != actual_sha256 + or not isinstance(dependencies, list) + or not dependencies + or any( + not isinstance(dependency, str) + or re.fullmatch(r"[A-Za-z0-9._-]+\.dll", dependency) is None + for dependency in dependencies + ) + or len(dependencies) != len({dependency.lower() for dependency in dependencies}) + ): + raise SystemExit("trusted Windows bootstrap import evidence is invalid") + if any( + dependency.lower() not in APPROVED_WINDOWS_BOOTSTRAP_DEPENDENCIES + and WINDOWS_API_SET_PATTERN.fullmatch(dependency) is None + for dependency in dependencies + ): + raise SystemExit("trusted Windows bootstrap imports an unapproved DLL") + + +def validate_report_preflight( + document, + artifact_root, + *, + expected_supervisor_sha256, + allow_unverified_windows, + expected_bootstrap_path=None, +): + """Run the one preflight contract for normal and skipped reports.""" + infrastructure = document.get("infrastructure") + if not isinstance(infrastructure, dict): + raise SystemExit("startup report has no infrastructure evidence") + preflight_path = artifact_root / "sandbox-preflight.json" + preflight = load_json_object(preflight_path, "sandbox preflight evidence") + if ( + preflight.get("policy") != infrastructure.get("sandbox_policy") + or preflight.get("handshake") != infrastructure.get("sandbox_handshake") + or preflight.get("cleanup") != infrastructure.get("sandbox_cleanup") + or preflight.get("supervisor_sha256") != expected_supervisor_sha256 + or file_sha256(preflight_path) != infrastructure.get("preflight_sha256") + ): + raise SystemExit("startup report does not prove its sandbox preflight") + try: + status = validate_preflight_evidence( + preflight, + expected_supervisor_sha256=expected_supervisor_sha256, + allow_unverified_windows=allow_unverified_windows, + ) + except ValueError as error: + raise SystemExit(f"startup report preflight is invalid: {error}") from error + if preflight.get("backend") == SKIPPED_BACKEND: + validate_windows_bootstrap_attestation( + artifact_root, + preflight, + expected_trusted_sha=( + document.get("trusted_sha") + or infrastructure.get("trusted_sha") + ), + expected_bootstrap_path=expected_bootstrap_path, + ) + return status + + def require_nonempty_artifact(path, label): if path.is_symlink(): raise SystemExit(f"{label} is a symlink: {path}") @@ -206,7 +379,7 @@ def validate_skipped_report(document, artifact_root): or infrastructure["sandbox_policy"] != "fixture-root-only-write" or infrastructure["sandbox_handshake"] != "nonce-bound-ready-arm-with-pre-exec-t0" or infrastructure["sandbox_cleanup"] != "descendant-channel-eof-after-process-tree-empty" - or infrastructure["sandbox_claim_status"] != "unverified" + or infrastructure["sandbox_claim_status"] != PREFLIGHT_STATUS_UNVERIFIED or infrastructure["sandbox_claim_reason"] != reason or not isinstance(infrastructure["expected_supervisor_sha256"], str) or FULL_SHA256_PATTERN.fullmatch(infrastructure["expected_supervisor_sha256"]) is None @@ -239,24 +412,14 @@ def validate_skipped_report(document, artifact_root): if os.environ.get("RUNNER_OS") not in (None, "Windows"): raise SystemExit("skipped startup report is only valid on Windows") - preflight_path = artifact_root / "sandbox-preflight.json" - preflight = load_json_object(preflight_path, "sandbox preflight evidence") - if ( - preflight.get("schema_version") != 8 - or preflight.get("backend") != SKIPPED_BACKEND - or "windows_grandchild_in_job" not in preflight - or ( - preflight.get("windows_grandchild_in_job") is not None - and preflight.get("windows_grandchild_in_job") is not True - ) - or any(field not in preflight for field in SKIPPED_PREFLIGHT_FIELDS) - or any(preflight[field] is not None for field in SKIPPED_PREFLIGHT_FIELDS) - or preflight.get("policy") != infrastructure["sandbox_policy"] - or preflight.get("handshake") != infrastructure["sandbox_handshake"] - or preflight.get("cleanup") != infrastructure["sandbox_cleanup"] - or file_sha256(preflight_path) != infrastructure["preflight_sha256"] - ): - raise SystemExit("skipped report does not prove unavailable Windows observations") + claim_status = validate_report_preflight( + document, + artifact_root, + expected_supervisor_sha256=infrastructure["supervisor_sha256"], + allow_unverified_windows=True, + ) + if claim_status != PREFLIGHT_STATUS_UNVERIFIED: + raise SystemExit("skipped report must contain unavailable Windows observations") markdown_path = artifact_root / "startup-benchmark.md" markdown = markdown_path.read_text(encoding="utf-8") @@ -517,6 +680,22 @@ def close_artifact(artifact_root): validate_skipped_report(report, artifact_root) else: validate_raw_distributions(artifact_root) + infrastructure = report.get("infrastructure") + if not isinstance(infrastructure, dict): + raise SystemExit("startup report has no infrastructure evidence") + status = validate_report_preflight( + report, + artifact_root, + expected_supervisor_sha256=infrastructure.get("supervisor_sha256"), + allow_unverified_windows=False, + expected_bootstrap_path=( + infrastructure.get("windows_bootstrap_binary") + if os.environ.get("RUNNER_OS") == "Windows" + else None + ), + ) + if status != PREFLIGHT_STATUS_VERIFIED: + raise SystemExit("normal startup report cannot be unverified") validate_harness_test_evidence(artifact_root) if not skipped: validate_required_native_profiles(artifact_root) @@ -1003,192 +1182,50 @@ def get(dotted): "infrastructure.expected_preflight_sha256" ): raise SystemExit("sandbox preflight evidence changed after attestation") -preflight_path = path.parent / "sandbox-preflight.json" -preflight_bytes = preflight_path.read_bytes() -if hashlib.sha256(preflight_bytes).hexdigest() != get( - "infrastructure.preflight_sha256" -): - raise SystemExit("sandbox preflight file does not match its attested SHA-256") -preflight = json.loads(preflight_bytes) -if not isinstance(preflight, dict) or preflight.get("schema_version") != 8: - raise SystemExit("sandbox preflight evidence has the wrong schema") appcontainer_feasibility_path = path.parent / "windows-appcontainer-feasibility.json" if os.environ["RUNNER_OS"] == "Windows": validate_appcontainer_feasibility(appcontainer_feasibility_path) elif appcontainer_feasibility_path.exists(): raise SystemExit("non-Windows evidence contains an AppContainer feasibility record") -windows_preflight_fields = ( - "windows_grandchild_in_job", - "windows_active_process_zero", - "windows_caller_se_impersonate_enabled", - "windows_standard_handles_valid", - "windows_explicit_handle_list", - "windows_bootstrap_sha256", - "windows_bootstrap_config_nonce", - "windows_bootstrap_config_consumed", - "windows_bootstrap_resume_previous_count", - "windows_bootstrap_ready_elapsed_ms", - "windows_bootstrap_exact_job", - "windows_bootstrap_trusted_path_write_denied", - "windows_bootstrap_self_write_denied", - "windows_restricting_sid", - "windows_broker_authentication_id", - "windows_restricted_authentication_id", - "windows_product_authentication_id", - "windows_restricted_authentication_matches_broker", - "windows_product_authentication_matches_broker", - "windows_se_increase_quota_present", - "windows_se_increase_quota_enabled", - "windows_create_process_as_user_succeeded", - "windows_restricted_token_write_restricted", - "windows_restricted_token_low_integrity", - "windows_restricted_token_no_enabled_privileges", - "windows_restricted_token_restricting_sid_match", - "windows_product_write_restricted", - "windows_product_low_integrity", - "windows_product_no_enabled_privileges", - "windows_product_restricting_sid_match", - "windows_product_exact_job", - "windows_product_resume_previous_count", - "windows_product_process_id", - "windows_product_primary_thread_id", - "windows_private_desktop", - "windows_private_window_station_created", - "windows_private_desktop_created", - "windows_private_desktop_broker_assigned", - "windows_private_desktop_product_assigned", - "windows_private_desktop_closed_after_job_empty", +expected_bootstrap_path = ( + get(WINDOWS_BOOTSTRAP_REPORT_FIELDS[0]) + if os.environ["RUNNER_OS"] == "Windows" + else None ) -if any(field not in preflight for field in windows_preflight_fields): - raise SystemExit("sandbox preflight evidence is missing a Windows bootstrap field") -windows_bootstrap_fields = ( - "infrastructure.windows_bootstrap_binary", - "infrastructure.expected_windows_bootstrap_sha256", - "infrastructure.windows_bootstrap_sha256", - "infrastructure.windows_bootstrap_bytes", +preflight_status = validate_report_preflight( + document, + path.parent, + expected_supervisor_sha256=get("infrastructure.supervisor_sha256"), + allow_unverified_windows=False, + expected_bootstrap_path=expected_bootstrap_path, ) +if preflight_status != PREFLIGHT_STATUS_VERIFIED: + raise SystemExit("normal startup evidence cannot be unverified") if os.environ["RUNNER_OS"] == "Windows": - bootstrap_binary = get(windows_bootstrap_fields[0]) - expected_bootstrap_sha256 = get(windows_bootstrap_fields[1]) - bootstrap_sha256 = get(windows_bootstrap_fields[2]) - bootstrap_bytes = get(windows_bootstrap_fields[3]) - if not isinstance(bootstrap_binary, str) or not bootstrap_binary: - raise SystemExit("benchmark evidence has no trusted Windows bootstrap path") - if expected_bootstrap_sha256 != os.environ["WINDOWS_BOOTSTRAP_SHA256"]: - raise SystemExit("benchmark evidence has the wrong trusted Windows bootstrap SHA-256") - if bootstrap_sha256 != expected_bootstrap_sha256: - raise SystemExit("trusted Windows bootstrap changed after exact build attestation") + bootstrap_binary = get(WINDOWS_BOOTSTRAP_REPORT_FIELDS[0]) + expected_bootstrap_sha256 = get(WINDOWS_BOOTSTRAP_REPORT_FIELDS[1]) + bootstrap_sha256 = get(WINDOWS_BOOTSTRAP_REPORT_FIELDS[2]) + bootstrap_bytes = get(WINDOWS_BOOTSTRAP_REPORT_FIELDS[3]) if ( - not isinstance(bootstrap_bytes, int) + not isinstance(bootstrap_binary, str) + or not bootstrap_binary + or not isinstance(expected_bootstrap_sha256, str) + or FULL_SHA256_PATTERN.fullmatch(expected_bootstrap_sha256) is None + or expected_bootstrap_sha256 != os.environ["WINDOWS_BOOTSTRAP_SHA256"] + or bootstrap_sha256 != expected_bootstrap_sha256 + or not isinstance(bootstrap_bytes, int) or isinstance(bootstrap_bytes, bool) or bootstrap_bytes <= 0 ): - raise SystemExit("benchmark evidence has an invalid trusted Windows bootstrap size") - for dotted in windows_bootstrap_fields[1:3]: - value = get(dotted) - if len(value) != 64 or any( - character not in "0123456789abcdef" for character in value - ): - raise SystemExit(f"benchmark evidence has invalid {dotted}: {value!r}") - import_evidence = json.loads( - (path.parent / "windows-bootstrap-imports.json").read_text(encoding="utf-8-sig") - ) - if set(import_evidence) != {"schema_version", "bootstrap_sha256", "dependencies"}: - raise SystemExit("trusted Windows bootstrap import evidence has unknown fields") - dependencies = import_evidence["dependencies"] - if ( - import_evidence["schema_version"] != 1 - or import_evidence["bootstrap_sha256"] != bootstrap_sha256 - or not isinstance(dependencies, list) - or not dependencies - or len(dependencies) != len(set(dependencies)) - or any( - not isinstance(dependency, str) - or re.fullmatch(r"[A-Za-z0-9._-]+\.dll", dependency) is None - for dependency in dependencies - ) - ): - raise SystemExit("trusted Windows bootstrap import evidence is invalid") - approved_physical_dependencies = {"advapi32.dll", "bcrypt.dll", "kernel32.dll"} - api_set_pattern = re.compile( - r"(?i:(?:api|ext)-[a-z0-9-]+-l[0-9]+-[0-9]+-[0-9]+\.dll)" - ) - if any( - dependency.lower() not in approved_physical_dependencies - and api_set_pattern.fullmatch(dependency) is None - for dependency in dependencies - ): - raise SystemExit("trusted Windows bootstrap imports an unapproved DLL") - ready_elapsed_ms = preflight["windows_bootstrap_ready_elapsed_ms"] - restricting_sid = preflight["windows_restricting_sid"] - broker_authentication_id = preflight["windows_broker_authentication_id"] - product_process_id = preflight["windows_product_process_id"] - product_primary_thread_id = preflight["windows_product_primary_thread_id"] - bootstrap_nonce = preflight["windows_bootstrap_config_nonce"] - expected_private_desktop = ( - f"cmuxb-{bootstrap_nonce[:24]}\\desk-{bootstrap_nonce[24:48]}" - if isinstance(bootstrap_nonce, str) - and re.fullmatch(r"[0-9a-fA-F]{64}", bootstrap_nonce) is not None - else None - ) - if ( - preflight["windows_bootstrap_sha256"] != bootstrap_sha256 - or expected_private_desktop is None - or preflight["windows_bootstrap_config_consumed"] is not True - or preflight["windows_bootstrap_resume_previous_count"] != 1 - or not isinstance(ready_elapsed_ms, int) - or isinstance(ready_elapsed_ms, bool) - or not 0 <= ready_elapsed_ms <= 30_000 - or preflight["windows_bootstrap_exact_job"] is not True - or preflight["windows_grandchild_in_job"] is not True - or preflight["windows_active_process_zero"] is not True - or preflight["windows_caller_se_impersonate_enabled"] is not True - or preflight["windows_standard_handles_valid"] is not True - or preflight["windows_explicit_handle_list"] is not True - or preflight["windows_bootstrap_trusted_path_write_denied"] is not True - or preflight["windows_bootstrap_self_write_denied"] is not True - or not isinstance(restricting_sid, str) - or len(restricting_sid) > 184 - or re.fullmatch(r"S-1(?:-\d+)+", restricting_sid) is None - or not isinstance(broker_authentication_id, str) - or re.fullmatch(r"[0-9a-fA-F]{16}", broker_authentication_id) is None - or preflight["windows_restricted_authentication_id"] - != broker_authentication_id - or preflight["windows_product_authentication_id"] - != broker_authentication_id - or preflight["windows_restricted_authentication_matches_broker"] is not True - or preflight["windows_product_authentication_matches_broker"] is not True - or preflight["windows_se_increase_quota_present"] is not True - or preflight["windows_se_increase_quota_enabled"] is not True - or preflight["windows_create_process_as_user_succeeded"] is not True - or preflight["windows_restricted_token_write_restricted"] is not True - or preflight["windows_restricted_token_low_integrity"] is not True - or preflight["windows_restricted_token_no_enabled_privileges"] is not True - or preflight["windows_restricted_token_restricting_sid_match"] is not True - or preflight["windows_product_write_restricted"] is not True - or preflight["windows_product_low_integrity"] is not True - or preflight["windows_product_no_enabled_privileges"] is not True - or preflight["windows_product_restricting_sid_match"] is not True - or preflight["windows_product_exact_job"] is not True - or preflight["windows_product_resume_previous_count"] != 1 - or not isinstance(product_process_id, int) - or isinstance(product_process_id, bool) - or product_process_id <= 0 - or not isinstance(product_primary_thread_id, int) - or isinstance(product_primary_thread_id, bool) - or product_primary_thread_id <= 0 - or preflight["windows_private_desktop"] != expected_private_desktop - or preflight["windows_private_window_station_created"] is not True - or preflight["windows_private_desktop_created"] is not True - or preflight["windows_private_desktop_broker_assigned"] is not True - or preflight["windows_private_desktop_product_assigned"] is not True - or preflight["windows_private_desktop_closed_after_job_empty"] is not True - ): - raise SystemExit("sandbox preflight has invalid native Windows bootstrap proof") -elif any(get(dotted) is not None for dotted in windows_bootstrap_fields): - raise SystemExit("non-Windows evidence contains Windows bootstrap identity") -elif any(preflight[field] is not None for field in windows_preflight_fields): - raise SystemExit("non-Windows preflight contains Windows bootstrap proof") + raise SystemExit("benchmark evidence has invalid Windows bootstrap identity") + preflight = load_json_object(path.parent / "sandbox-preflight.json", "sandbox preflight evidence") + if preflight.get("windows_bootstrap_sha256") != bootstrap_sha256: + raise SystemExit("sandbox preflight and report identify different bootstraps") + validate_windows_bootstrap_attestation(path.parent, preflight) +else: + for dotted in WINDOWS_BOOTSTRAP_REPORT_FIELDS: + if get(dotted) is not None: + raise SystemExit("non-Windows evidence contains Windows bootstrap identity") expected_sandbox_contract = { "infrastructure.sandbox_policy": "fixture-root-only-write", "infrastructure.sandbox_handshake": "nonce-bound-ready-arm-with-pre-exec-t0", From 7a36ba02d8a2b64e8097fd4de61337cd89a51b52 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:36:52 -0700 Subject: [PATCH 13/23] fix: link preflight claims to attested inputs --- cmux-tui/scripts/verify-startup-benchmark.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmux-tui/scripts/verify-startup-benchmark.py b/cmux-tui/scripts/verify-startup-benchmark.py index 3aefa2c1005f..9fc6e4ba8fdf 100644 --- a/cmux-tui/scripts/verify-startup-benchmark.py +++ b/cmux-tui/scripts/verify-startup-benchmark.py @@ -130,7 +130,8 @@ def validate_windows_bootstrap_attestation( expected_bootstrap_path is not None and ( not isinstance(expected_bootstrap_path, str) - or pathlib.Path(expected_bootstrap_path).resolve() != bootstrap_path.resolve() + or os.path.normcase(os.path.abspath(expected_bootstrap_path)) + != os.path.normcase(os.path.abspath(path_value)) ) ): raise SystemExit("startup report is not linked to the attested Windows bootstrap") @@ -204,9 +205,14 @@ def validate_report_preflight( preflight_path = artifact_root / "sandbox-preflight.json" preflight = load_json_object(preflight_path, "sandbox preflight evidence") if ( - preflight.get("policy") != infrastructure.get("sandbox_policy") + preflight.get("backend") != infrastructure.get("sandbox_backend") + or preflight.get("policy") != infrastructure.get("sandbox_policy") or preflight.get("handshake") != infrastructure.get("sandbox_handshake") or preflight.get("cleanup") != infrastructure.get("sandbox_cleanup") + or infrastructure.get("expected_supervisor_sha256") + != expected_supervisor_sha256 + or infrastructure.get("expected_preflight_sha256") + != infrastructure.get("preflight_sha256") or preflight.get("supervisor_sha256") != expected_supervisor_sha256 or file_sha256(preflight_path) != infrastructure.get("preflight_sha256") ): From 2db91e15eac73be3f2b5783bf94458e283d0e0c4 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:38:27 -0700 Subject: [PATCH 14/23] test: keep missing grandchild claims unverified --- .../scripts/test_startup_benchmark_claim.py | 7 +++++++ .../test_startup_benchmark_contract.py | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index e5b2a931b63f..96280d833f3d 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -263,6 +263,13 @@ def test_unavailable_only_windows_claim_is_accepted_as_skipped(self) -> None: ) self.assertEqual(optional_result.returncode, 0, optional_result.stderr) + grandchild_only_result = self._run_verifier( + runner_os="Windows", grandchild_value=None, unsupported_value=True + ) + self.assertEqual( + grandchild_only_result.returncode, 0, grandchild_only_result.stderr + ) + def test_false_windows_observation_is_rejected(self) -> None: result = self._run_verifier(runner_os="Windows", unsupported_value=False) diff --git a/cmux-tui/scripts/test_startup_benchmark_contract.py b/cmux-tui/scripts/test_startup_benchmark_contract.py index a17eb42b12e8..b448358243e8 100644 --- a/cmux-tui/scripts/test_startup_benchmark_contract.py +++ b/cmux-tui/scripts/test_startup_benchmark_contract.py @@ -196,6 +196,27 @@ def test_unavailable_only_windows_fields_are_skippable_but_not_verified(self) -> allow_unverified_windows=False, ) + def test_grandchild_only_unavailable_is_not_verified(self) -> None: + evidence = windows_evidence() + evidence["windows_grandchild_in_job"] = None + for field in UNAVAILABLE_FIELDS: + evidence[field] = True + + self.assertEqual( + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=True, + ), + "unverified", + ) + with self.assertRaises(ValueError): + CONTRACT.validate_preflight_evidence( + evidence, + expected_supervisor_sha256="c" * 64, + allow_unverified_windows=False, + ) + def test_common_or_observed_false_proofs_reject_skipped_and_normal_paths(self) -> None: for field in ( *CORE_FIELDS, From 3c414bea736ed157d908be4481ad100f960e3634 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:38:43 -0700 Subject: [PATCH 15/23] fix: require observed Windows child membership for verification --- cmux-tui/scripts/startup_benchmark_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmux-tui/scripts/startup_benchmark_contract.py b/cmux-tui/scripts/startup_benchmark_contract.py index 9e6b026ebdf5..4816ad636371 100644 --- a/cmux-tui/scripts/startup_benchmark_contract.py +++ b/cmux-tui/scripts/startup_benchmark_contract.py @@ -346,7 +346,7 @@ def _validate_windows(evidence: dict, allow_unverified_windows: bool) -> str: "Windows private desktop evidence is invalid", ) - if all(value is True for value in unavailable): + if grandchild is True and all(value is True for value in unavailable): return PREFLIGHT_STATUS_VERIFIED if not allow_unverified_windows: raise ValueError("Windows native observations are unavailable") From e914371dcc4488e8fbf3717d3c9732dc59efb420 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:48:03 -0700 Subject: [PATCH 16/23] style: apply hosted rustfmt to startup benchmark tests --- .../examples/startup_benchmark_preflight.rs | 14 +++----------- .../examples/startup_benchmark_support/args.rs | 13 ++----------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs index d72f45624c81..b4287c228e85 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_preflight.rs @@ -166,9 +166,7 @@ struct WindowsPreflightObservations { explicit_handle_list: Option, } -fn windows_preflight_observations( - grandchild_in_job: Option, -) -> WindowsPreflightObservations { +fn windows_preflight_observations(grandchild_in_job: Option) -> WindowsPreflightObservations { // Keep only observations produced by a probe. Do not infer process, privilege, or handle // state from supervisor exit status or from the fact that this code runs on Windows. WindowsPreflightObservations { @@ -2339,10 +2337,7 @@ mod tests { let unique = format!( "cmux-preflight-cleanup-{}-{}", std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() ); let parent = std::env::temp_dir().join(unique); let root = parent.join("preflight-root"); @@ -2366,10 +2361,7 @@ mod tests { let unique = format!( "cmux-preflight-guard-{}-{}", std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() ); let parent = std::env::temp_dir().join(unique); let root = parent.join("preflight-root"); diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/args.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/args.rs index 06d89f27dc7a..8f8f9b91cc07 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/args.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_support/args.rs @@ -361,12 +361,7 @@ mod tests { let baseline_source = root.join("baseline-source"); let candidate_source = root.join("candidate-source"); let fixture_parent = root.join("fixtures"); - for directory in [ - &trusted_source, - &baseline_source, - &candidate_source, - &fixture_parent, - ] { + for directory in [&trusted_source, &baseline_source, &candidate_source, &fixture_parent] { fs::create_dir(directory)?; } let supervisor_binary = root.join("supervisor"); @@ -394,11 +389,7 @@ mod tests { } else { PathBuf::new() }, - windows_bootstrap_sha256: if cfg!(windows) { - "a".repeat(64) - } else { - String::new() - }, + windows_bootstrap_sha256: if cfg!(windows) { "a".repeat(64) } else { String::new() }, sandbox_backend: expected_sandbox_backend().to_string(), sandbox_preflight, sandbox_preflight_sha256: "a".repeat(64), From 55628cf6b6dde5f25d69f4d1f97660ded401b86f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:09:02 -0700 Subject: [PATCH 17/23] test: reject unlinked Windows AppContainer skips --- .../scripts/test_startup_benchmark_claim.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index 96280d833f3d..1808aec5455a 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -33,6 +33,55 @@ class SkippedClaimTests(unittest.TestCase): + @staticmethod + def _write_appcontainer_evidence( + output_dir: Path, + *, + runner_readable=True, + runner_hash="d" * 64, + expected_hash="d" * 64, + staging_acl_attested=True, + fixture_acl_attested=True, + account_readable=False, + account_error_code=5, + restricted_token_run_started=False, + ) -> None: + (output_dir / "windows-appcontainer-feasibility.json").write_text( + json.dumps( + { + "schema_version": 1, + "status": "unavailable", + "backend": "windows-appcontainer-feasibility", + "nonce": "e" * 64, + "stage": "staging-readability", + "reason": ( + "dedicated Windows account could not read the nonce-bound staged " + "target before the restricted-token broker started" + ), + "runner_staged_target_readable": runner_readable, + "runner_staged_target_sha256": runner_hash, + "expected_staged_target_sha256": expected_hash, + "staging_creation_acl_applied": staging_acl_attested, + "fixture_creation_acl_applied": fixture_acl_attested, + "staged_target_regular_file": True, + "account_staged_target_readable": account_readable, + "account_staged_target_error_code": account_error_code, + "restricted_token_run_started": restricted_token_run_started, + "profile_deleted": True, + "account_profile_unloaded": True, + "adjacent_sentinel_deleted": True, + "staging_directory_deleted": True, + "fixture_directory_deleted": True, + "preexisting_parent_before_sha256": "f" * 64, + "preexisting_parent_after_sha256": "f" * 64, + "preexisting_parent_unchanged": True, + }, + sort_keys=True, + ) + + "\n", + encoding="utf-8", + ) + @staticmethod def _write_preflight( output_dir: Path, @@ -161,6 +210,7 @@ def _run_verifier( grandchild_value=True, tamper_bootstrap=False, tamper_imports=False, + appcontainer_evidence=None, ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as temporary: output_dir = Path(temporary) @@ -204,6 +254,13 @@ def _run_verifier( fixture_parent_name="cbp-test", report=report, ) + if appcontainer_evidence is None: + self._write_appcontainer_evidence(output_dir) + else: + (output_dir / "windows-appcontainer-feasibility.json").write_text( + json.dumps(appcontainer_evidence, sort_keys=True) + "\n", + encoding="utf-8", + ) environment = os.environ.copy() environment.update( { @@ -313,6 +370,60 @@ def test_bootstrap_hash_or_imports_are_required_for_skips(self) -> None: result = self._run_verifier(runner_os="Windows", **{option: True}) self.assertNotEqual(result.returncode, 0) + def test_generic_appcontainer_access_denied_is_not_an_unavailable_claim(self) -> None: + result = self._run_verifier( + runner_os="Windows", + appcontainer_evidence={ + "schema_version": 4, + "nonce": "e" * 64, + "stage": "config-validate", + "error": "Access is denied. (os error 5)", + }, + ) + self.assertNotEqual(result.returncode, 0) + + def test_unavailable_appcontainer_claim_requires_runner_and_cleanup_attestation(self) -> None: + for changes in ( + {"runner_staged_target_readable": False}, + {"runner_staged_target_sha256": "0" * 64}, + {"staging_creation_acl_applied": False}, + {"fixture_creation_acl_applied": False}, + {"account_staged_target_error_code": None}, + {"account_staged_target_error_code": 5, "restricted_token_run_started": True}, + {"preexisting_parent_unchanged": False}, + ): + with self.subTest(changes=changes): + evidence = { + "schema_version": 1, + "status": "unavailable", + "backend": "windows-appcontainer-feasibility", + "nonce": "e" * 64, + "stage": "staging-readability", + "reason": "staged target was unavailable to the dedicated account", + "runner_staged_target_readable": True, + "runner_staged_target_sha256": "d" * 64, + "expected_staged_target_sha256": "d" * 64, + "staging_creation_acl_applied": True, + "fixture_creation_acl_applied": True, + "staged_target_regular_file": True, + "account_staged_target_readable": False, + "account_staged_target_error_code": 5, + "restricted_token_run_started": False, + "profile_deleted": True, + "account_profile_unloaded": True, + "adjacent_sentinel_deleted": True, + "staging_directory_deleted": True, + "fixture_directory_deleted": True, + "preexisting_parent_before_sha256": "f" * 64, + "preexisting_parent_after_sha256": "f" * 64, + "preexisting_parent_unchanged": True, + } + evidence.update(changes) + result = self._run_verifier( + runner_os="Windows", appcontainer_evidence=evidence + ) + self.assertNotEqual(result.returncode, 0) + def test_skipped_claim_is_rejected_on_linux_and_macos(self) -> None: for runner_os in ("Linux", "macOS"): with self.subTest(runner_os=runner_os): From 3107dd2535c729b11a00c25b574d3e364abd453c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:15:10 -0700 Subject: [PATCH 18/23] fix: classify Windows staging capability failures explicitly --- .../workflows/cmux-tui-startup-benchmark.yml | 30 +- .github/workflows/cmux-tui.yml | 40 ++- .../startup_benchmark_appcontainer.rs | 329 +++++++++++++++++- cmux-tui/scripts/verify-startup-benchmark.py | 109 +++++- 4 files changed, 487 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cmux-tui-startup-benchmark.yml b/.github/workflows/cmux-tui-startup-benchmark.yml index 03d649455ba1..8cea9ed0536d 100644 --- a/.github/workflows/cmux-tui-startup-benchmark.yml +++ b/.github/workflows/cmux-tui-startup-benchmark.yml @@ -1271,12 +1271,24 @@ jobs: claim_reason="" if [[ "$RUNNER_OS" == "Windows" ]]; then appcontainer_evidence="$ARTIFACT_DIR/windows-appcontainer-feasibility.json" - "$preflight" \ - --appcontainer-feasibility \ - --fixture-parent "$PREFLIGHT_PARENT" \ - --output "$appcontainer_evidence" - "$PYTHON_CMD" "$TRUSTED_SOURCE/cmux-tui/scripts/verify-startup-benchmark.py" \ - --appcontainer-feasibility "$appcontainer_evidence" + if "$preflight" \ + --appcontainer-feasibility \ + --fixture-parent "$PREFLIGHT_PARENT" \ + --output "$appcontainer_evidence"; then + "$PYTHON_CMD" "$TRUSTED_SOURCE/cmux-tui/scripts/verify-startup-benchmark.py" \ + --appcontainer-feasibility "$appcontainer_evidence" + else + appcontainer_status=$? + if [[ "$appcontainer_status" -ne 78 ]]; then + echo "trusted Windows AppContainer feasibility failed with exit $appcontainer_status" >&2 + exit "$appcontainer_status" + fi + "$PYTHON_CMD" "$TRUSTED_SOURCE/cmux-tui/scripts/verify-startup-benchmark.py" \ + --appcontainer-feasibility "$appcontainer_evidence" + claim_status="unverified" + claim_reason="Windows AppContainer staging-readability capability was unavailable; no restricted-token broker run was started" + printf '%s\n' "$claim_reason" > "$ARTIFACT_DIR/windows-appcontainer-unavailable.txt" + fi if run_preflight "$evidence"; then : else @@ -1285,8 +1297,10 @@ jobs: echo "trusted Windows preflight failed with exit $preflight_status" >&2 exit "$preflight_status" fi - claim_status="unverified" - claim_reason="trusted Windows preflight could not observe all required native security signals" + if [[ "$claim_status" != "unverified" ]]; then + claim_status="unverified" + claim_reason="trusted Windows preflight could not observe all required native security signals" + fi { printf '%s\n' "$claim_reason" } >> "$ARTIFACT_DIR/windows-preflight-unverified.txt" diff --git a/.github/workflows/cmux-tui.yml b/.github/workflows/cmux-tui.yml index 34532bbbc09e..18bd69e72616 100644 --- a/.github/workflows/cmux-tui.yml +++ b/.github/workflows/cmux-tui.yml @@ -902,10 +902,23 @@ jobs: evidence="$RUNNER_TEMP/startup-containment/preflight.json" appcontainer_evidence="$RUNNER_TEMP/startup-containment/windows-appcontainer-feasibility.json" test -f "$CMUX_BENCH_TEST_WINDOWS_BOOTSTRAP" - "$preflight" \ - --appcontainer-feasibility \ - --fixture-parent "$CMUX_BENCH_TEST_FIXTURE_PARENT" \ - --output "$appcontainer_evidence" + appcontainer_unavailable=0 + if "$preflight" \ + --appcontainer-feasibility \ + --fixture-parent "$CMUX_BENCH_TEST_FIXTURE_PARENT" \ + --output "$appcontainer_evidence"; then + python3 scripts/verify-startup-benchmark.py \ + --appcontainer-feasibility "$appcontainer_evidence" + else + appcontainer_status=$? + if [[ "$appcontainer_status" -ne 78 ]]; then + echo "Windows AppContainer feasibility failed with exit $appcontainer_status" >&2 + exit "$appcontainer_status" + fi + python3 scripts/verify-startup-benchmark.py \ + --appcontainer-feasibility "$appcontainer_evidence" + appcontainer_unavailable=1 + fi test -s "$appcontainer_evidence" if "$preflight" \ --supervisor "$supervisor" \ @@ -914,7 +927,13 @@ jobs: --fixture-parent "$CMUX_BENCH_TEST_FIXTURE_PARENT" \ --output "$evidence" \ --backend windows-restricted-token-job; then - printf 'claim_status=verified\n' >> "$GITHUB_OUTPUT" + if [[ "$appcontainer_unavailable" -eq 1 ]]; then + printf 'claim_status=unverified\n' >> "$GITHUB_OUTPUT" + printf '%s\n' 'Windows AppContainer staging-readability capability was unavailable; no restricted-token broker run was started' \ + > "$RUNNER_TEMP/startup-containment/windows-preflight-unverified.txt" + else + printf 'claim_status=verified\n' >> "$GITHUB_OUTPUT" + fi else preflight_status=$? if [[ "$preflight_status" -ne 78 ]]; then @@ -922,8 +941,13 @@ jobs: exit "$preflight_status" fi printf 'claim_status=unverified\n' >> "$GITHUB_OUTPUT" - printf '%s\n' 'trusted Windows preflight could not observe all required native security signals' \ - > "$RUNNER_TEMP/startup-containment/windows-preflight-unverified.txt" + if [[ "$appcontainer_unavailable" -eq 1 ]]; then + printf '%s\n' 'Windows AppContainer staging-readability capability was unavailable; no restricted-token broker run was started' \ + > "$RUNNER_TEMP/startup-containment/windows-preflight-unverified.txt" + else + printf '%s\n' 'trusted Windows preflight could not observe all required native security signals' \ + > "$RUNNER_TEMP/startup-containment/windows-preflight-unverified.txt" + fi printf '%s\n' 'Windows startup containment claim skipped: required native security signals were unavailable.' \ >> "$GITHUB_STEP_SUMMARY" fi @@ -940,6 +964,7 @@ jobs: ${{ runner.temp }}/startup-containment/*-bootstrap-hang.json ${{ runner.temp }}/startup-containment/*-bootstrap-hang.dmp ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility-failure.json + ${{ runner.temp }}/startup-containment/windows-appcontainer-unavailable.txt if-no-files-found: warn retention-days: 7 @@ -951,6 +976,7 @@ jobs: path: | ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility.json ${{ runner.temp }}/startup-containment/windows-appcontainer-feasibility-failure.json + ${{ runner.temp }}/startup-containment/windows-appcontainer-unavailable.txt ${{ runner.temp }}/startup-containment/windows-preflight-unverified.txt if-no-files-found: warn retention-days: 7 diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs index e54066598aa5..0a64548f90aa 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs @@ -23,8 +23,8 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use windows_sys::Win32::Foundation::{ CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_SUCCESS, GENERIC_WRITE, HANDLE, - HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, LocalFree, SetHandleInformation, WAIT_OBJECT_0, - WAIT_TIMEOUT, + ERROR_ACCESS_DENIED, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, LocalFree, + SetHandleInformation, WAIT_OBJECT_0, WAIT_TIMEOUT, }; use windows_sys::Win32::NetworkManagement::WindowsFirewall::{ INET_FIREWALL_APP_CONTAINER, NetworkIsolationEnumAppContainers, @@ -85,6 +85,11 @@ const BROKER_TIMEOUT: Duration = Duration::from_secs(50); const PRODUCT_TIMEOUT: Duration = Duration::from_secs(30); const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); const JOB_COMPLETION_KEY: usize = 0x434d_5558; +const APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION: u32 = 1; +const APP_CONTAINER_UNAVAILABLE_STATUS: &str = "unavailable"; +const APP_CONTAINER_UNAVAILABLE_STAGE: &str = "staging-readability"; +const APP_CONTAINER_UNAVAILABLE_REASON: &str = + "dedicated Windows account could not read the nonce-bound staged target before the restricted-token broker started"; #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] @@ -297,6 +302,123 @@ struct FeasibilityEvidence { preexisting_parent_unchanged: bool, } +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct AppContainerUnavailableEvidence { + schema_version: u32, + status: &'static str, + backend: &'static str, + nonce: String, + stage: &'static str, + reason: String, + runner_staged_target_readable: bool, + runner_staged_target_sha256: String, + expected_staged_target_sha256: String, + staging_creation_acl_applied: bool, + fixture_creation_acl_applied: bool, + staged_target_regular_file: bool, + account_staged_target_readable: bool, + account_staged_target_error_code: u32, + restricted_token_run_started: bool, + profile_deleted: bool, + account_profile_unloaded: bool, + adjacent_sentinel_deleted: bool, + staging_directory_deleted: bool, + fixture_directory_deleted: bool, + preexisting_parent_before_sha256: String, + preexisting_parent_after_sha256: String, + preexisting_parent_unchanged: bool, +} + +impl AppContainerUnavailableEvidence { + fn validate(&self) -> Result<()> { + if self.schema_version != APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION + || self.status != APP_CONTAINER_UNAVAILABLE_STATUS + || self.backend != "windows-appcontainer-feasibility" + || self.stage != APP_CONTAINER_UNAVAILABLE_STAGE + || self.nonce.len() != 64 + || !self.nonce.bytes().all(|byte| byte.is_ascii_hexdigit()) + || !self.reason.starts_with(APP_CONTAINER_UNAVAILABLE_REASON) + || self.reason.len() > 4096 + || !self.runner_staged_target_readable + || self.runner_staged_target_sha256 != self.expected_staged_target_sha256 + || !self.staging_creation_acl_applied + || !self.fixture_creation_acl_applied + || !self.staged_target_regular_file + || self.account_staged_target_readable + || self.account_staged_target_error_code != ERROR_ACCESS_DENIED + || self.restricted_token_run_started + || !self.profile_deleted + || !self.account_profile_unloaded + || !self.adjacent_sentinel_deleted + || !self.staging_directory_deleted + || !self.fixture_directory_deleted + || !self.preexisting_parent_unchanged + || self.preexisting_parent_before_sha256 != self.preexisting_parent_after_sha256 + { + bail!("Windows AppContainer unavailable evidence is incomplete"); + } + validate_sha256(&self.runner_staged_target_sha256, "runner staged target")?; + validate_sha256(&self.expected_staged_target_sha256, "expected staged target")?; + validate_sha256( + &self.preexisting_parent_before_sha256, + "pre-existing parent before descriptor", + )?; + validate_sha256( + &self.preexisting_parent_after_sha256, + "pre-existing parent after descriptor", + )?; + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum StagedTargetReadability { + Ready, + Unavailable, + Invalid, +} + +fn classify_staged_target_readability( + runner_staged_target_readable: bool, + runner_hash_matches: bool, + staging_acl_attested: bool, + fixture_acl_attested: bool, + staged_target_regular_file: bool, + account_staged_target_readable: bool, + account_hash_matches: bool, + account_error_code: Option, +) -> StagedTargetReadability { + if !runner_staged_target_readable + || !runner_hash_matches + || !staging_acl_attested + || !fixture_acl_attested + || !staged_target_regular_file + { + return StagedTargetReadability::Invalid; + } + if account_staged_target_readable { + return if account_hash_matches { + StagedTargetReadability::Ready + } else { + StagedTargetReadability::Invalid + }; + } + if account_error_code == Some(ERROR_ACCESS_DENIED as i32) { + StagedTargetReadability::Unavailable + } else { + StagedTargetReadability::Invalid + } +} + +fn raw_os_error(error: &anyhow::Error) -> Option { + error.chain().find_map(|cause| { + cause + .downcast_ref::() + .and_then(io::Error::raw_os_error) + }) +} + impl FeasibilityEvidence { fn validate(&self) -> Result<()> { let product = &self.broker.product; @@ -443,7 +565,69 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { bail!("staged AppContainer probe hash changed"); } fs::write(&adjacent, b"protected").context("create AppContainer adjacent sentinel")?; - + let staging_creation_acl_applied = !file_security( + staging.path(), + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + )? + .is_empty(); + let fixture_creation_acl_applied = !file_security( + fixture.path(), + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + )? + .is_empty(); + let staged_target_regular_file = { + let metadata = fs::symlink_metadata(&staged_target)?; + metadata.file_type().is_file() && !metadata.file_type().is_symlink() + }; + let account_readability = account.impersonate(|_| sha256_file(&staged_target)); + let (account_staged_target_readable, account_hash_matches, account_error_code) = + match &account_readability { + Ok(observed_hash) => (true, observed_hash == &staged_probe_sha256, None), + Err(error) => (false, false, raw_os_error(error)), + }; + let staging_readability = classify_staged_target_readability( + true, + staged_probe_sha256 == target_sha256, + staging_creation_acl_applied, + fixture_creation_acl_applied, + staged_target_regular_file, + account_staged_target_readable, + account_hash_matches, + account_error_code, + ); + if staging_readability == StagedTargetReadability::Unavailable { + let account_error = account_readability + .as_ref() + .err() + .expect("unavailable staging readability has an account error"); + let evidence = cleanup_unavailable_appcontainer( + &mut account, + &mut profile, + &adjacent, + &mut staging, + &mut fixture, + &fixture_parent, + parent_security_information, + &parent_security_before, + nonce, + staged_probe_sha256.clone(), + target_sha256, + staging_creation_acl_applied, + fixture_creation_acl_applied, + staged_target_regular_file, + u32::try_from(account_error_code.expect("access-denied error code is present"))?, + account_error, + )?; + write_new_json(&output, &evidence)?; + return Err(super::WindowsClaimUnavailable.into()); + } + if staging_readability != StagedTargetReadability::Ready { + let error = account_readability + .err() + .map(|error| format!("{error:#}")) + .unwrap_or_else(|| "account staged-target hash did not match".into()); + bail!("AppContainer staged-target readability proof failed: {error}"); + } let config = BrokerConfig { schema_version: EVIDENCE_SCHEMA_VERSION, nonce: nonce.clone(), @@ -1194,6 +1378,78 @@ impl OwnedNonceDirectory { } } +fn cleanup_unavailable_appcontainer( + account: &mut AccountProfile, + profile: &mut AppContainerProfile, + adjacent: &Path, + staging: &mut OwnedNonceDirectory, + fixture: &mut OwnedNonceDirectory, + fixture_parent: &Path, + parent_security_information: u32, + parent_security_before: &[usize], + nonce: String, + runner_staged_target_sha256: String, + expected_staged_target_sha256: String, + staging_creation_acl_applied: bool, + fixture_creation_acl_applied: bool, + staged_target_regular_file: bool, + account_staged_target_error_code: u32, + account_staged_target_error: &anyhow::Error, +) -> Result { + account.impersonate(|_| profile.delete())?; + let profile_deleted = profile.deleted; + if !profile_deleted || profile.folder.exists() { + bail!("AppContainer unavailable cleanup did not delete the profile"); + } + + fs::remove_file(adjacent).context("remove AppContainer unavailable adjacent sentinel")?; + let adjacent_sentinel_deleted = !adjacent.exists(); + fixture.remove()?; + staging.remove()?; + let fixture_directory_deleted = !fixture.path.exists(); + let staging_directory_deleted = !staging.path.exists(); + + account.unload()?; + let account_profile_unloaded = account.profile.is_null(); + if !account_profile_unloaded { + bail!("AppContainer unavailable cleanup did not unload the account profile"); + } + + let parent_security_after = file_security(fixture_parent, parent_security_information)?; + let preexisting_parent_unchanged = parent_security_before == parent_security_after; + if !preexisting_parent_unchanged { + bail!("AppContainer unavailable cleanup changed the pre-existing parent descriptor"); + } + + let evidence = AppContainerUnavailableEvidence { + schema_version: APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION, + status: APP_CONTAINER_UNAVAILABLE_STATUS, + backend: "windows-appcontainer-feasibility", + nonce, + stage: APP_CONTAINER_UNAVAILABLE_STAGE, + reason: format!("{APP_CONTAINER_UNAVAILABLE_REASON}: {account_staged_target_error:#}"), + runner_staged_target_readable: true, + runner_staged_target_sha256, + expected_staged_target_sha256, + staging_creation_acl_applied, + fixture_creation_acl_applied, + staged_target_regular_file, + account_staged_target_readable: false, + account_staged_target_error_code, + restricted_token_run_started: false, + profile_deleted, + account_profile_unloaded, + adjacent_sentinel_deleted, + staging_directory_deleted, + fixture_directory_deleted, + preexisting_parent_before_sha256: hash_words(parent_security_before), + preexisting_parent_after_sha256: hash_words(&parent_security_after), + preexisting_parent_unchanged, + }; + evidence.validate()?; + Ok(evidence) +} + fn run_account_broker( token: HANDLE, executable: &Path, @@ -2638,6 +2894,73 @@ fn result_label(result: &Result) -> String { mod tests { use super::*; + #[test] + fn staging_readability_requires_runner_and_acl_attestation_before_unavailable() { + assert_eq!( + classify_staged_target_readability( + true, + true, + true, + true, + true, + false, + false, + Some(ERROR_ACCESS_DENIED as i32), + ), + StagedTargetReadability::Unavailable + ); + for (runner_readable, runner_hash_matches, staging_acl, fixture_acl, regular) in [ + (false, true, true, true, true), + (true, false, true, true, true), + (true, true, false, true, true), + (true, true, true, false, true), + (true, true, true, true, false), + ] { + assert_eq!( + classify_staged_target_readability( + runner_readable, + runner_hash_matches, + staging_acl, + fixture_acl, + regular, + false, + false, + Some(ERROR_ACCESS_DENIED as i32), + ), + StagedTargetReadability::Invalid + ); + } + } + + #[test] + fn staging_readability_rejects_generic_access_denied_and_hash_mismatch() { + assert_eq!( + classify_staged_target_readability( + true, true, true, true, true, false, false, None, + ), + StagedTargetReadability::Invalid + ); + assert_eq!( + classify_staged_target_readability( + true, + true, + true, + true, + true, + true, + false, + None, + ), + StagedTargetReadability::Invalid + ); + assert_eq!( + classify_staged_target_readability( + true, true, true, true, true, true, true, None, + ), + StagedTargetReadability::Ready + ); + } + #[test] fn profile_name_is_nonce_bound_and_bounded() { let nonce = "12".repeat(32); diff --git a/cmux-tui/scripts/verify-startup-benchmark.py b/cmux-tui/scripts/verify-startup-benchmark.py index 9fc6e4ba8fdf..51fb8fa013ed 100644 --- a/cmux-tui/scripts/verify-startup-benchmark.py +++ b/cmux-tui/scripts/verify-startup-benchmark.py @@ -34,6 +34,32 @@ "infrastructure.windows_bootstrap_sha256", "infrastructure.windows_bootstrap_bytes", ) +APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION = 1 +APP_CONTAINER_UNAVAILABLE_FIELDS = { + "schema_version", + "status", + "backend", + "nonce", + "stage", + "reason", + "runner_staged_target_readable", + "runner_staged_target_sha256", + "expected_staged_target_sha256", + "staging_creation_acl_applied", + "fixture_creation_acl_applied", + "staged_target_regular_file", + "account_staged_target_readable", + "account_staged_target_error_code", + "restricted_token_run_started", + "profile_deleted", + "account_profile_unloaded", + "adjacent_sentinel_deleted", + "staging_directory_deleted", + "fixture_directory_deleted", + "preexisting_parent_before_sha256", + "preexisting_parent_after_sha256", + "preexisting_parent_unchanged", +} def require_full_sha(value, label): @@ -418,6 +444,13 @@ def validate_skipped_report(document, artifact_root): if os.environ.get("RUNNER_OS") not in (None, "Windows"): raise SystemExit("skipped startup report is only valid on Windows") + appcontainer_status = validate_appcontainer_feasibility( + artifact_root / "windows-appcontainer-feasibility.json", + allow_unavailable=True, + ) + if appcontainer_status != "unavailable": + raise SystemExit("skipped report must contain unavailable AppContainer evidence") + claim_status = validate_report_preflight( document, artifact_root, @@ -724,8 +757,77 @@ def close_artifact(artifact_root): validate_artifact_manifest(artifact_root) -def validate_appcontainer_feasibility(path): - evidence = json.loads(path.read_text(encoding="utf-8")) +def validate_appcontainer_feasibility_unavailable(evidence): + require_exact_object( + evidence, + APP_CONTAINER_UNAVAILABLE_FIELDS, + "AppContainer unavailable evidence", + ) + nonce = evidence["nonce"] + reason = evidence["reason"] + if ( + evidence["schema_version"] != APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION + or evidence["status"] != "unavailable" + or evidence["backend"] != "windows-appcontainer-feasibility" + or not isinstance(nonce, str) + or re.fullmatch(r"[0-9a-f]{64}", nonce) is None + or evidence["stage"] != "staging-readability" + or not isinstance(reason, str) + or not reason.startswith( + "dedicated Windows account could not read the nonce-bound staged target before the restricted-token broker started" + ) + or len(reason.encode("utf-8")) > 4096 + ): + raise SystemExit("AppContainer unavailable identity is invalid") + for field in ( + "runner_staged_target_sha256", + "expected_staged_target_sha256", + "preexisting_parent_before_sha256", + "preexisting_parent_after_sha256", + ): + if ( + not isinstance(evidence[field], str) + or FULL_SHA256_PATTERN.fullmatch(evidence[field]) is None + ): + raise SystemExit(f"AppContainer unavailable {field} is invalid") + if ( + evidence["runner_staged_target_sha256"] + != evidence["expected_staged_target_sha256"] + or evidence["preexisting_parent_before_sha256"] + != evidence["preexisting_parent_after_sha256"] + ): + raise SystemExit("AppContainer unavailable hashes do not match") + for field in ( + "runner_staged_target_readable", + "staging_creation_acl_applied", + "fixture_creation_acl_applied", + "staged_target_regular_file", + "profile_deleted", + "account_profile_unloaded", + "adjacent_sentinel_deleted", + "staging_directory_deleted", + "fixture_directory_deleted", + "preexisting_parent_unchanged", + ): + if evidence[field] is not True: + raise SystemExit(f"AppContainer unavailable proof is false: {field}") + if ( + evidence["account_staged_target_readable"] is not False + or evidence["restricted_token_run_started"] is not False + or not isinstance(evidence["account_staged_target_error_code"], int) + or isinstance(evidence["account_staged_target_error_code"], bool) + or evidence["account_staged_target_error_code"] != 5 + ): + raise SystemExit("AppContainer unavailable account probe is not an exact access denial") + + +def validate_appcontainer_feasibility(path, *, allow_unavailable=False): + evidence = load_json_object(path, "AppContainer feasibility evidence") + if evidence.get("status") == "unavailable": + if not allow_unavailable: + raise SystemExit("AppContainer unavailable evidence is not valid for a verified claim") + validate_appcontainer_feasibility_unavailable(evidence) + return "unavailable" require_exact_object( evidence, { @@ -1005,6 +1107,7 @@ def validate_appcontainer_feasibility(path): or parent_after != parent_before ): raise SystemExit("AppContainer nonce-owned path cleanup evidence is invalid") + return "verified" def validate_appcontainer_feasibility_failure(path): @@ -1036,7 +1139,7 @@ def validate_appcontainer_feasibility_failure(path): if len(sys.argv) == 3 and sys.argv[1] == "--appcontainer-feasibility": - validate_appcontainer_feasibility(pathlib.Path(sys.argv[2])) + validate_appcontainer_feasibility(pathlib.Path(sys.argv[2]), allow_unavailable=True) raise SystemExit(0) if len(sys.argv) == 3 and sys.argv[1] == "--appcontainer-feasibility-failure": validate_appcontainer_feasibility_failure(pathlib.Path(sys.argv[2])) From 3bc275c15c02137f82434dc4fd48d262f763885c Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:20:05 -0700 Subject: [PATCH 19/23] fix: attest account probe before Windows skip --- .../startup_benchmark_appcontainer.rs | 14 ++++++++++- .../scripts/test_startup_benchmark_claim.py | 23 ++++++++++++++++++- cmux-tui/scripts/verify-startup-benchmark.py | 11 ++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs index 0a64548f90aa..28112b44b8da 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs @@ -317,6 +317,7 @@ struct AppContainerUnavailableEvidence { staging_creation_acl_applied: bool, fixture_creation_acl_applied: bool, staged_target_regular_file: bool, + account_probe_impersonated: bool, account_staged_target_readable: bool, account_staged_target_error_code: u32, restricted_token_run_started: bool, @@ -345,6 +346,7 @@ impl AppContainerUnavailableEvidence { || !self.staging_creation_acl_applied || !self.fixture_creation_acl_applied || !self.staged_target_regular_file + || !self.account_probe_impersonated || self.account_staged_target_readable || self.account_staged_target_error_code != ERROR_ACCESS_DENIED || self.restricted_token_run_started @@ -385,6 +387,7 @@ fn classify_staged_target_readability( staging_acl_attested: bool, fixture_acl_attested: bool, staged_target_regular_file: bool, + account_probe_impersonated: bool, account_staged_target_readable: bool, account_hash_matches: bool, account_error_code: Option, @@ -394,6 +397,7 @@ fn classify_staged_target_readability( || !staging_acl_attested || !fixture_acl_attested || !staged_target_regular_file + || !account_probe_impersonated { return StagedTargetReadability::Invalid; } @@ -579,7 +583,11 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { let metadata = fs::symlink_metadata(&staged_target)?; metadata.file_type().is_file() && !metadata.file_type().is_symlink() }; - let account_readability = account.impersonate(|_| sha256_file(&staged_target)); + let mut account_probe_impersonated = false; + let account_readability = account.impersonate(|_| { + account_probe_impersonated = true; + sha256_file(&staged_target) + }); let (account_staged_target_readable, account_hash_matches, account_error_code) = match &account_readability { Ok(observed_hash) => (true, observed_hash == &staged_probe_sha256, None), @@ -591,6 +599,7 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { staging_creation_acl_applied, fixture_creation_acl_applied, staged_target_regular_file, + account_probe_impersonated, account_staged_target_readable, account_hash_matches, account_error_code, @@ -615,6 +624,7 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { staging_creation_acl_applied, fixture_creation_acl_applied, staged_target_regular_file, + account_probe_impersonated, u32::try_from(account_error_code.expect("access-denied error code is present"))?, account_error, )?; @@ -1393,6 +1403,7 @@ fn cleanup_unavailable_appcontainer( staging_creation_acl_applied: bool, fixture_creation_acl_applied: bool, staged_target_regular_file: bool, + account_probe_impersonated: bool, account_staged_target_error_code: u32, account_staged_target_error: &anyhow::Error, ) -> Result { @@ -1434,6 +1445,7 @@ fn cleanup_unavailable_appcontainer( staging_creation_acl_applied, fixture_creation_acl_applied, staged_target_regular_file, + account_probe_impersonated, account_staged_target_readable: false, account_staged_target_error_code, restricted_token_run_started: false, diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index 1808aec5455a..f90fdd4f0849 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -64,6 +64,7 @@ def _write_appcontainer_evidence( "staging_creation_acl_applied": staging_acl_attested, "fixture_creation_acl_applied": fixture_acl_attested, "staged_target_regular_file": True, + "account_probe_impersonated": True, "account_staged_target_readable": account_readable, "account_staged_target_error_code": account_error_code, "restricted_token_run_started": restricted_token_run_started, @@ -91,6 +92,7 @@ def _write_preflight( missing_field=None, bootstrap_value=b"trusted Windows bootstrap", grandchild_value=True, + all_windows_observed=False, ) -> str: nonce = "b" * 64 authentication_id = "0123456789abcdef" @@ -157,6 +159,9 @@ def _write_preflight( "supervisor_sha256": "c" * 64, } ) + if all_windows_observed: + for field in CONTRACT.WINDOWS_UNAVAILABLE_FIELDS: + preflight[field] = True if false_field is not None: preflight[false_field] = False if missing_field is not None: @@ -208,9 +213,11 @@ def _run_verifier( missing_field=None, bootstrap_value=b"trusted Windows bootstrap", grandchild_value=True, + all_windows_observed=False, tamper_bootstrap=False, tamper_imports=False, appcontainer_evidence=None, + skip_reason="required native Windows observations were unavailable", ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as temporary: output_dir = Path(temporary) @@ -221,6 +228,7 @@ def _run_verifier( missing_field=missing_field, bootstrap_value=bootstrap_value, grandchild_value=grandchild_value, + all_windows_observed=all_windows_observed, ) if tamper_bootstrap: (output_dir / "trusted-bootstrap.exe").write_bytes(b"tampered") @@ -247,7 +255,7 @@ def _run_verifier( candidate_sha=candidate_sha, supervisor_sha256=supervisor_sha256, preflight_sha256=preflight_sha256, - reason="required native Windows observations were unavailable", + reason=skip_reason, ) CLAIM.write_skipped_artifacts( output_dir=output_dir, @@ -382,6 +390,18 @@ def test_generic_appcontainer_access_denied_is_not_an_unavailable_claim(self) -> ) self.assertNotEqual(result.returncode, 0) + def test_appcontainer_unavailable_can_skip_after_common_preflight_is_verified(self) -> None: + result = self._run_verifier( + runner_os="Windows", + unsupported_value=True, + all_windows_observed=True, + skip_reason=( + "Windows AppContainer staging-readability capability was unavailable; " + "no restricted-token broker run was started" + ), + ) + self.assertEqual(result.returncode, 0, result.stderr) + def test_unavailable_appcontainer_claim_requires_runner_and_cleanup_attestation(self) -> None: for changes in ( {"runner_staged_target_readable": False}, @@ -406,6 +426,7 @@ def test_unavailable_appcontainer_claim_requires_runner_and_cleanup_attestation( "staging_creation_acl_applied": True, "fixture_creation_acl_applied": True, "staged_target_regular_file": True, + "account_probe_impersonated": True, "account_staged_target_readable": False, "account_staged_target_error_code": 5, "restricted_token_run_started": False, diff --git a/cmux-tui/scripts/verify-startup-benchmark.py b/cmux-tui/scripts/verify-startup-benchmark.py index 51fb8fa013ed..89b965b099e5 100644 --- a/cmux-tui/scripts/verify-startup-benchmark.py +++ b/cmux-tui/scripts/verify-startup-benchmark.py @@ -48,6 +48,7 @@ "staging_creation_acl_applied", "fixture_creation_acl_applied", "staged_target_regular_file", + "account_probe_impersonated", "account_staged_target_readable", "account_staged_target_error_code", "restricted_token_run_started", @@ -457,7 +458,14 @@ def validate_skipped_report(document, artifact_root): expected_supervisor_sha256=infrastructure["supervisor_sha256"], allow_unverified_windows=True, ) - if claim_status != PREFLIGHT_STATUS_UNVERIFIED: + if claim_status == PREFLIGHT_STATUS_VERIFIED: + if not reason.startswith( + "Windows AppContainer staging-readability capability was unavailable" + ): + raise SystemExit( + "a skipped report with verified common preflight needs an AppContainer skip reason" + ) + elif claim_status != PREFLIGHT_STATUS_UNVERIFIED: raise SystemExit("skipped report must contain unavailable Windows observations") markdown_path = artifact_root / "startup-benchmark.md" @@ -802,6 +810,7 @@ def validate_appcontainer_feasibility_unavailable(evidence): "staging_creation_acl_applied", "fixture_creation_acl_applied", "staged_target_regular_file", + "account_probe_impersonated", "profile_deleted", "account_profile_unloaded", "adjacent_sentinel_deleted", From 89604b81cbaba9ee137e03820d62c83e9bd4ef17 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:29:57 -0700 Subject: [PATCH 20/23] style: apply hosted rustfmt to AppContainer probe --- .../startup_benchmark_appcontainer.rs | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs index 28112b44b8da..3cd2d51016c2 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs @@ -22,8 +22,8 @@ use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use windows_sys::Win32::Foundation::{ - CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_SUCCESS, GENERIC_WRITE, HANDLE, - ERROR_ACCESS_DENIED, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, LocalFree, + CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, ERROR_ACCESS_DENIED, ERROR_SUCCESS, + GENERIC_WRITE, HANDLE, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, LocalFree, SetHandleInformation, WAIT_OBJECT_0, WAIT_TIMEOUT, }; use windows_sys::Win32::NetworkManagement::WindowsFirewall::{ @@ -88,8 +88,7 @@ const JOB_COMPLETION_KEY: usize = 0x434d_5558; const APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION: u32 = 1; const APP_CONTAINER_UNAVAILABLE_STATUS: &str = "unavailable"; const APP_CONTAINER_UNAVAILABLE_STAGE: &str = "staging-readability"; -const APP_CONTAINER_UNAVAILABLE_REASON: &str = - "dedicated Windows account could not read the nonce-bound staged target before the restricted-token broker started"; +const APP_CONTAINER_UNAVAILABLE_REASON: &str = "dedicated Windows account could not read the nonce-bound staged target before the restricted-token broker started"; #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] @@ -416,11 +415,9 @@ fn classify_staged_target_readability( } fn raw_os_error(error: &anyhow::Error) -> Option { - error.chain().find_map(|cause| { - cause - .downcast_ref::() - .and_then(io::Error::raw_os_error) - }) + error + .chain() + .find_map(|cause| cause.downcast_ref::().and_then(io::Error::raw_os_error)) } impl FeasibilityEvidence { @@ -569,16 +566,12 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { bail!("staged AppContainer probe hash changed"); } fs::write(&adjacent, b"protected").context("create AppContainer adjacent sentinel")?; - let staging_creation_acl_applied = !file_security( - staging.path(), - DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, - )? - .is_empty(); - let fixture_creation_acl_applied = !file_security( - fixture.path(), - DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, - )? - .is_empty(); + let staging_creation_acl_applied = + !file_security(staging.path(), DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION)? + .is_empty(); + let fixture_creation_acl_applied = + !file_security(fixture.path(), DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION)? + .is_empty(); let staged_target_regular_file = { let metadata = fs::symlink_metadata(&staged_target)?; metadata.file_type().is_file() && !metadata.file_type().is_symlink() @@ -2915,6 +2908,7 @@ mod tests { true, true, true, + true, false, false, Some(ERROR_ACCESS_DENIED as i32), @@ -2937,6 +2931,7 @@ mod tests { regular, false, false, + false, Some(ERROR_ACCESS_DENIED as i32), ), StagedTargetReadability::Invalid @@ -2948,26 +2943,19 @@ mod tests { fn staging_readability_rejects_generic_access_denied_and_hash_mismatch() { assert_eq!( classify_staged_target_readability( - true, true, true, true, true, false, false, None, + true, true, true, true, true, true, false, false, None, ), StagedTargetReadability::Invalid ); assert_eq!( classify_staged_target_readability( - true, - true, - true, - true, - true, - true, - false, - None, + true, true, true, true, true, true, true, false, None, ), StagedTargetReadability::Invalid ); assert_eq!( classify_staged_target_readability( - true, true, true, true, true, true, true, None, + true, true, true, true, true, true, true, true, None, ), StagedTargetReadability::Ready ); From 3f3883aab8ef01f52f95409e39d0a146b99c8062 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:37:41 -0700 Subject: [PATCH 21/23] test: attest account-process AppContainer probe state --- .../scripts/test_startup_benchmark_claim.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index f90fdd4f0849..07400da8dbac 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -45,6 +45,7 @@ def _write_appcontainer_evidence( account_readable=False, account_error_code=5, restricted_token_run_started=False, + account_probe_kind="impersonated-account", ) -> None: (output_dir / "windows-appcontainer-feasibility.json").write_text( json.dumps( @@ -65,6 +66,7 @@ def _write_appcontainer_evidence( "fixture_creation_acl_applied": fixture_acl_attested, "staged_target_regular_file": True, "account_probe_impersonated": True, + "account_probe_kind": account_probe_kind, "account_staged_target_readable": account_readable, "account_staged_target_error_code": account_error_code, "restricted_token_run_started": restricted_token_run_started, @@ -217,6 +219,7 @@ def _run_verifier( tamper_bootstrap=False, tamper_imports=False, appcontainer_evidence=None, + account_probe_kind="impersonated-account", skip_reason="required native Windows observations were unavailable", ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as temporary: @@ -263,7 +266,10 @@ def _run_verifier( report=report, ) if appcontainer_evidence is None: - self._write_appcontainer_evidence(output_dir) + self._write_appcontainer_evidence( + output_dir, + account_probe_kind=account_probe_kind, + ) else: (output_dir / "windows-appcontainer-feasibility.json").write_text( json.dumps(appcontainer_evidence, sort_keys=True) + "\n", @@ -402,6 +408,19 @@ def test_appcontainer_unavailable_can_skip_after_common_preflight_is_verified(se ) self.assertEqual(result.returncode, 0, result.stderr) + def test_account_process_probe_access_denied_can_skip_only_when_explicitly_attested(self) -> None: + result = self._run_verifier( + runner_os="Windows", + account_probe_kind="account-process", + unsupported_value=True, + all_windows_observed=True, + skip_reason=( + "Windows AppContainer staging-readability capability was unavailable; " + "no restricted-token broker run was started" + ), + ) + self.assertEqual(result.returncode, 0, result.stderr) + def test_unavailable_appcontainer_claim_requires_runner_and_cleanup_attestation(self) -> None: for changes in ( {"runner_staged_target_readable": False}, From 2493af00d0eab72e36e79ee9e32d42b6704d1ffc Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:55:07 -0700 Subject: [PATCH 22/23] fix: attest Windows account-process staging failures --- .../startup_benchmark_appcontainer.rs | 447 +++++++++++++++++- .../scripts/test_startup_benchmark_claim.py | 66 +++ cmux-tui/scripts/verify-startup-benchmark.py | 77 ++- 3 files changed, 567 insertions(+), 23 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs index 3cd2d51016c2..9c64909d4a5a 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs @@ -88,6 +88,8 @@ const JOB_COMPLETION_KEY: usize = 0x434d_5558; const APP_CONTAINER_UNAVAILABLE_SCHEMA_VERSION: u32 = 1; const APP_CONTAINER_UNAVAILABLE_STATUS: &str = "unavailable"; const APP_CONTAINER_UNAVAILABLE_STAGE: &str = "staging-readability"; +const APP_CONTAINER_ACCOUNT_PROBE_IMPERSONATED: &str = "impersonated-account"; +const APP_CONTAINER_ACCOUNT_PROBE_PROCESS: &str = "account-process"; const APP_CONTAINER_UNAVAILABLE_REASON: &str = "dedicated Windows account could not read the nonce-bound staged target before the restricted-token broker started"; #[derive(Debug, Deserialize, Serialize)] @@ -113,12 +115,18 @@ struct BrokerFailureEvidence { nonce: String, stage: BrokerFailureStage, error: String, + account_sid: Option, + account_authentication_id: Option, + target: Option, + target_sha256: Option, + restricted_token_run_started: Option, } #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "kebab-case")] enum BrokerFailureStage { ConfigReceive, + StagingReadability, ConfigValidate, ProductLaunch, SuccessEvidenceEncode, @@ -129,7 +137,17 @@ enum BrokerFailureStage { #[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)] enum BrokerWireRecord { Success { schema_version: u32, nonce: String, evidence: Box }, - Failure { schema_version: u32, nonce: String, stage: BrokerFailureStage, error: String }, + Failure { + schema_version: u32, + nonce: String, + stage: BrokerFailureStage, + error: String, + account_sid: Option, + account_authentication_id: Option, + target: Option, + target_sha256: Option, + restricted_token_run_started: Option, + }, } #[derive(Debug, Deserialize, Serialize)] @@ -317,6 +335,12 @@ struct AppContainerUnavailableEvidence { fixture_creation_acl_applied: bool, staged_target_regular_file: bool, account_probe_impersonated: bool, + account_probe_kind: &'static str, + account_sid: String, + account_authentication_id: String, + account_process_target: PathBuf, + account_process_target_sha256: String, + account_process_probe_started: bool, account_staged_target_readable: bool, account_staged_target_error_code: u32, restricted_token_run_started: bool, @@ -346,6 +370,16 @@ impl AppContainerUnavailableEvidence { || !self.fixture_creation_acl_applied || !self.staged_target_regular_file || !self.account_probe_impersonated + || (self.account_probe_kind != APP_CONTAINER_ACCOUNT_PROBE_IMPERSONATED + && self.account_probe_kind != APP_CONTAINER_ACCOUNT_PROBE_PROCESS) + || !self.account_sid.starts_with("S-") + || self.account_sid.len() > 256 + || self.account_authentication_id.len() != 16 + || !self.account_authentication_id.bytes().all(|byte| byte.is_ascii_hexdigit()) + || (self.account_probe_kind == APP_CONTAINER_ACCOUNT_PROBE_PROCESS + && (self.account_process_target.as_os_str().is_empty() + || self.account_process_target_sha256 != self.runner_staged_target_sha256 + || !self.account_process_probe_started)) || self.account_staged_target_readable || self.account_staged_target_error_code != ERROR_ACCESS_DENIED || self.restricted_token_run_started @@ -359,6 +393,20 @@ impl AppContainerUnavailableEvidence { { bail!("Windows AppContainer unavailable evidence is incomplete"); } + if self.account_probe_kind == APP_CONTAINER_ACCOUNT_PROBE_PROCESS { + let expected_stage = format!("appcontainer-stage-{}", &self.nonce[..16]); + let target_name = self.account_process_target.file_name().and_then(|name| name.to_str()); + let stage_name = self + .account_process_target + .parent() + .and_then(Path::file_name) + .and_then(|name| name.to_str()); + if target_name != Some("startup-benchmark-appcontainer-probe.exe") + || stage_name != Some(expected_stage.as_str()) + { + bail!("Windows AppContainer account-process target identity is invalid"); + } + } validate_sha256(&self.runner_staged_target_sha256, "runner staged target")?; validate_sha256(&self.expected_staged_target_sha256, "expected staged target")?; validate_sha256( @@ -420,6 +468,34 @@ fn raw_os_error(error: &anyhow::Error) -> Option { .find_map(|cause| cause.downcast_ref::().and_then(io::Error::raw_os_error)) } +fn is_staging_readability_unavailable( + failure: &BrokerFailureEvidence, + config: &BrokerConfig, + expected_nonce: &str, + expected_account_sid: &str, + expected_account_authentication_id: &str, +) -> bool { + if failure.schema_version != EVIDENCE_SCHEMA_VERSION + || failure.nonce != expected_nonce + || failure.stage != BrokerFailureStage::StagingReadability + { + return false; + } + if failure.account_sid.as_deref() != Some(expected_account_sid) + || failure.account_authentication_id.as_deref() != Some(expected_account_authentication_id) + || failure.target.as_ref() != Some(&config.target) + || failure.target_sha256.as_deref() != Some(config.target_sha256.as_str()) + || failure.restricted_token_run_started != Some(false) + { + return false; + } + let target = config.target.display(); + failure.error + == format!( + "validate staged AppContainer target hash: {target}: read file metadata for {target}: Access is denied. (os error 5)" + ) +} + impl FeasibilityEvidence { fn validate(&self) -> Result<()> { let product = &self.broker.product; @@ -618,6 +694,12 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { fixture_creation_acl_applied, staged_target_regular_file, account_probe_impersonated, + APP_CONTAINER_ACCOUNT_PROBE_IMPERSONATED, + account_sid.clone(), + account_authentication_id.clone(), + PathBuf::new(), + String::new(), + false, u32::try_from(account_error_code.expect("access-denied error code is present"))?, account_error, )?; @@ -642,7 +724,7 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { profile_folder: profile.folder.clone(), profile_name: profile_name.clone(), appcontainer_sid: appcontainer_sid.clone(), - account_authentication_id, + account_authentication_id: account_authentication_id.clone(), }; let broker_run = run_account_broker(account.token(), &staged_target, &config, &nonce); let (broker_result, wire_failure, broker_stdout, broker_stderr) = match broker_run { @@ -657,10 +739,25 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { nonce, stage, error: wire_error, + account_sid, + account_authentication_id, + target, + target_sha256, + restricted_token_run_started, }), ) => ( Err(error), - Some(BrokerFailureEvidence { schema_version, nonce, stage, error: wire_error }), + Some(BrokerFailureEvidence { + schema_version, + nonce, + stage, + error: wire_error, + account_sid, + account_authentication_id, + target, + target_sha256, + restricted_token_run_started, + }), ), (Ok(()), Ok(BrokerWireRecord::Failure { .. })) => ( Err(anyhow::anyhow!("successful AppContainer broker emitted a failure record")), @@ -679,6 +776,45 @@ pub(super) fn run_controller(values: &[String]) -> Result<()> { let broker = match broker_result { Ok(broker) => broker, Err(error) => { + if let Some(failure) = wire_failure.as_ref() + && is_staging_readability_unavailable( + failure, + &config, + &nonce, + &account_sid, + &account_authentication_id, + ) + { + persist_broker_failure(failure, &output, &nonce)?; + let account_error = anyhow::anyhow!(failure.error.clone()); + let evidence = cleanup_unavailable_appcontainer( + &mut account, + &mut profile, + &adjacent, + &mut staging, + &mut fixture, + &fixture_parent, + parent_security_information, + &parent_security_before, + nonce, + staged_probe_sha256.clone(), + target_sha256, + staging_creation_acl_applied, + fixture_creation_acl_applied, + staged_target_regular_file, + account_probe_impersonated, + APP_CONTAINER_ACCOUNT_PROBE_PROCESS, + account_sid.clone(), + account_authentication_id.clone(), + config.target.clone(), + config.target_sha256.clone(), + true, + ERROR_ACCESS_DENIED, + &account_error, + )?; + write_new_json(&output, &evidence)?; + return Err(super::WindowsClaimUnavailable.into()); + } let copied_failure = wire_failure .as_ref() .context("AppContainer broker produced no valid failure wire record") @@ -778,21 +914,54 @@ pub(super) fn run_broker(values: &[String]) -> Result<()> { )); } }; + // Validate the nonce-bound path and read it from this exact account process before preparing + // or launching any restricted-token AppContainer process. A raw access denial here means the + // runner cannot exercise the capability and is a typed unavailable result, not a claim. + if let Err(error) = validate_config_identity(&config, &expected_nonce) { + return Err(record_broker_failure( + &expected_nonce, + BrokerFailureStage::ConfigValidate, + error, + )); + } let process_token = match prepare_broker_process_token() { Ok(process_token) => process_token, Err(error) => { return Err(record_broker_failure( &expected_nonce, BrokerFailureStage::ConfigValidate, - error.context("prepare account broker token before config validation"), + error.context("prepare account broker token after staging readability"), )); } }; - if let Err(error) = validate_config(&config, &expected_nonce) { - return Err(record_broker_failure( + let process_account_sid = match token_user_sid(process_token.0) { + Ok(sid) => sid, + Err(error) => { + return Err(record_broker_failure( + &expected_nonce, + BrokerFailureStage::ConfigValidate, + error.context("read account broker user SID before staging readability"), + )); + } + }; + let process_account_authentication_id = match token_authentication_id(process_token.0) { + Ok(authentication_id) => authentication_id, + Err(error) => { + return Err(record_broker_failure( + &expected_nonce, + BrokerFailureStage::ConfigValidate, + error.context("read account broker authentication ID before staging readability"), + )); + } + }; + if let Err(error) = validate_staged_target(&config) { + return Err(record_staging_readability_failure( &expected_nonce, - BrokerFailureStage::ConfigValidate, error, + process_account_sid, + process_account_authentication_id, + config.target.clone(), + config.target_sha256.clone(), )); } let evidence = match launch_appcontainer_product(&config, process_token.0) { @@ -1397,8 +1566,14 @@ fn cleanup_unavailable_appcontainer( fixture_creation_acl_applied: bool, staged_target_regular_file: bool, account_probe_impersonated: bool, + account_probe_kind: &'static str, + account_sid: String, + account_authentication_id: String, + account_process_target: PathBuf, + account_process_target_sha256: String, + account_process_probe_started: bool, account_staged_target_error_code: u32, - account_staged_target_error: &anyhow::Error, + _account_staged_target_error: &anyhow::Error, ) -> Result { account.impersonate(|_| profile.delete())?; let profile_deleted = profile.deleted; @@ -1431,7 +1606,7 @@ fn cleanup_unavailable_appcontainer( backend: "windows-appcontainer-feasibility", nonce, stage: APP_CONTAINER_UNAVAILABLE_STAGE, - reason: format!("{APP_CONTAINER_UNAVAILABLE_REASON}: {account_staged_target_error:#}"), + reason: APP_CONTAINER_UNAVAILABLE_REASON.into(), runner_staged_target_readable: true, runner_staged_target_sha256, expected_staged_target_sha256, @@ -1439,6 +1614,12 @@ fn cleanup_unavailable_appcontainer( fixture_creation_acl_applied, staged_target_regular_file, account_probe_impersonated, + account_probe_kind, + account_sid, + account_authentication_id, + account_process_target, + account_process_target_sha256, + account_process_probe_started, account_staged_target_readable: false, account_staged_target_error_code, restricted_token_run_started: false, @@ -2365,17 +2546,13 @@ fn parent_security_unchanged(path: &Path, information: u32, before: &[usize]) -> } Ok(()) } -fn validate_config(config: &BrokerConfig, expected_nonce: &str) -> Result<()> { +fn validate_config_identity(config: &BrokerConfig, expected_nonce: &str) -> Result<()> { validate_nonce(&config.nonce)?; validate_profile_name(&config.profile_name)?; if config.schema_version != EVIDENCE_SCHEMA_VERSION || config.nonce != expected_nonce { bail!("AppContainer broker config violated its identity boundary"); } - let observed_target_sha256 = sha256_file(&config.target).with_context(|| { - format!("validate staged AppContainer target hash: {}", config.target.display()) - })?; - if config.target_sha256 != observed_target_sha256 - || config.target.parent() != Some(config.staging_root.as_path()) + if config.target.parent() != Some(config.staging_root.as_path()) || config.staging_root == config.fixture_root || config.staging_root.parent() != config.fixture_root.parent() || config.adjacent_path.starts_with(&config.fixture_root) @@ -2383,12 +2560,6 @@ fn validate_config(config: &BrokerConfig, expected_nonce: &str) -> Result<()> { { bail!("AppContainer broker config violated its identity boundary"); } - let target = fs::symlink_metadata(&config.target).with_context(|| { - format!("validate staged AppContainer target metadata: {}", config.target.display()) - })?; - if !target.file_type().is_file() || target.file_type().is_symlink() { - bail!("staged AppContainer target was not one regular file"); - } let expected_sid = derive_profile_sid(&config.profile_name) .context("derive expected AppContainer profile SID")?; let observed = OwnedSid::from_string(&config.appcontainer_sid) @@ -2399,6 +2570,27 @@ fn validate_config(config: &BrokerConfig, expected_nonce: &str) -> Result<()> { Ok(()) } +fn validate_staged_target(config: &BrokerConfig) -> Result<()> { + let observed_target_sha256 = sha256_file(&config.target).with_context(|| { + format!("validate staged AppContainer target hash: {}", config.target.display()) + })?; + if config.target_sha256 != observed_target_sha256 { + bail!("AppContainer broker config violated its identity boundary"); + } + let target = fs::symlink_metadata(&config.target).with_context(|| { + format!("validate staged AppContainer target metadata: {}", config.target.display()) + })?; + if !target.file_type().is_file() || target.file_type().is_symlink() { + bail!("staged AppContainer target was not one regular file"); + } + Ok(()) +} + +fn validate_config(config: &BrokerConfig, expected_nonce: &str) -> Result<()> { + validate_config_identity(config, expected_nonce)?; + validate_staged_target(config) +} + fn validate_product(product: &ProductEvidence, config: &BrokerConfig) -> Result<()> { if product.schema_version != EVIDENCE_SCHEMA_VERSION || product.nonce != config.nonce @@ -2459,6 +2651,34 @@ fn validate_broker_failure(failure: &BrokerFailureEvidence, expected_nonce: &str { bail!("AppContainer broker failure evidence is invalid"); } + let has_process_probe_fields = failure.account_sid.is_some() + || failure.account_authentication_id.is_some() + || failure.target.is_some() + || failure.target_sha256.is_some() + || failure.restricted_token_run_started.is_some(); + if failure.stage == BrokerFailureStage::StagingReadability { + let (Some(account_sid), Some(account_authentication_id), Some(target), Some(target_sha256), Some(restricted_token_run_started)) = ( + failure.account_sid.as_ref(), + failure.account_authentication_id.as_ref(), + failure.target.as_ref(), + failure.target_sha256.as_ref(), + failure.restricted_token_run_started, + ) else { + bail!("AppContainer staging-readability failure evidence is incomplete"); + }; + if !account_sid.starts_with("S-") + || account_sid.len() > 256 + || account_authentication_id.len() != 16 + || !account_authentication_id.bytes().all(|byte| byte.is_ascii_hexdigit()) + || target.as_os_str().is_empty() + || restricted_token_run_started + { + bail!("AppContainer staging-readability failure evidence is invalid"); + } + validate_sha256(target_sha256, "AppContainer staging-readability target")?; + } else if has_process_probe_fields { + bail!("AppContainer broker failure evidence has unexpected process probe fields"); + } Ok(()) } @@ -2484,6 +2704,11 @@ fn record_broker_failure( nonce: nonce.to_string(), stage, error: bounded_error(&error), + account_sid: None, + account_authentication_id: None, + target: None, + target_sha256: None, + restricted_token_run_started: None, }; match encode_canonical_json_line(&failure).and_then(|encoded| write_broker_stdout(&encoded)) { Ok(()) => error, @@ -2493,6 +2718,33 @@ fn record_broker_failure( } } +fn record_staging_readability_failure( + nonce: &str, + error: anyhow::Error, + account_sid: String, + account_authentication_id: String, + target: PathBuf, + target_sha256: String, +) -> anyhow::Error { + let failure = BrokerWireRecord::Failure { + schema_version: EVIDENCE_SCHEMA_VERSION, + nonce: nonce.to_string(), + stage: BrokerFailureStage::StagingReadability, + error: bounded_error(&error), + account_sid: Some(account_sid), + account_authentication_id: Some(account_authentication_id), + target: Some(target), + target_sha256: Some(target_sha256), + restricted_token_run_started: Some(false), + }; + match encode_canonical_json_line(&failure).and_then(|encoded| write_broker_stdout(&encoded)) { + Ok(()) => error, + Err(write) => error.context(format!( + "also failed to write AppContainer staging-readability failure record: {write:#}" + )), + } +} + fn encode_canonical_json_line(value: &impl Serialize) -> Result> { let mut encoded = serde_json::to_vec(value)?; if encoded.len() + 1 > MAX_RECORD_BYTES { @@ -2533,12 +2785,27 @@ fn validate_broker_wire(record: &BrokerWireRecord, expected_nonce: &str) -> Resu bail!("AppContainer broker success wire identity was invalid"); } } - BrokerWireRecord::Failure { schema_version, nonce, stage, error } => { + BrokerWireRecord::Failure { + schema_version, + nonce, + stage, + error, + account_sid, + account_authentication_id, + target, + target_sha256, + restricted_token_run_started, + } => { let failure = BrokerFailureEvidence { schema_version: *schema_version, nonce: nonce.clone(), stage: *stage, error: error.clone(), + account_sid: account_sid.clone(), + account_authentication_id: account_authentication_id.clone(), + target: target.clone(), + target_sha256: target_sha256.clone(), + restricted_token_run_started: *restricted_token_run_started, }; validate_broker_failure(&failure, expected_nonce)?; } @@ -3139,6 +3406,7 @@ mod tests { fn broker_failure_record_uses_schema_four_and_the_fixed_stage_allowlist() { for (stage, expected) in [ (BrokerFailureStage::ConfigReceive, "config-receive"), + (BrokerFailureStage::StagingReadability, "staging-readability"), (BrokerFailureStage::ConfigValidate, "config-validate"), (BrokerFailureStage::ProductLaunch, "product-launch"), (BrokerFailureStage::SuccessEvidenceEncode, "success-evidence-encode"), @@ -3149,6 +3417,11 @@ mod tests { nonce: "12".repeat(32), stage, error: "denied".into(), + account_sid: None, + account_authentication_id: None, + target: None, + target_sha256: None, + restricted_token_run_started: None, }; let encoded = serde_json::to_value(&evidence).unwrap(); @@ -3167,6 +3440,126 @@ mod tests { assert!(serde_json::from_value::(unknown).is_err()); } + #[test] + fn staging_readability_failure_requires_exact_account_identity_and_target() { + let nonce = "12".repeat(32); + let staging_root = PathBuf::from( + r"\\?\D:\a\_temp\cbt\appcontainer-stage-1212121212121212", + ); + let target = staging_root.join("startup-benchmark-appcontainer-probe.exe"); + let config = BrokerConfig { + schema_version: EVIDENCE_SCHEMA_VERSION, + nonce: nonce.clone(), + target: target.clone(), + target_sha256: "34".repeat(32), + staging_root: staging_root.clone(), + fixture_root: PathBuf::from( + r"\\?\D:\a\_temp\cbt\appcontainer-fixture-1212121212121212", + ), + adjacent_path: PathBuf::from(r"\\?\D:\a\_temp\cbt\appcontainer-adjacent-1212"), + profile_folder: PathBuf::from(r"\\?\D:\a\profile"), + profile_name: format!("cmux.bench.ac.{}", &nonce[..32]), + appcontainer_sid: "S-1-15-2-1".into(), + account_authentication_id: "0000000100000002".into(), + }; + let exact_error = format!( + "validate staged AppContainer target hash: {target}: read file metadata for {target}: Access is denied. (os error 5)" + ); + let make_failure = |stage, error, account_sid, authentication_id, failure_target, hash, restricted| { + BrokerFailureEvidence { + schema_version: EVIDENCE_SCHEMA_VERSION, + nonce: nonce.clone(), + stage, + error: error.into(), + account_sid: Some(account_sid.into()), + account_authentication_id: Some(authentication_id.into()), + target: Some(failure_target), + target_sha256: Some(hash.into()), + restricted_token_run_started: Some(restricted), + } + }; + let valid = make_failure( + BrokerFailureStage::StagingReadability, + exact_error.clone(), + "S-1-5-21-1", + "0000000100000002", + target.clone(), + "34".repeat(32), + false, + ); + assert!(is_staging_readability_unavailable( + &valid, + &config, + &nonce, + "S-1-5-21-1", + "0000000100000002", + )); + for invalid in [ + make_failure( + BrokerFailureStage::ConfigValidate, + exact_error.clone(), + "S-1-5-21-1", + "0000000100000002", + target.clone(), + "34".repeat(32), + false, + ), + make_failure( + BrokerFailureStage::StagingReadability, + exact_error.clone(), + "S-1-5-21-2", + "0000000100000002", + target.clone(), + "34".repeat(32), + false, + ), + make_failure( + BrokerFailureStage::StagingReadability, + exact_error.clone(), + "S-1-5-21-1", + "0000000100000003", + target.clone(), + "34".repeat(32), + false, + ), + make_failure( + BrokerFailureStage::StagingReadability, + exact_error.clone(), + "S-1-5-21-1", + "0000000100000002", + target.with_file_name("other.exe"), + "34".repeat(32), + false, + ), + make_failure( + BrokerFailureStage::StagingReadability, + exact_error.clone(), + "S-1-5-21-1", + "0000000100000002", + target.clone(), + "35".repeat(32), + false, + ), + make_failure( + BrokerFailureStage::StagingReadability, + exact_error, + "S-1-5-21-1", + "0000000100000002", + target, + "34".repeat(32), + true, + ), + ] { + assert!(!is_staging_readability_unavailable( + &invalid, + &config, + &nonce, + "S-1-5-21-1", + "0000000100000002", + )); + } + } + #[test] fn broker_wire_is_one_canonical_nonce_bound_line() { let nonce = "12".repeat(32); @@ -3175,6 +3568,11 @@ mod tests { nonce: nonce.clone(), stage: BrokerFailureStage::ConfigValidate, error: "denied".into(), + account_sid: None, + account_authentication_id: None, + target: None, + target_sha256: None, + restricted_token_run_started: None, }; let encoded = encode_canonical_json_line(&failure).unwrap(); @@ -3216,6 +3614,11 @@ mod tests { nonce: nonce.clone(), stage: BrokerFailureStage::ConfigValidate, error: "denied".into(), + account_sid: None, + account_authentication_id: None, + target: None, + target_sha256: None, + restricted_token_run_started: None, }; assert!(persist_broker_failure(&evidence, &requested_output, &"34".repeat(32)).is_err()); let copied = persist_broker_failure(&evidence, &requested_output, &nonce).unwrap(); diff --git a/cmux-tui/scripts/test_startup_benchmark_claim.py b/cmux-tui/scripts/test_startup_benchmark_claim.py index 07400da8dbac..994db3167f2f 100644 --- a/cmux-tui/scripts/test_startup_benchmark_claim.py +++ b/cmux-tui/scripts/test_startup_benchmark_claim.py @@ -46,7 +46,29 @@ def _write_appcontainer_evidence( account_error_code=5, restricted_token_run_started=False, account_probe_kind="impersonated-account", + account_sid="S-1-5-21-1", + account_authentication_id="0000000100000002", + account_process_target=None, + account_process_target_sha256=None, + account_process_probe_started=None, ) -> None: + if account_probe_kind == "account-process": + if account_process_target is None: + account_process_target = ( + r"\\?\D:\a\_temp\cbt\appcontainer-stage-eeeeeeeeeeeeeeee" + r"\startup-benchmark-appcontainer-probe.exe" + ) + if account_process_target_sha256 is None: + account_process_target_sha256 = runner_hash + if account_process_probe_started is None: + account_process_probe_started = True + else: + if account_process_target is None: + account_process_target = "" + if account_process_target_sha256 is None: + account_process_target_sha256 = "" + if account_process_probe_started is None: + account_process_probe_started = False (output_dir / "windows-appcontainer-feasibility.json").write_text( json.dumps( { @@ -67,6 +89,11 @@ def _write_appcontainer_evidence( "staged_target_regular_file": True, "account_probe_impersonated": True, "account_probe_kind": account_probe_kind, + "account_sid": account_sid, + "account_authentication_id": account_authentication_id, + "account_process_target": account_process_target, + "account_process_target_sha256": account_process_target_sha256, + "account_process_probe_started": account_process_probe_started, "account_staged_target_readable": account_readable, "account_staged_target_error_code": account_error_code, "restricted_token_run_started": restricted_token_run_started, @@ -220,6 +247,11 @@ def _run_verifier( tamper_imports=False, appcontainer_evidence=None, account_probe_kind="impersonated-account", + account_sid="S-1-5-21-1", + account_authentication_id="0000000100000002", + account_process_target=None, + account_process_target_sha256=None, + account_process_probe_started=None, skip_reason="required native Windows observations were unavailable", ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as temporary: @@ -269,6 +301,11 @@ def _run_verifier( self._write_appcontainer_evidence( output_dir, account_probe_kind=account_probe_kind, + account_sid=account_sid, + account_authentication_id=account_authentication_id, + account_process_target=account_process_target, + account_process_target_sha256=account_process_target_sha256, + account_process_probe_started=account_process_probe_started, ) else: (output_dir / "windows-appcontainer-feasibility.json").write_text( @@ -421,6 +458,29 @@ def test_account_process_probe_access_denied_can_skip_only_when_explicitly_attes ) self.assertEqual(result.returncode, 0, result.stderr) + def test_forged_account_process_probe_identity_is_rejected(self) -> None: + for changes in ( + {"account_sid": "S-1-5-21-2"}, + {"account_authentication_id": "0000000100000003"}, + { + "account_process_target": ( + r"\\?\D:\a\_temp\cbt\appcontainer-stage-eeeeeeeeeeeeeeee" + r"\other.exe" + ) + }, + {"account_process_target_sha256": "0" * 64}, + {"account_process_probe_started": False}, + ): + with self.subTest(changes=changes): + result = self._run_verifier( + runner_os="Windows", + account_probe_kind="account-process", + unsupported_value=True, + all_windows_observed=True, + **changes, + ) + self.assertNotEqual(result.returncode, 0) + def test_unavailable_appcontainer_claim_requires_runner_and_cleanup_attestation(self) -> None: for changes in ( {"runner_staged_target_readable": False}, @@ -446,6 +506,12 @@ def test_unavailable_appcontainer_claim_requires_runner_and_cleanup_attestation( "fixture_creation_acl_applied": True, "staged_target_regular_file": True, "account_probe_impersonated": True, + "account_probe_kind": "impersonated-account", + "account_sid": "S-1-5-21-1", + "account_authentication_id": "0000000100000002", + "account_process_target": "", + "account_process_target_sha256": "", + "account_process_probe_started": False, "account_staged_target_readable": False, "account_staged_target_error_code": 5, "restricted_token_run_started": False, diff --git a/cmux-tui/scripts/verify-startup-benchmark.py b/cmux-tui/scripts/verify-startup-benchmark.py index 89b965b099e5..24196b055872 100644 --- a/cmux-tui/scripts/verify-startup-benchmark.py +++ b/cmux-tui/scripts/verify-startup-benchmark.py @@ -16,6 +16,7 @@ FULL_SHA_PATTERN = re.compile(r"[0-9a-f]{40}") FULL_SHA256_PATTERN = re.compile(r"[0-9a-f]{64}") +APP_CONTAINER_SID_PATTERN = re.compile(r"S-1(?:-\d+)+") SKIPPED_REPORT_SCHEMA = 4 SKIPPED_PLATFORM = "windows-azure" SKIPPED_BACKEND = "windows-restricted-token-job" @@ -49,6 +50,12 @@ "fixture_creation_acl_applied", "staged_target_regular_file", "account_probe_impersonated", + "account_probe_kind", + "account_sid", + "account_authentication_id", + "account_process_target", + "account_process_target_sha256", + "account_process_probe_started", "account_staged_target_readable", "account_staged_target_error_code", "restricted_token_run_started", @@ -820,6 +827,40 @@ def validate_appcontainer_feasibility_unavailable(evidence): ): if evidence[field] is not True: raise SystemExit(f"AppContainer unavailable proof is false: {field}") + if evidence["account_probe_kind"] not in { + "impersonated-account", + "account-process", + }: + raise SystemExit("AppContainer unavailable probe kind is invalid") + if ( + not isinstance(evidence["account_sid"], str) + or APP_CONTAINER_SID_PATTERN.fullmatch(evidence["account_sid"]) is None + or not isinstance(evidence["account_authentication_id"], str) + or re.fullmatch(r"[0-9a-f]{16}", evidence["account_authentication_id"]) is None + ): + raise SystemExit("AppContainer unavailable account identity is invalid") + process_target = evidence["account_process_target"] + process_hash = evidence["account_process_target_sha256"] + process_started = evidence["account_process_probe_started"] + if evidence["account_probe_kind"] == "account-process": + normalized_target = process_target.replace("/", "\\") if isinstance(process_target, str) else "" + target_parts = [part for part in normalized_target.split("\\") if part] + expected_stage = f"appcontainer-stage-{nonce[:16]}" + if ( + not isinstance(process_target, str) + or not process_target.endswith( + "startup-benchmark-appcontainer-probe.exe" + ) + or len(target_parts) < 2 + or target_parts[-2] != expected_stage + or not isinstance(process_hash, str) + or FULL_SHA256_PATTERN.fullmatch(process_hash) is None + or process_hash != evidence["runner_staged_target_sha256"] + or process_started is not True + ): + raise SystemExit("AppContainer unavailable account-process proof is invalid") + elif process_target != "" or process_hash != "" or process_started is not False: + raise SystemExit("AppContainer unavailable impersonation proof is invalid") if ( evidence["account_staged_target_readable"] is not False or evidence["restricted_token_run_started"] is not False @@ -1123,7 +1164,17 @@ def validate_appcontainer_feasibility_failure(path): evidence = json.loads(path.read_text(encoding="utf-8")) require_exact_object( evidence, - {"schema_version", "nonce", "stage", "error"}, + { + "schema_version", + "nonce", + "stage", + "error", + "account_sid", + "account_authentication_id", + "target", + "target_sha256", + "restricted_token_run_started", + }, "AppContainer feasibility failure evidence", ) nonce = evidence["nonce"] @@ -1135,6 +1186,7 @@ def validate_appcontainer_feasibility_failure(path): or evidence["stage"] not in { "config-receive", + "staging-readability", "config-validate", "product-launch", "success-evidence-encode", @@ -1145,6 +1197,29 @@ def validate_appcontainer_feasibility_failure(path): or len(error.encode("utf-8")) > 4096 ): raise SystemExit("AppContainer feasibility failure evidence is invalid") + process_fields = ( + evidence["account_sid"], + evidence["account_authentication_id"], + evidence["target"], + evidence["target_sha256"], + evidence["restricted_token_run_started"], + ) + if evidence["stage"] == "staging-readability": + account_sid, authentication_id, target, target_sha256, restricted_started = process_fields + if ( + not isinstance(account_sid, str) + or APP_CONTAINER_SID_PATTERN.fullmatch(account_sid) is None + or not isinstance(authentication_id, str) + or re.fullmatch(r"[0-9a-f]{16}", authentication_id) is None + or not isinstance(target, str) + or not target.endswith("startup-benchmark-appcontainer-probe.exe") + or not isinstance(target_sha256, str) + or FULL_SHA256_PATTERN.fullmatch(target_sha256) is None + or restricted_started is not False + ): + raise SystemExit("AppContainer staging-readability identity is invalid") + elif any(value is not None for value in process_fields): + raise SystemExit("AppContainer feasibility failure has unexpected process fields") if len(sys.argv) == 3 and sys.argv[1] == "--appcontainer-feasibility": From e488456d5b1f3c9ffa38cc0d6b94a577d4ee22a5 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:01:15 -0700 Subject: [PATCH 23/23] style: apply hosted rustfmt to account-process probe --- .../startup_benchmark_appcontainer.rs | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs index 9c64909d4a5a..4cf1bb50d8de 100644 --- a/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs +++ b/cmux-tui/crates/cmux-tui/examples/startup_benchmark_appcontainer.rs @@ -136,7 +136,11 @@ enum BrokerFailureStage { #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)] enum BrokerWireRecord { - Success { schema_version: u32, nonce: String, evidence: Box }, + Success { + schema_version: u32, + nonce: String, + evidence: Box, + }, Failure { schema_version: u32, nonce: String, @@ -395,7 +399,8 @@ impl AppContainerUnavailableEvidence { } if self.account_probe_kind == APP_CONTAINER_ACCOUNT_PROBE_PROCESS { let expected_stage = format!("appcontainer-stage-{}", &self.nonce[..16]); - let target_name = self.account_process_target.file_name().and_then(|name| name.to_str()); + let target_name = + self.account_process_target.file_name().and_then(|name| name.to_str()); let stage_name = self .account_process_target .parent() @@ -2657,13 +2662,20 @@ fn validate_broker_failure(failure: &BrokerFailureEvidence, expected_nonce: &str || failure.target_sha256.is_some() || failure.restricted_token_run_started.is_some(); if failure.stage == BrokerFailureStage::StagingReadability { - let (Some(account_sid), Some(account_authentication_id), Some(target), Some(target_sha256), Some(restricted_token_run_started)) = ( + let ( + Some(account_sid), + Some(account_authentication_id), + Some(target), + Some(target_sha256), + Some(restricted_token_run_started), + ) = ( failure.account_sid.as_ref(), failure.account_authentication_id.as_ref(), failure.target.as_ref(), failure.target_sha256.as_ref(), failure.restricted_token_run_started, - ) else { + ) + else { bail!("AppContainer staging-readability failure evidence is incomplete"); }; if !account_sid.starts_with("S-") @@ -3443,9 +3455,7 @@ mod tests { #[test] fn staging_readability_failure_requires_exact_account_identity_and_target() { let nonce = "12".repeat(32); - let staging_root = PathBuf::from( - r"\\?\D:\a\_temp\cbt\appcontainer-stage-1212121212121212", - ); + let staging_root = PathBuf::from(r"\\?\D:\a\_temp\cbt\appcontainer-stage-1212121212121212"); let target = staging_root.join("startup-benchmark-appcontainer-probe.exe"); let config = BrokerConfig { schema_version: EVIDENCE_SCHEMA_VERSION, @@ -3465,19 +3475,20 @@ mod tests { let exact_error = format!( "validate staged AppContainer target hash: {target}: read file metadata for {target}: Access is denied. (os error 5)" ); - let make_failure = |stage, error, account_sid, authentication_id, failure_target, hash, restricted| { - BrokerFailureEvidence { - schema_version: EVIDENCE_SCHEMA_VERSION, - nonce: nonce.clone(), - stage, - error: error.into(), - account_sid: Some(account_sid.into()), - account_authentication_id: Some(authentication_id.into()), - target: Some(failure_target), - target_sha256: Some(hash.into()), - restricted_token_run_started: Some(restricted), - } - }; + let make_failure = + |stage, error, account_sid, authentication_id, failure_target, hash, restricted| { + BrokerFailureEvidence { + schema_version: EVIDENCE_SCHEMA_VERSION, + nonce: nonce.clone(), + stage, + error: error.into(), + account_sid: Some(account_sid.into()), + account_authentication_id: Some(authentication_id.into()), + target: Some(failure_target), + target_sha256: Some(hash.into()), + restricted_token_run_started: Some(restricted), + } + }; let valid = make_failure( BrokerFailureStage::StagingReadability, exact_error.clone(),