From 5d4034501fc6c9c0592aaf048f552ad38daf7115 Mon Sep 17 00:00:00 2001 From: metaphorics <152830360+metaphorics@users.noreply.github.com> Date: Sun, 20 Sep 2026 21:36:04 +0900 Subject: [PATCH 1/2] fix(ci): provision weekly receipt authority per job --- .github/workflows/weekly-audit.yml | 20 ++++++++ .../tests/workflow_receipts.rs | 49 ++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/.github/workflows/weekly-audit.yml b/.github/workflows/weekly-audit.yml index dd8e0134..c12ed98a 100644 --- a/.github/workflows/weekly-audit.yml +++ b/.github/workflows/weekly-audit.yml @@ -131,6 +131,16 @@ jobs: - name: Build compiler lane worker shell: bash run: cargo build --locked --release -p bamts-verification --bin ts_lane_worker + - name: Fetch pinned TypeScript compiler authority + shell: bash + run: >- + cargo run --locked -p bamts-verification -- source fetch + typescript-7-compiler --dest target/authority/typescript-7.0.2 + - name: Fetch pinned TypeScript test authority + shell: bash + run: >- + cargo run --locked -p bamts-verification -- source fetch + typescript-primary-tests --dest target/authority/typescript-7.0.2-tests - name: Run weekly-audit receipt shard shell: bash env: @@ -191,6 +201,16 @@ jobs: - name: Build compiler lane worker shell: bash run: cargo build --locked --release -p bamts-verification --bin ts_lane_worker + - name: Fetch pinned TypeScript compiler authority + shell: bash + run: >- + cargo run --locked -p bamts-verification -- source fetch + typescript-7-compiler --dest target/authority/typescript-7.0.2 + - name: Fetch pinned TypeScript test authority + shell: bash + run: >- + cargo run --locked -p bamts-verification -- source fetch + typescript-primary-tests --dest target/authority/typescript-7.0.2-tests - name: Merge complete compatible matrix shell: bash env: diff --git a/crates/bamts-verification/tests/workflow_receipts.rs b/crates/bamts-verification/tests/workflow_receipts.rs index 303b3675..209443a7 100644 --- a/crates/bamts-verification/tests/workflow_receipts.rs +++ b/crates/bamts-verification/tests/workflow_receipts.rs @@ -1,4 +1,4 @@ -use std::{fs, path::PathBuf}; +use std::{collections::BTreeMap, fs, path::PathBuf}; fn repository_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -13,6 +13,53 @@ fn workflow(name: &str) -> String { .unwrap_or_else(|error| panic!("cannot read {name}: {error}")) } +#[test] +fn compiler_receipt_jobs_provision_authority_before_execution() { + #[derive(serde::Deserialize)] + struct Workflow { + jobs: BTreeMap, + } + + #[derive(serde::Deserialize)] + struct Job { + #[serde(default)] + steps: Vec, + } + + #[derive(serde::Deserialize)] + struct Step { + #[serde(default)] + run: String, + } + + for name in ["ci.yml", "nightly.yml", "weekly-audit.yml"] { + let parsed: Workflow = serde_saphyr::from_str(&workflow(name)) + .unwrap_or_else(|error| panic!("cannot parse {name}: {error}")); + let mut checked_jobs = 0; + for (job_name, job) in parsed.jobs { + let Some(execution) = job.steps.iter().position(|step| { + step.run.contains("--catalog typescript-7.0.2") + && (step.run.contains("suite run") || step.run.contains("suite merge")) + }) else { + continue; + }; + checked_jobs += 1; + for required in [ + "source fetch typescript-7-compiler --dest target/authority/typescript-7.0.2", + "source fetch typescript-primary-tests --dest target/authority/typescript-7.0.2-tests", + ] { + assert!( + job.steps[..execution] + .iter() + .any(|step| step.run.contains(required)), + "{name}:{job_name} must provision `{required}` before receipt execution" + ); + } + } + assert!(checked_jobs > 0, "{name} contains no compiler receipt jobs"); + } +} + #[test] fn receipt_workflows_bind_attempt_and_merge_complete_matrices() { for (name, raw_root, merged_root, retention) in [ From d2c80d973cc5462974a774160baa8874a4c2e77e Mon Sep 17 00:00:00 2001 From: metaphorics <152830360+metaphorics@users.noreply.github.com> Date: Sun, 20 Sep 2026 21:38:33 +0900 Subject: [PATCH 2/2] fix(windows): accept TrustedInstaller-owned cache ancestors --- .../bamts-native/src/cache_guard/windows.rs | 72 ++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/crates/bamts-native/src/cache_guard/windows.rs b/crates/bamts-native/src/cache_guard/windows.rs index 04040f18..c6830558 100644 --- a/crates/bamts-native/src/cache_guard/windows.rs +++ b/crates/bamts-native/src/cache_guard/windows.rs @@ -17,7 +17,7 @@ use windows::{ ACCESS_ALLOWED_ACE, ACE_HEADER, ACL, Authorization::{ ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW, - GetSecurityInfo, SDDL_REVISION_1, SE_FILE_OBJECT, + ConvertStringSidToSidW, GetSecurityInfo, SDDL_REVISION_1, SE_FILE_OBJECT, }, CONTAINER_INHERIT_ACE, CopySid, CreateWellKnownSid, DACL_SECURITY_INFORMATION, EqualSid, GENERIC_MAPPING, GetAce, GetLengthSid, GetSecurityDescriptorControl, @@ -47,6 +47,10 @@ use super::{CacheGuardError, HeldArchive}; const MAX_CHAIN_DEPTH: usize = 32; const MAX_NAME_ATTEMPTS: usize = 128; const COMPARE_BUFFER_BYTES: usize = 64 * 1024; +// Windows Modules Installer owns protected system directories, including the +// system-drive root on current Windows images. It is not a private-cache owner. +const TRUSTED_INSTALLER_SID: &str = + "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464"; static NEXT_INVOCATION_ID: AtomicU64 = AtomicU64::new(0); #[derive(Clone, Copy)] @@ -113,6 +117,7 @@ struct TrustedSids { system: OwnedSid, administrators: OwnedSid, creator_owner: OwnedSid, + trusted_installer: OwnedSid, } #[derive(Debug)] @@ -281,10 +286,11 @@ impl TrustedSids { system: well_known_sid(WinLocalSystemSid)?, administrators: well_known_sid(WinBuiltinAdministratorsSid)?, creator_owner: well_known_sid(WinCreatorOwnerSid)?, + trusted_installer: parse_sid(TRUSTED_INSTALLER_SID)?, }) } - fn owner_is_trusted(&self, candidate: PSID) -> bool { + fn owner_is_trusted(&self, candidate: PSID, policy: DirectoryPolicy) -> bool { [ self.user.as_sid(), self.system.as_sid(), @@ -292,10 +298,13 @@ impl TrustedSids { ] .into_iter() .any(|trusted| equal_sid(candidate, trusted)) + || (matches!(policy, DirectoryPolicy::Ancestor) + && equal_sid(candidate, self.trusted_installer.as_sid())) } - fn mutation_is_trusted(&self, candidate: PSID) -> bool { - self.owner_is_trusted(candidate) || equal_sid(candidate, self.creator_owner.as_sid()) + fn mutation_is_trusted(&self, candidate: PSID, policy: DirectoryPolicy) -> bool { + self.owner_is_trusted(candidate, policy) + || equal_sid(candidate, self.creator_owner.as_sid()) } } @@ -409,7 +418,7 @@ fn inspect_security_descriptor( policy: DirectoryPolicy, trusted: &TrustedSids, ) -> Result<(), CacheGuardError> { - if owner.is_invalid() || !trusted.owner_is_trusted(owner) { + if owner.is_invalid() || !trusted.owner_is_trusted(owner, policy) { return Err(CacheGuardError::UntrustedOwner { path: path.to_owned(), owner: sid_to_string(owner, path).unwrap_or_else(|_| "".to_owned()), @@ -461,7 +470,7 @@ fn inspect_security_descriptor( continue; } let trustee = PSID(ptr::from_ref(&ace.SidStart).cast_mut().cast()); - if !trusted.mutation_is_trusted(trustee) { + if !trusted.mutation_is_trusted(trustee, policy) { return Err(CacheGuardError::UntrustedWriteAce { path: path.to_owned(), trustee: sid_to_string(trustee, path) @@ -637,8 +646,23 @@ fn well_known_sid( Ok(buffer) } +fn parse_sid(value: &str) -> Result { + let path = Path::new(""); + let value = wide(OsStr::new(value)); + let mut sid = PSID(ptr::null_mut()); + // SAFETY: value is NUL terminated and sid is a valid output pointer. + unsafe { ConvertStringSidToSidW(PCWSTR(value.as_ptr()), &mut sid) } + .map_err(|error| CacheGuardError::io(path, io::Error::from_raw_os_error(error.code().0)))?; + let result = copy_sid(sid, path); + // SAFETY: ConvertStringSidToSidW allocated sid with LocalAlloc on success. + unsafe { + LocalFree(Some(HLOCAL(sid.0))); + } + result +} + fn copy_sid(source: PSID, path: &Path) -> Result { - // SAFETY: source comes from a successful token/security query. + // SAFETY: source comes from a successful Windows SID query or conversion. let size = unsafe { GetLengthSid(source) }; let mut output = OwnedSid::with_byte_capacity(size as usize); // SAFETY: the pointer-aligned output has at least the size reported for source. @@ -682,7 +706,8 @@ fn wide(value: &OsStr) -> Vec { mod tests { use super::{ CONTAINER_INHERIT_ACE, DELETE, DirectoryPolicy, FILE_DELETE_CHILD, FILE_WRITE_DATA, - INHERIT_ONLY_ACE, OBJECT_INHERIT_ACE, PrivateCacheRoot, WRITE_DAC, ace_grants_mutation, + INHERIT_ONLY_ACE, OBJECT_INHERIT_ACE, PrivateCacheRoot, TrustedSids, WRITE_DAC, + ace_grants_mutation, parse_sid, well_known_sid, }; use std::{ fs, @@ -700,6 +725,37 @@ mod tests { )) } + #[test] + fn trusted_installer_is_trusted_only_for_ancestors() { + let trusted = TrustedSids::current().expect("trusted SIDs"); + let installer = trusted.trusted_installer.as_sid(); + assert!(trusted.owner_is_trusted(installer, DirectoryPolicy::Ancestor)); + assert!(trusted.mutation_is_trusted(installer, DirectoryPolicy::Ancestor)); + assert!(!trusted.owner_is_trusted(installer, DirectoryPolicy::Cache)); + assert!(!trusted.mutation_is_trusted(installer, DirectoryPolicy::Cache)); + } + + #[test] + fn system_ancestor_exception_preserves_the_exact_trust_boundary() { + let trusted = TrustedSids::current().expect("trusted SIDs"); + let other_service = + parse_sid("S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478465") + .expect("distinct service SID"); + let everyone = well_known_sid(windows::Win32::Security::WinWorldSid).expect("Everyone SID"); + for policy in [DirectoryPolicy::Ancestor, DirectoryPolicy::Cache] { + for owner in [&trusted.user, &trusted.system, &trusted.administrators] { + assert!(trusted.owner_is_trusted(owner.as_sid(), policy)); + assert!(trusted.mutation_is_trusted(owner.as_sid(), policy)); + } + for untrusted in [&other_service, &everyone] { + assert!(!trusted.owner_is_trusted(untrusted.as_sid(), policy)); + assert!(!trusted.mutation_is_trusted(untrusted.as_sid(), policy)); + } + assert!(!trusted.owner_is_trusted(trusted.creator_owner.as_sid(), policy)); + assert!(trusted.mutation_is_trusted(trusted.creator_owner.as_sid(), policy)); + } + } + #[test] fn inherit_only_cache_ace_is_checked_for_descendant_mutation() { let inherit_only = INHERIT_ONLY_ACE.0 as u8;