use proxywarden_lib::component_catalog::ComponentId as CatalogComponentId; use proxywarden_lib::component_cutover::{ ComponentCutoverUserEvidence, CUTOVER_USER_EVIDENCE_SCHEMA_VERSION, }; use proxywarden_lib::privileged_jobs::{ cutover_recovery_probe_fingerprint, execute_privileged_job, launch_privileged_job, parse_early_helper_arguments, validate_install_receipt, CanonicalComponentRoot, ElevatedJobLauncher, ElevatedLaunchOutcome, ElevationProbe, EpochClock, InstallReceipt, InstallReceiptManager, InstalledAssetReceipt, InstalledFileReceipt, InstalledPackageSource, LaunchedPrivilegedJob, ManagedComponent, PlannedAssetFingerprint, PlannedGithubOriginProof, PlannedPackageFingerprint, PrivilegedAction, PrivilegedActionRunner, PrivilegedCutoverContext, PrivilegedCutoverMode, PrivilegedJobId, PrivilegedJobLaunchState, PrivilegedJobStatus, PrivilegedJobStore, PrivilegedJobsError, PrivilegedMutationResult, PrivilegedPlanResolver, PrivilegedResultCode, PrivilegedRunnerFailure, ResolvedActionPlan, INSTALL_RECEIPT_SCHEMA_VERSION, MAX_PRIVILEGED_RECORD_BYTES, PRIVILEGED_JOB_SCHEMA_VERSION, }; use proxywarden_lib::process::{ known_service_path_matches_exact, service_path_matches_exact, KnownWindowsService, }; use proxywarden_lib::safe_fs; use proxywarden_lib::storage::StoragePaths; use serde_json::Value; use std::cell::{Cell, RefCell}; use std::ffi::OsString; use std::fs; #[cfg(windows)] use std::fs::{File, OpenOptions}; use std::io; #[cfg(windows)] use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; use std::sync::{mpsc, Arc, Mutex}; use std::thread; use uuid::Uuid; #[test] fn early_mode_accepts_only_exact_canonical_uuid_v4_arguments() { let id = Uuid::new_v4().hyphenated().to_string(); assert_eq!( parse_early_helper_arguments(Vec::::new()).expect("normal launch"), None ); assert_eq!( parse_early_helper_arguments([OsString::from("--other")]).expect("unreserved args"), None ); assert_eq!( parse_early_helper_arguments([OsString::from("--elevated-helper"), OsString::from(&id),]) .expect("exact helper args") .expect("helper id") .as_str(), id ); assert!(matches!( parse_early_helper_arguments([ OsString::from("--elevated-helper"), OsString::from(id.to_uppercase()), ]), Err(PrivilegedJobsError::InvalidJobId) )); for invalid in ["not-a-uuid", "00000000-0000-0000-0000-000000000000"] { assert!(matches!( parse_early_helper_arguments([ OsString::from("--elevated-helper"), OsString::from(invalid), ]), Err(PrivilegedJobsError::InvalidJobId) )); } assert!(matches!( parse_early_helper_arguments([ OsString::from("--elevated-helper"), OsString::from(&id), OsString::from("extra"), ]), Err(PrivilegedJobsError::InvalidArguments) )); } #[test] fn native_service_boundary_has_fixed_names_and_service_specific_paths() { let expected = Path::new(r"C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe"); assert_eq!(KnownWindowsService::Proxifyre.name(), "ProxiFyreService"); assert_eq!(KnownWindowsService::SingBox.name(), "ProxyWardenSingBox"); assert!(service_path_matches_exact( r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe""#, expected )); let bare = Path::new(r"C:\ProxyWarden\sing-box.exe"); assert!(service_path_matches_exact( r"c:\proxywarden\sing-box.exe", bare )); assert!(!service_path_matches_exact( r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe" --other"#, expected )); assert!(!service_path_matches_exact( r"C:\Program Files\ProxyWarden\components\other.exe", expected )); assert!(known_service_path_matches_exact( KnownWindowsService::Proxifyre, r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe" -displayname "ProxiFyre Service" -servicename "ProxiFyreService""#, expected, )); assert!(!known_service_path_matches_exact( KnownWindowsService::Proxifyre, r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe""#, expected, )); } #[test] fn request_is_typed_and_contains_no_paths_programs_arguments_or_service_names() { let fixture = Fixture::new("typed-request"); let launcher = RecordingLauncher::completed(); let launched = fixture.launch( PrivilegedAction::InstallProxifyre, &StaticResolver::new(plan(PrivilegedAction::InstallProxifyre)), &launcher, ); let json = serde_json::to_string(&launched.request).expect("serialize request"); assert!(json.contains("proxifyre.install")); assert!(!json.contains("C:\\")); assert!(!json.contains("serviceName")); assert!(!json.contains("program")); assert!(!json.contains("arguments")); let calls = launcher.calls.borrow(); assert_eq!(calls.len(), 1); assert_eq!(calls[0].0, fixture.executable); assert_eq!(calls[0].1, launched.request.job_id().as_str()); } #[test] fn schema_v2_actions_and_operation_fingerprints_are_typed_and_domain_separated() { assert_eq!(PRIVILEGED_JOB_SCHEMA_VERSION, 2); assert_eq!( serde_json::to_string(&PrivilegedAction::CutoverProxifyre).expect("cutover action"), "\"proxifyre.cutover\"" ); assert_eq!( serde_json::to_string(&PrivilegedAction::CleanupProxifyreQuarantine) .expect("cleanup action"), "\"proxifyre.cleanup-quarantine\"" ); let stop = plan(PrivilegedAction::StopProxifyre); let uninstall = plan(PrivilegedAction::UninstallProxifyre); let cutover_new = cutover_plan(PrivilegedCutoverMode::New); let cutover_recovery = cutover_plan(PrivilegedCutoverMode::Recovery); assert_ne!(stop.operation_fingerprint, uninstall.operation_fingerprint); assert_ne!( cutover_new.operation_fingerprint, cutover_recovery.operation_fingerprint ); for fingerprint in [ &stop.operation_fingerprint, &uninstall.operation_fingerprint, &cutover_new.operation_fingerprint, &cutover_recovery.operation_fingerprint, ] { assert_eq!(fingerprint.len(), 64); assert!(fingerprint.bytes().all(|byte| byte.is_ascii_hexdigit())); } } #[test] fn cutover_asset_contract_is_conditional_and_exactly_ordered() { let fixture = Fixture::new("cutover-assets"); let launcher = RecordingLauncher::completed(); for (action, value) in [ ( PrivilegedAction::CutoverProxifyre, cutover_plan(PrivilegedCutoverMode::New), ), ( PrivilegedAction::CutoverProxifyre, cutover_plan(PrivilegedCutoverMode::Recovery), ), (PrivilegedAction::CleanupProxifyreQuarantine, cleanup_plan()), ] { fixture.launch(action, &StaticResolver::new(value), &launcher); } let new_plan = cutover_plan(PrivilegedCutoverMode::New); assert_eq!( new_plan .package .as_ref() .expect("new cutover package") .assets .iter() .map(|asset| asset.component_id) .collect::>(), vec![ CatalogComponentId::Proxifyre, CatalogComponentId::WindowsPacketFilter, CatalogComponentId::VcRuntime, ] ); assert!(cutover_plan(PrivilegedCutoverMode::Recovery) .package .is_none()); assert!(cleanup_plan().package.is_none()); let mut recovery_with_package = cutover_plan(PrivilegedCutoverMode::Recovery); recovery_with_package.package = new_plan.package.clone(); let mut cleanup_with_package = cleanup_plan(); cleanup_with_package.package = new_plan.package; for (action, invalid) in [ (PrivilegedAction::CutoverProxifyre, recovery_with_package), ( PrivilegedAction::CleanupProxifyreQuarantine, cleanup_with_package, ), ] { assert!(matches!( launch_privileged_job( &fixture.store, action, &fixture.executable, &fixture.clock, &StaticResolver::new(invalid), &RecordingLauncher::completed(), ), Err(PrivilegedJobsError::InvalidRecord) )); } } #[test] fn cutover_operation_fingerprint_binds_startup_session_and_typed_cleanup_evidence() { let first_new = cutover_plan(PrivilegedCutoverMode::New); let mut second_context = first_new .cutover_context .clone() .expect("new cutover context"); second_context.initiating_startup_session_id = Some("44444444-4444-4444-8444-444444444444".to_string()); let second_new = ResolvedActionPlan::new( PrivilegedAction::CutoverProxifyre, first_new.package.clone(), first_new.inventory_fingerprint.clone(), first_new.configuration_fingerprint.clone(), Some(second_context), ); assert_ne!( first_new.operation_fingerprint, second_new.operation_fingerprint ); let cleanup = cleanup_plan(); let mut changed_context = cleanup.cutover_context.clone().expect("cleanup context"); changed_context .user_evidence .as_mut() .expect("typed user evidence") .startup_session_id = "55555555-5555-4555-8555-555555555555".to_string(); let changed_cleanup = ResolvedActionPlan::new( PrivilegedAction::CleanupProxifyreQuarantine, None, cleanup.inventory_fingerprint.clone(), cleanup.configuration_fingerprint.clone(), Some(changed_context), ); assert_ne!( cleanup.operation_fingerprint, changed_cleanup.operation_fingerprint ); let fixture = Fixture::new("cleanup-evidence"); fixture.launch( PrivilegedAction::CleanupProxifyreQuarantine, &StaticResolver::new(cleanup.clone()), &RecordingLauncher::completed(), ); let mut invalid_context = cleanup.cutover_context.expect("cleanup context"); invalid_context .user_evidence .as_mut() .expect("typed user evidence") .route_smoke_confirmed = false; let invalid = ResolvedActionPlan::new( PrivilegedAction::CleanupProxifyreQuarantine, None, "a".repeat(64), "e".repeat(64), Some(invalid_context), ); assert!(matches!( launch_privileged_job( &fixture.store, PrivilegedAction::CleanupProxifyreQuarantine, &fixture.executable, &fixture.clock, &StaticResolver::new(invalid), &RecordingLauncher::completed(), ), Err(PrivilegedJobsError::InvalidRecord) )); } #[test] fn sealed_recovery_probe_is_canonical_and_bound_to_exact_inventory() { let inventory = "a".repeat(64); let valid = recovery_probe_plan(&inventory); let fixture = Fixture::new("valid-recovery-probe"); fixture.launch( PrivilegedAction::CutoverProxifyre, &StaticResolver::new(valid.clone()), &RecordingLauncher::completed(), ); let mut wrong_hash_context = valid.cutover_context.clone().expect("probe context"); wrong_hash_context.evidence_fingerprint = "b".repeat(64); let wrong_hash = ResolvedActionPlan::new( PrivilegedAction::CutoverProxifyre, None, inventory.clone(), "e".repeat(64), Some(wrong_hash_context), ); let wrong_inventory = ResolvedActionPlan::new( PrivilegedAction::CutoverProxifyre, None, "c".repeat(64), "e".repeat(64), valid.cutover_context, ); for (case, invalid) in [ ("wrong-hash", wrong_hash), ("wrong-inventory", wrong_inventory), ] { let fixture = Fixture::new(case); assert!(matches!( launch_privileged_job( &fixture.store, PrivilegedAction::CutoverProxifyre, &fixture.executable, &fixture.clock, &StaticResolver::new(invalid), &RecordingLauncher::completed(), ), Err(PrivilegedJobsError::InvalidRecord) )); } } #[test] fn strict_schema_and_size_limit_reject_before_runner() { let fixture = Fixture::new("strict-schema"); let resolver = StaticResolver::new(plan(PrivilegedAction::StartProxifyre)); let launched = fixture.launch( PrivilegedAction::StartProxifyre, &resolver, &RecordingLauncher::completed(), ); let request_path = fixture.request_path(launched.request.job_id()); let mut json: Value = serde_json::from_slice(&fs::read(&request_path).expect("read request")).expect("json"); json.as_object_mut().expect("request object").insert( "unexpectedPath".to_string(), Value::String("C:\\evil".to_string()), ); fs::write(&request_path, serde_json::to_vec(&json).expect("encode")).expect("mutate request"); let runner = CountingRunner::success(); assert!(matches!( fixture.execute(&launched, &resolver, &runner), Err(PrivilegedJobsError::InvalidRecord) )); assert_eq!(runner.calls.get(), 0); let second = fixture.launch( PrivilegedAction::StartProxifyre, &resolver, &RecordingLauncher::completed(), ); fs::write( fixture.request_path(second.request.job_id()), vec![b' '; MAX_PRIVILEGED_RECORD_BYTES as usize + 1], ) .expect("oversize request"); assert!(matches!( fixture.execute(&second, &resolver, &runner), Err(PrivilegedJobsError::RecordTooLarge) )); assert_eq!(runner.calls.get(), 0); } #[test] fn expired_and_future_dated_requests_are_rejected_without_claim() { for (name, created_at, expected) in [ ("expired", 699_u64, "expired"), ("future", 1_031_u64, "future"), ] { let fixture = Fixture::new(name); let resolver = StaticResolver::new(plan(PrivilegedAction::StopProxifyre)); let launched = fixture.launch( PrivilegedAction::StopProxifyre, &resolver, &RecordingLauncher::completed(), ); rewrite_request_field( &fixture.request_path(launched.request.job_id()), "createdAtEpochSeconds", Value::from(created_at), ); let error = fixture .execute(&launched, &resolver, &CountingRunner::success()) .expect_err("invalid time"); assert!( (expected == "expired" && matches!(error, PrivilegedJobsError::Expired)) || (expected == "future" && matches!(error, PrivilegedJobsError::FutureDated)) ); assert!(!fixture.claim_path(launched.request.job_id()).exists()); } } #[test] fn non_elevated_helper_does_not_create_system_state_or_call_runner() { let fixture = Fixture::new("not-elevated"); let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox)); let launched = fixture.launch( PrivilegedAction::StartSingBox, &resolver, &RecordingLauncher::completed(), ); let runner = CountingRunner::success(); let error = execute_privileged_job( &fixture.store, launched.request.job_id(), &fixture.clock, &FixedElevation(false), &resolver, &runner, ) .expect_err("non-elevated helper must fail"); assert!(matches!(error, PrivilegedJobsError::NotElevated)); assert_eq!(runner.calls.get(), 0); assert!(!fixture.system_dir().exists()); } #[test] fn same_job_is_consumed_once_and_result_identity_is_validated() { let fixture = Fixture::new("replay"); let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox)); let launched = fixture.launch( PrivilegedAction::StartSingBox, &resolver, &RecordingLauncher::completed(), ); let runner = CountingRunner::success(); let result = fixture .execute(&launched, &resolver, &runner) .expect("first execution"); assert_eq!(result.status, PrivilegedJobStatus::Succeeded); assert_eq!(result.code, PrivilegedResultCode::Completed); assert_eq!(runner.calls.get(), 1); assert!(matches!( fixture.execute(&launched, &resolver, &runner), Err(PrivilegedJobsError::Replay) )); assert_eq!(runner.calls.get(), 1); assert_eq!( fixture .store .read_result(&launched.request) .expect("validated result"), result ); } #[test] fn cutover_success_and_failure_codes_are_stable_and_redacted() { for (index, code) in [ PrivilegedResultCode::CutoverAwaitingNextStart, PrivilegedResultCode::CutoverQuarantinePending, PrivilegedResultCode::CutoverRolledBack, PrivilegedResultCode::CutoverCleanupPending, PrivilegedResultCode::CutoverComplete, ] .into_iter() .enumerate() { let fixture = Fixture::new(&format!("cc-{index}")); let action = PrivilegedAction::CutoverProxifyre; let resolver = StaticResolver::new(cutover_plan(PrivilegedCutoverMode::Recovery)); let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed()); let runner = CountingRunner { calls: Cell::new(0), result: Ok(PrivilegedMutationResult { activation: None, changed: true, reboot_required: false, result_code: Some(code), }), }; let result = fixture .execute(&launched, &resolver, &runner) .expect("stable cutover result"); assert_eq!(result.code, code); assert!(!result.message.contains("C:\\")); assert!(!result.message.contains(&"d".repeat(64))); } for (index, (failure, code)) in [ ( PrivilegedRunnerFailure::CutoverRecoveryRequired, PrivilegedResultCode::CutoverRecoveryRequired, ), ( PrivilegedRunnerFailure::CutoverStateConflict, PrivilegedResultCode::CutoverStateConflict, ), ( PrivilegedRunnerFailure::CutoverIdentityRejected, PrivilegedResultCode::CutoverIdentityRejected, ), ] .into_iter() .enumerate() { let fixture = Fixture::new(&format!("cf-{index}")); let action = PrivilegedAction::CutoverProxifyre; let resolver = StaticResolver::new(cutover_plan(PrivilegedCutoverMode::Recovery)); let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed()); let result = fixture .execute(&launched, &resolver, &CountingRunner::failure(failure)) .expect("stable failure result"); assert_eq!(result.code, code); assert_eq!(result.status, PrivilegedJobStatus::Failed); } } #[test] fn precreated_result_blocks_before_claim_and_runner() { let fixture = Fixture::new("precreated-result"); let resolver = StaticResolver::new(plan(PrivilegedAction::StopSingBox)); let launched = fixture.launch( PrivilegedAction::StopSingBox, &resolver, &RecordingLauncher::completed(), ); fixture.prepare_portable_system_dir(); let result_path = fixture.result_path(launched.request.job_id()); fs::write(&result_path, b"{}").expect("precreate result"); safe_fs::protect_path_for_owner_admin_system(&result_path).expect("protect result"); let runner = CountingRunner::success(); assert!(matches!( fixture.execute(&launched, &resolver, &runner), Err(PrivilegedJobsError::ResultAlreadyExists) )); assert_eq!(runner.calls.get(), 0); assert!(!fixture.claim_path(launched.request.job_id()).exists()); } #[test] fn uac_cancel_removes_only_held_request_and_never_creates_system_state() { let fixture = Fixture::new("uac-cancel"); let resolver = StaticResolver::new(plan(PrivilegedAction::InstallSingBox)); let launched = fixture.launch( PrivilegedAction::InstallSingBox, &resolver, &RecordingLauncher::canceled(), ); assert_eq!(launched.state, PrivilegedJobLaunchState::Canceled); assert!(!fixture.request_path(launched.request.job_id()).exists()); assert!(!fixture.system_dir().exists()); } #[test] fn indeterminate_wait_keeps_request_for_the_already_started_helper() { let fixture = Fixture::new("indeterminate-wait"); let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox)); let launched = fixture.launch( PrivilegedAction::StartSingBox, &resolver, &RecordingLauncher::indeterminate(), ); assert_eq!(launched.state, PrivilegedJobLaunchState::Indeterminate); assert!(fixture.request_path(launched.request.job_id()).exists()); assert!(!fixture.system_dir().exists()); fixture .execute(&launched, &resolver, &CountingRunner::success()) .expect("helper terminal result"); wait_until_request_is_removed(&fixture, launched.request.job_id()); } #[cfg(windows)] #[test] fn indeterminate_keeper_blocks_request_write_delete_and_rename_until_terminal_result() { let fixture = Fixture::new("indeterminate-keeper"); let action = PrivilegedAction::StartSingBox; let resolver = StaticResolver::new(plan(action)); let launched = fixture.launch(action, &resolver, &RecordingLauncher::indeterminate()); let request = fixture.request_path(launched.request.job_id()); let renamed = request.with_extension("moved.json"); assert!(fs::write(&request, b"{}").is_err()); assert!(fs::remove_file(&request).is_err()); assert!(fs::rename(&request, &renamed).is_err()); assert!(request.exists()); assert!(!renamed.exists()); fixture .execute(&launched, &resolver, &CountingRunner::success()) .expect("helper terminal result"); wait_until_request_is_removed(&fixture, launched.request.job_id()); } #[cfg(windows)] #[test] fn parent_lease_blocks_tamper_while_helper_reads_and_executes() { let fixture = Fixture::new("request-lease"); let action = PrivilegedAction::StartProxifyre; let resolver = StaticResolver::new(plan(action)); let runner = CountingRunner::success(); let tamper_blocked = Cell::new(false); let launcher = InlineHelperLauncher { store: &fixture.store, clock: &fixture.clock, resolver: &resolver, runner: &runner, request_dir: fixture.paths.privileged_jobs_dir.clone(), tamper_blocked: &tamper_blocked, }; let launched = fixture.launch(action, &resolver, &launcher); assert!(tamper_blocked.get()); let helper_result = fixture .store .read_result(&launched.request) .expect("inline helper result"); assert_eq!( helper_result.status, PrivilegedJobStatus::Succeeded, "{helper_result:?}" ); assert_eq!(runner.calls.get(), 1); assert!(matches!( launched.state, PrivilegedJobLaunchState::Completed { exit_code: 0 } )); } #[test] fn helper_rebuilds_plan_and_blocks_changed_inventory_before_runner() { let fixture = Fixture::new("plan-change-helper"); let original = plan(PrivilegedAction::StartProxifyre); let launched = fixture.launch( PrivilegedAction::StartProxifyre, &StaticResolver::new(original.clone()), &RecordingLauncher::completed(), ); let mut changed = original; changed.inventory_fingerprint = "b".repeat(64); let runner = CountingRunner::success(); let result = fixture .execute(&launched, &StaticResolver::new(changed), &runner) .expect("structured rejected result"); assert_eq!(result.status, PrivilegedJobStatus::Failed); assert_eq!(result.code, PrivilegedResultCode::PreconditionFailed); assert_eq!(runner.calls.get(), 0); } #[test] fn elevated_cutover_mode_mismatch_is_stable_and_never_calls_runner() { let fixture = Fixture::new("cutover-mode-change"); let launched = fixture.launch( PrivilegedAction::CutoverProxifyre, &StaticResolver::new(cutover_plan(PrivilegedCutoverMode::New)), &RecordingLauncher::completed(), ); let runner = CountingRunner::success(); let result = fixture .execute( &launched, &StaticResolver::new(cutover_plan(PrivilegedCutoverMode::Recovery)), &runner, ) .expect("structured cutover mode rejection"); assert_eq!(result.status, PrivilegedJobStatus::Failed); assert_eq!(result.code, PrivilegedResultCode::CutoverStateConflict); assert_eq!(runner.calls.get(), 0); } #[test] fn client_rebuilds_plan_before_launch_and_removes_stale_request() { let fixture = Fixture::new("plan-change-client"); let action = PrivilegedAction::InstallProxifyre; let mut changed = plan(action); changed.inventory_fingerprint = "c".repeat(64); let resolver = SequenceResolver::new([plan(action), changed]); let launcher = RecordingLauncher::completed(); assert!(matches!( launch_privileged_job( &fixture.store, action, &fixture.executable, &fixture.clock, &resolver, &launcher, ), Err(PrivilegedJobsError::PlanChanged) )); assert!(launcher.calls.borrow().is_empty()); assert!(fs::read_dir(&fixture.paths.privileged_jobs_dir) .expect("request dir") .next() .is_none()); } #[test] fn client_removes_request_when_commit_plan_cannot_be_rebuilt() { let fixture = Fixture::new("client-plan-error"); let action = PrivilegedAction::StartProxifyre; let resolver = FailOnSecondResolve { plan: plan(action), calls: Cell::new(0), }; let launcher = RecordingLauncher::completed(); assert!(launch_privileged_job( &fixture.store, action, &fixture.executable, &fixture.clock, &resolver, &launcher, ) .is_err()); assert_eq!(launcher.calls.borrow().len(), 0); let requests = fs::read_dir(&fixture.paths.privileged_jobs_dir) .expect("request directory") .filter_map(Result::ok) .filter(|entry| { entry .file_name() .to_string_lossy() .ends_with(".request.json") }) .count(); assert_eq!(requests, 0); } #[test] fn store_owned_helper_binding_rejects_another_absolute_executable_before_launcher() { let fixture = Fixture::new("helper-binding"); let action = PrivilegedAction::StartProxifyre; let launcher = RecordingLauncher::completed(); let other_executable = fixture .executable .parent() .expect("app root") .join("CopiedProxyWarden.exe"); assert!(matches!( launch_privileged_job( &fixture.store, action, &other_executable, &fixture.clock, &StaticResolver::new(plan(action)), &launcher, ), Err(PrivilegedJobsError::InvalidArguments) )); assert!(launcher.calls.borrow().is_empty()); assert!(!fixture.paths.privileged_jobs_dir.exists()); } #[test] fn package_source_contract_is_bundled_for_install_and_cache_for_update() { let fixture = Fixture::new("package-source"); let bad_install = plan_with_source( PrivilegedAction::InstallProxifyre, InstalledPackageSource::Cache, ); let bad_update = plan_with_source( PrivilegedAction::UpdateProxifyre, InstalledPackageSource::Bundled, ); for (action, bad_plan) in [ (PrivilegedAction::InstallProxifyre, bad_install), (PrivilegedAction::UpdateProxifyre, bad_update), ] { assert!(matches!( launch_privileged_job( &fixture.store, action, &fixture.executable, &fixture.clock, &StaticResolver::new(bad_plan), &RecordingLauncher::completed(), ), Err(PrivilegedJobsError::InvalidRecord) )); } let install = PrivilegedAction::InstallProxifyre; let mut wrong_component = plan(install); wrong_component .package .as_mut() .expect("install package") .assets[1] .component_id = CatalogComponentId::Winsw; let mut wrong_asset_version = plan(install); wrong_asset_version .package .as_mut() .expect("install package") .assets[1] .version = "latest".to_string(); let mut reordered = plan(install); reordered .package .as_mut() .expect("install package") .assets .swap(0, 1); for invalid in [wrong_component, wrong_asset_version, reordered] { assert!(matches!( launch_privileged_job( &fixture.store, install, &fixture.executable, &fixture.clock, &StaticResolver::new(invalid), &RecordingLauncher::completed(), ), Err(PrivilegedJobsError::InvalidRecord) )); } let update = PrivilegedAction::UpdateSingBox; let mut invalid_origin_plans = Vec::new(); for mutate in [ |proof: &mut PlannedGithubOriginProof| proof.release_id = 0, |proof: &mut PlannedGithubOriginProof| proof.asset_id = 0, |proof: &mut PlannedGithubOriginProof| proof.stable_tag = "v9.9.9".to_string(), |proof: &mut PlannedGithubOriginProof| proof.asset_name = "other.zip".to_string(), |proof: &mut PlannedGithubOriginProof| proof.repository = "attacker/repo".to_string(), |proof: &mut PlannedGithubOriginProof| proof.size += 1, |proof: &mut PlannedGithubOriginProof| proof.sha256_from_api = "f".repeat(64), ] { let mut invalid = plan(update); mutate( invalid .package .as_mut() .and_then(|package| package.origin_proof.as_mut()) .expect("cache origin proof"), ); invalid_origin_plans.push(invalid); } for invalid in invalid_origin_plans { assert!(matches!( launch_privileged_job( &fixture.store, update, &fixture.executable, &fixture.clock, &StaticResolver::new(invalid), &RecordingLauncher::completed(), ), Err(PrivilegedJobsError::InvalidRecord) )); } let mut ambiguous_cache = plan(update); ambiguous_cache .package .as_mut() .expect("update package") .assets .push(PlannedAssetFingerprint { component_id: CatalogComponentId::SingBox, version: "1.2.3".to_string(), name: "second-component.zip".to_string(), sha256: "f".repeat(64), size: 43, }); let launcher = RecordingLauncher::completed(); assert!(matches!( launch_privileged_job( &fixture.store, update, &fixture.executable, &fixture.clock, &StaticResolver::new(ambiguous_cache), &launcher, ), Err(PrivilegedJobsError::InvalidRecord) )); assert!(launcher.calls.borrow().is_empty()); } #[test] fn helper_blocks_cached_origin_proof_id_swap_before_runner() { let fixture = Fixture::new("origin-proof-swap"); let action = PrivilegedAction::UpdateProxifyre; let initial = plan(action); let launched = fixture.launch( action, &StaticResolver::new(initial.clone()), &RecordingLauncher::completed(), ); let mut changed = initial; changed .package .as_mut() .and_then(|package| package.origin_proof.as_mut()) .expect("cache origin proof") .release_id += 1; let runner = CountingRunner::success(); let result = fixture .execute(&launched, &StaticResolver::new(changed), &runner) .expect("structured precondition failure"); assert_eq!(result.code, PrivilegedResultCode::PreconditionFailed); assert_eq!(runner.calls.get(), 0); } #[test] fn helper_revalidates_ttl_after_resolver_and_returns_fixed_failure_without_runner() { let fixture = Fixture::new("ttl-after-resolver"); let action = PrivilegedAction::StartProxifyre; let original = plan(action); let launched = fixture.launch( action, &StaticResolver::new(original.clone()), &RecordingLauncher::completed(), ); let execution_clock = MutableClock::new(1_000); let resolver = ExpireDuringResolve { plan: original, clock: &execution_clock, }; let runner = CountingRunner::success(); let result = execute_privileged_job( &fixture.store, launched.request.job_id(), &execution_clock, &FixedElevation(true), &resolver, &runner, ) .expect("fixed terminal failure"); assert_eq!(result.status, PrivilegedJobStatus::Failed); assert_eq!(result.code, PrivilegedResultCode::PreconditionFailed); assert_eq!(runner.calls.get(), 0); } #[cfg(windows)] #[test] fn failed_final_result_replacement_preserves_running_bytes_exactly() { let fixture = Fixture::new("atomic-final-result"); let action = PrivilegedAction::StopSingBox; let resolver = StaticResolver::new(plan(action)); let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed()); let runner = ResultPinningRunner::new(fixture.result_path(launched.request.job_id())); assert!(matches!( fixture.execute(&launched, &resolver, &runner), Err(PrivilegedJobsError::Io(_)) )); let running_bytes = runner.release().expect("captured running bytes"); let persisted = fs::read(fixture.result_path(launched.request.job_id())) .expect("running result remains readable"); assert_eq!(persisted, running_bytes); let persisted_json: Value = serde_json::from_slice(&persisted).expect("result json"); assert_eq!(persisted_json["status"], "running"); } #[cfg(windows)] #[test] fn result_watcher_open_read_lease_allows_terminal_atomic_replace() { let fixture = Arc::new(Fixture::new("result-watcher-replace")); let action = PrivilegedAction::StartProxifyre; let resolver = StaticResolver::new(plan(action)); let launched = fixture.launch(action, &resolver, &RecordingLauncher::completed()); let (entered_tx, entered_rx) = mpsc::channel(); let (release_tx, release_rx) = mpsc::channel(); let runner = BlockingRunner { entered: Mutex::new(Some(entered_tx)), release: Mutex::new(release_rx), }; let fixture_thread = Arc::clone(&fixture); let resolver_thread = resolver.clone(); let job_id = launched.request.job_id().clone(); let handle = thread::spawn(move || { execute_privileged_job( &fixture_thread.store, &job_id, &fixture_thread.clock, &FixedElevation(true), &resolver_thread, &runner, ) }); entered_rx .recv() .expect("runner entered after Running write"); let (observed_running, mut read_lease) = fixture .store .read_result_with_open_lease_for_tests(&launched.request) .expect("watcher reads Running and keeps the bounded read handle"); assert_eq!(observed_running.status, PrivilegedJobStatus::Running); release_tx.send(()).expect("release runner"); let terminal = handle .join() .expect("helper thread") .expect("ReplaceFileW succeeds while watcher read handle remains open"); assert_eq!(terminal.status, PrivilegedJobStatus::Succeeded); assert_eq!( fixture .store .read_result(&launched.request) .expect("new terminal result is visible"), terminal ); let mut original_running_bytes = Vec::new(); read_lease .seek(std::io::SeekFrom::Start(0)) .expect("rewind old result handle"); read_lease .read_to_end(&mut original_running_bytes) .expect("old open handle still references the replaced Running file"); let original_running: Value = serde_json::from_slice(&original_running_bytes).expect("old Running json"); assert_eq!(original_running["status"], "running"); } #[test] fn results_are_fixed_and_do_not_echo_plan_fingerprints_or_paths() { let fixture = Fixture::new("redaction"); let resolver = StaticResolver::new(plan(PrivilegedAction::UpdateProxifyre)); let launched = fixture.launch( PrivilegedAction::UpdateProxifyre, &resolver, &RecordingLauncher::completed(), ); let result = fixture .execute( &launched, &resolver, &CountingRunner::failure(PrivilegedRunnerFailure::PackageVerificationFailed), ) .expect("structured failure"); let json = serde_json::to_string(&result).expect("result json"); assert_eq!(result.code, PrivilegedResultCode::PackageVerificationFailed); assert!(!json.contains(&"a".repeat(64))); assert!(!json.contains("C:\\")); assert!(!json.contains("https://")); } #[test] fn system_directory_is_never_blessed_when_precreated_by_issuer() { let fixture = Fixture::new("foreign-system"); let resolver = StaticResolver::new(plan(PrivilegedAction::StartSingBox)); let launched = fixture.launch( PrivilegedAction::StartSingBox, &resolver, &RecordingLauncher::completed(), ); let system = fixture.system_dir(); fs::create_dir_all(&system).expect("foreign system dir"); let sentinel = system.join("sentinel.txt"); fs::write(&sentinel, b"issuer-owned").expect("sentinel"); assert!(fixture .execute(&launched, &resolver, &CountingRunner::success()) .is_err()); assert_eq!( fs::read(&sentinel).expect("unchanged sentinel"), b"issuer-owned" ); assert!(!fixture.claim_path(launched.request.job_id()).exists()); } #[test] fn production_system_layout_is_outside_programdata_and_under_the_verified_app_root() { let fixture = Fixture::new("production-system-layout"); let app_root = fixture.executable.parent().expect("app root"); let store = PrivilegedJobStore::from_storage_paths_with_verified_app_root_for_tests( &fixture.paths, app_root, &fixture.executable, ); let system = store.system_directory_for_tests(); assert_eq!(system, app_root.join(".proxywarden-privileged-jobs")); assert!(!system.starts_with(&fixture.paths.root)); assert!(!system.starts_with(&fixture.paths.privileged_jobs_dir)); } #[test] fn global_lifecycle_lock_serializes_different_jobs() { let fixture = Arc::new(Fixture::new("global-lock")); let action = PrivilegedAction::StartProxifyre; let resolver = StaticResolver::new(plan(action)); let first = fixture.launch(action, &resolver, &RecordingLauncher::completed()); let second = fixture.launch(action, &resolver, &RecordingLauncher::completed()); let (entered_tx, entered_rx) = mpsc::channel(); let (release_tx, release_rx) = mpsc::channel(); let blocking = BlockingRunner { entered: Mutex::new(Some(entered_tx)), release: Mutex::new(release_rx), }; let fixture_thread = Arc::clone(&fixture); let resolver_thread = resolver.clone(); let first_id = first.request.job_id().clone(); let handle = thread::spawn(move || { execute_privileged_job( &fixture_thread.store, &first_id, &fixture_thread.clock, &FixedElevation(true), &resolver_thread, &blocking, ) }); entered_rx.recv().expect("first runner entered"); assert!(matches!( fixture.execute(&second, &resolver, &CountingRunner::success()), Err(PrivilegedJobsError::LifecycleBusy) )); release_tx.send(()).expect("release first runner"); assert!(handle.join().expect("helper thread").is_ok()); } #[test] fn install_receipt_schema_rejects_wrong_root_traversal_hash_and_unknown_fields() { let fixture = Fixture::new("receipt"); let root = CanonicalComponentRoot::from_current_exe_for_tests( &fixture.executable, ManagedComponent::Proxifyre, ) .expect("canonical root"); let receipt = valid_receipt(&root); validate_install_receipt(&root, &receipt).expect("valid receipt"); let mut wrong = receipt.clone(); wrong.install_root.push_str("-other"); assert!(matches!( validate_install_receipt(&root, &wrong), Err(PrivilegedJobsError::InvalidReceipt) )); let mut traversal = receipt.clone(); traversal.installed_files[0].relative_path = "..\\evil.exe".to_string(); assert!(validate_install_receipt(&root, &traversal).is_err()); let mut uppercase_hash = receipt.clone(); uppercase_hash.asset.sha256 = "A".repeat(64); assert!(validate_install_receipt(&root, &uppercase_hash).is_err()); for invalid_version in ["", "running", "1.02", "1.2.3.4.5"] { let mut invalid = receipt.clone(); invalid.version = invalid_version.to_string(); assert!( validate_install_receipt(&root, &invalid).is_err(), "version {invalid_version:?}" ); } for invalid_path in [ "CON", "con.txt", "PRN.json", "AUX", "NUL.log", "CONIN$", "conout$.txt", "CLOCK$.json", "COM1.exe", "COM¹.txt", "LPT9.txt", "folder.\\file.exe", "folder \\file.exe", "bad.exe", "bad|name.exe", "bad?name.exe", "bad*name.exe", "bad\"name.exe", ] { let mut invalid = receipt.clone(); invalid.installed_files[0].relative_path = invalid_path.to_string(); assert!( validate_install_receipt(&root, &invalid).is_err(), "path {invalid_path:?}" ); } let mut invalid_asset = receipt.clone(); invalid_asset.asset.name = "bundle?.zip".to_string(); assert!(validate_install_receipt(&root, &invalid_asset).is_err()); let mut unicode_case_duplicate = receipt.clone(); unicode_case_duplicate .installed_files .push(InstalledFileReceipt { relative_path: "BIN\\ÄPP.EXE".to_string(), sha256: "c".repeat(64), size: 7, }); unicode_case_duplicate.installed_files[0].relative_path = "bin\\äpp.exe".to_string(); assert!(validate_install_receipt(&root, &unicode_case_duplicate).is_err()); let mut json = serde_json::to_value(&receipt).expect("receipt json"); json.as_object_mut().expect("object").insert( "arbitraryCommand".to_string(), Value::String("cmd.exe".to_string()), ); assert!(serde_json::from_value::(json).is_err()); } #[test] fn copied_executable_in_user_writable_location_is_not_a_trusted_component_root() { let fixture = Fixture::new("untrusted-app-root"); fs::create_dir_all(fixture.executable.parent().expect("exe parent")).expect("app dir"); fs::write(&fixture.executable, b"not-an-installed-app").expect("copied exe"); assert!(CanonicalComponentRoot::from_current_exe( &fixture.executable, ManagedComponent::Proxifyre, ) .is_err()); } struct Fixture { root: PathBuf, paths: StoragePaths, store: PrivilegedJobStore, executable: PathBuf, clock: FixedClock, } impl Fixture { fn new(name: &str) -> Self { let root = std::env::temp_dir().join(format!( "proxywarden-privileged-{name}-{}", Uuid::new_v4().hyphenated() )); fs::create_dir_all(&root).expect("create fixture root"); let paths = StoragePaths::new(root.join("ProgramData")); let executable = root.join("Program Files").join("ProxyWarden.exe"); let store = PrivilegedJobStore::from_storage_paths_for_tests(&paths, &executable); Self { root, paths, store, executable, clock: FixedClock(1_000), } } fn launch( &self, action: PrivilegedAction, resolver: &dyn PrivilegedPlanResolver, launcher: &dyn ElevatedJobLauncher, ) -> LaunchedPrivilegedJob { launch_privileged_job( &self.store, action, &self.executable, &self.clock, resolver, launcher, ) .expect("launch job") } fn execute( &self, launched: &LaunchedPrivilegedJob, resolver: &dyn PrivilegedPlanResolver, runner: &dyn PrivilegedActionRunner, ) -> Result { execute_privileged_job( &self.store, launched.request.job_id(), &self.clock, &FixedElevation(true), resolver, runner, ) } fn request_path(&self, id: &PrivilegedJobId) -> PathBuf { self.paths .privileged_jobs_dir .join(format!("{}.request.json", id.as_str())) } fn system_dir(&self) -> PathBuf { self.paths.privileged_jobs_dir.join("system") } fn claim_path(&self, id: &PrivilegedJobId) -> PathBuf { self.system_dir() .join(format!("{}.claim.json", id.as_str())) } fn result_path(&self, id: &PrivilegedJobId) -> PathBuf { self.system_dir() .join(format!("{}.result.json", id.as_str())) } fn prepare_portable_system_dir(&self) { let system = self.system_dir(); fs::create_dir_all(&system).expect("system dir"); safe_fs::protect_path_for_owner_admin_system(&system).expect("protect system dir"); } } impl Drop for Fixture { fn drop(&mut self) { let _ = fs::remove_dir_all(&self.root); } } fn wait_until_request_is_removed(fixture: &Fixture, id: &PrivilegedJobId) { let request = fixture.request_path(id); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while request.exists() && std::time::Instant::now() < deadline { thread::sleep(std::time::Duration::from_millis(10)); } assert!(!request.exists(), "keeper did not release terminal request"); } #[derive(Clone, Copy)] struct FixedClock(u64); impl EpochClock for FixedClock { fn now_epoch_seconds(&self) -> u64 { self.0 } } struct MutableClock(Cell); impl MutableClock { fn new(value: u64) -> Self { Self(Cell::new(value)) } } impl EpochClock for MutableClock { fn now_epoch_seconds(&self) -> u64 { self.0.get() } } struct FixedElevation(bool); impl ElevationProbe for FixedElevation { fn is_elevated(&self) -> bool { self.0 } } #[derive(Clone)] struct StaticResolver { plan: ResolvedActionPlan, } impl StaticResolver { fn new(plan: ResolvedActionPlan) -> Self { Self { plan } } } impl PrivilegedPlanResolver for StaticResolver { fn resolve( &self, _action: PrivilegedAction, ) -> Result { Ok(self.plan.clone()) } } struct SequenceResolver { plans: RefCell>, } impl SequenceResolver { fn new(plans: impl IntoIterator) -> Self { let mut plans = plans.into_iter().collect::>(); plans.reverse(); Self { plans: RefCell::new(plans), } } } impl PrivilegedPlanResolver for SequenceResolver { fn resolve( &self, _action: PrivilegedAction, ) -> Result { self.plans .borrow_mut() .pop() .ok_or(PrivilegedRunnerFailure::PreconditionFailed) } } struct FailOnSecondResolve { plan: ResolvedActionPlan, calls: Cell, } impl PrivilegedPlanResolver for FailOnSecondResolve { fn resolve( &self, _action: PrivilegedAction, ) -> Result { let call = self.calls.get(); self.calls.set(call + 1); if call == 0 { Ok(self.plan.clone()) } else { Err(PrivilegedRunnerFailure::OperationFailed) } } } struct ExpireDuringResolve<'a> { plan: ResolvedActionPlan, clock: &'a MutableClock, } impl PrivilegedPlanResolver for ExpireDuringResolve<'_> { fn resolve( &self, _action: PrivilegedAction, ) -> Result { self.clock .0 .set(1_000 + proxywarden_lib::privileged_jobs::PRIVILEGED_JOB_TTL_SECONDS + 1); Ok(self.plan.clone()) } } struct CountingRunner { calls: Cell, result: Result, } impl CountingRunner { fn success() -> Self { Self { calls: Cell::new(0), result: Ok(PrivilegedMutationResult { activation: None, changed: true, reboot_required: false, result_code: None, }), } } fn failure(failure: PrivilegedRunnerFailure) -> Self { Self { calls: Cell::new(0), result: Err(failure), } } } impl PrivilegedActionRunner for CountingRunner { fn run( &self, _action: PrivilegedAction, _plan: &ResolvedActionPlan, ) -> Result { self.calls.set(self.calls.get() + 1); self.result.clone() } } #[cfg(windows)] struct ResultPinningRunner { result_path: PathBuf, running_bytes: RefCell>>, pin: RefCell>, } #[cfg(windows)] impl ResultPinningRunner { fn new(result_path: PathBuf) -> Self { Self { result_path, running_bytes: RefCell::new(None), pin: RefCell::new(None), } } fn release(&self) -> Option> { self.pin.borrow_mut().take(); self.running_bytes.borrow_mut().take() } } #[cfg(windows)] impl PrivilegedActionRunner for ResultPinningRunner { fn run( &self, _action: PrivilegedAction, _plan: &ResolvedActionPlan, ) -> Result { use std::os::windows::fs::OpenOptionsExt; let running = fs::read(&self.result_path).map_err(|_| PrivilegedRunnerFailure::OperationFailed)?; let mut options = OpenOptions::new(); options.read(true).share_mode(0); let pin = options .open(&self.result_path) .map_err(|_| PrivilegedRunnerFailure::OperationFailed)?; self.running_bytes.replace(Some(running)); self.pin.replace(Some(pin)); Ok(PrivilegedMutationResult { activation: None, changed: true, reboot_required: false, result_code: None, }) } } struct BlockingRunner { entered: Mutex>>, release: Mutex>, } impl PrivilegedActionRunner for BlockingRunner { fn run( &self, _action: PrivilegedAction, _plan: &ResolvedActionPlan, ) -> Result { if let Some(sender) = self.entered.lock().expect("entered lock").take() { sender.send(()).expect("signal entered"); } self.release .lock() .expect("release lock") .recv() .expect("release signal"); Ok(PrivilegedMutationResult { activation: None, changed: true, reboot_required: false, result_code: None, }) } } struct RecordingLauncher { outcome: ElevatedLaunchOutcome, calls: RefCell>, } impl RecordingLauncher { fn completed() -> Self { Self { outcome: ElevatedLaunchOutcome::Completed { exit_code: 0 }, calls: RefCell::new(Vec::new()), } } fn canceled() -> Self { Self { outcome: ElevatedLaunchOutcome::Canceled, calls: RefCell::new(Vec::new()), } } fn indeterminate() -> Self { Self { outcome: ElevatedLaunchOutcome::Indeterminate, calls: RefCell::new(Vec::new()), } } } impl ElevatedJobLauncher for RecordingLauncher { fn launch( &self, executable: &Path, job_id: &PrivilegedJobId, ) -> io::Result { self.calls .borrow_mut() .push((executable.to_path_buf(), job_id.as_str().to_string())); Ok(self.outcome) } } #[cfg(windows)] struct InlineHelperLauncher<'a> { store: &'a PrivilegedJobStore, clock: &'a dyn EpochClock, resolver: &'a dyn PrivilegedPlanResolver, runner: &'a dyn PrivilegedActionRunner, request_dir: PathBuf, tamper_blocked: &'a Cell, } #[cfg(windows)] impl ElevatedJobLauncher for InlineHelperLauncher<'_> { fn launch( &self, _executable: &Path, job_id: &PrivilegedJobId, ) -> io::Result { let request = self .request_dir .join(format!("{}.request.json", job_id.as_str())); self.tamper_blocked.set(fs::write(&request, b"{}").is_err()); execute_privileged_job( self.store, job_id, self.clock, &FixedElevation(true), self.resolver, self.runner, ) .map_err(io::Error::other)?; Ok(ElevatedLaunchOutcome::Completed { exit_code: 0 }) } } fn plan(action: PrivilegedAction) -> ResolvedActionPlan { let package = action.requires_test_package().map(|source| { let components: &[CatalogComponentId] = match action { PrivilegedAction::InstallProxifyre => &[ CatalogComponentId::Proxifyre, CatalogComponentId::WindowsPacketFilter, CatalogComponentId::VcRuntime, ], PrivilegedAction::UpdateProxifyre => &[CatalogComponentId::Proxifyre], PrivilegedAction::InstallSingBox => { &[CatalogComponentId::SingBox, CatalogComponentId::Winsw] } PrivilegedAction::UpdateSingBox => &[CatalogComponentId::SingBox], _ => &[], }; let assets = components .iter() .enumerate() .map(|(index, component_id)| PlannedAssetFingerprint { component_id: *component_id, version: if index == 0 { "1.2.3".to_string() } else { format!("1.2.{}", index + 3) }, name: format!("component-{index}.zip"), sha256: format!("{:x}", index + 13).repeat(64)[..64].to_string(), size: 42 + index as u64, }) .collect::>(); let asset = assets.first().expect("package asset"); let origin_proof = (source == InstalledPackageSource::Cache).then(|| PlannedGithubOriginProof { repository: match action.component() { ManagedComponent::Proxifyre => "wiresock/proxifyre", ManagedComponent::SingBox => "SagerNet/sing-box", } .to_string(), stable_tag: "v1.2.3".to_string(), release_id: 11, asset_id: 22, asset_name: asset.name.clone(), size: asset.size, sha256_from_api: asset.sha256.clone(), }); PlannedPackageFingerprint { source, version: "1.2.3".to_string(), assets, origin_proof, } }); ResolvedActionPlan::new(action, package, "a".repeat(64), "e".repeat(64), None) } fn cutover_plan(mode: PrivilegedCutoverMode) -> ResolvedActionPlan { let package = (mode == PrivilegedCutoverMode::New).then(|| { plan(PrivilegedAction::InstallProxifyre) .package .expect("bundle") }); ResolvedActionPlan::new( PrivilegedAction::CutoverProxifyre, package, "a".repeat(64), "e".repeat(64), Some(PrivilegedCutoverContext { mode, cutover_id: (mode == PrivilegedCutoverMode::Recovery) .then(|| "11111111-1111-4111-8111-111111111111".to_string()), initiating_startup_session_id: (mode == PrivilegedCutoverMode::New) .then(|| "22222222-2222-4222-8222-222222222222".to_string()), evidence_fingerprint: "d".repeat(64), user_evidence: None, }), ) } fn recovery_probe_plan(inventory_fingerprint: &str) -> ResolvedActionPlan { ResolvedActionPlan::new( PrivilegedAction::CutoverProxifyre, None, inventory_fingerprint.to_string(), "e".repeat(64), Some(PrivilegedCutoverContext { mode: PrivilegedCutoverMode::Recovery, cutover_id: None, initiating_startup_session_id: None, evidence_fingerprint: cutover_recovery_probe_fingerprint(inventory_fingerprint), user_evidence: None, }), ) } fn cleanup_plan() -> ResolvedActionPlan { ResolvedActionPlan::new( PrivilegedAction::CleanupProxifyreQuarantine, None, "a".repeat(64), "e".repeat(64), Some(PrivilegedCutoverContext { mode: PrivilegedCutoverMode::Cleanup, cutover_id: Some("11111111-1111-4111-8111-111111111111".to_string()), evidence_fingerprint: "c".repeat(64), initiating_startup_session_id: None, user_evidence: Some(ComponentCutoverUserEvidence { schema_version: CUTOVER_USER_EVIDENCE_SCHEMA_VERSION, cutover_id: "11111111-1111-4111-8111-111111111111".to_string(), startup_session_id: "33333333-3333-4333-8333-333333333333".to_string(), current_inventory_fingerprint: "a".repeat(64), route_smoke_confirmed: true, observed_at_epoch_seconds: 700, confirmed_at_epoch_seconds: Some(701), }), }), ) } fn plan_with_source( action: PrivilegedAction, source: InstalledPackageSource, ) -> ResolvedActionPlan { let mut value = plan(action); value.package.as_mut().expect("package action").source = source; value } trait TestActionPackage { fn requires_test_package(self) -> Option; } impl TestActionPackage for PrivilegedAction { fn requires_test_package(self) -> Option { match self { Self::InstallProxifyre | Self::InstallSingBox => Some(InstalledPackageSource::Bundled), Self::UpdateProxifyre | Self::UpdateSingBox => Some(InstalledPackageSource::Cache), _ => None, } } } fn rewrite_request_field(path: &Path, name: &str, value: Value) { let mut json: Value = serde_json::from_slice(&fs::read(path).expect("read request")).expect("request json"); json.as_object_mut() .expect("request object") .insert(name.to_string(), value); fs::write(path, serde_json::to_vec(&json).expect("encode request")).expect("write request"); } fn valid_receipt(root: &CanonicalComponentRoot) -> InstallReceipt { InstallReceipt { schema_version: INSTALL_RECEIPT_SCHEMA_VERSION, manager: InstallReceiptManager::ProxyWarden, component: root.component(), install_root: root.path().display().to_string(), service_name: "ProxiFyreService".to_string(), version: "2.2.1".to_string(), source: InstalledPackageSource::Bundled, asset: InstalledAssetReceipt { name: "ProxiFyre.zip".to_string(), sha256: "a".repeat(64), size: 100, }, installed_files: vec![InstalledFileReceipt { relative_path: "ProxiFyre.exe".to_string(), sha256: "b".repeat(64), size: 10, }], installed_at: 1_000, windows_packet_filter: None, } }