Release v2.0.0
CI / Windows baseline (push) Canceled after 0s

This commit is contained in:
2026-09-11 18:29:33 +03:00
parent c5532f2087
commit c6b66a4ed7
8 changed files with 192 additions and 19 deletions
+1 -5
View File
@@ -1288,11 +1288,7 @@ fn trusted_release_candidate(
else {
return Err(ComponentPackagesError::InvalidReleaseMetadata);
};
if release.id == 0
|| release.draft
|| release.prerelease
|| release.assets.len() > 100
|| !release.tag_name.starts_with('v')
if release.id == 0 || release.draft || release.prerelease || !release.tag_name.starts_with('v')
{
return Err(ComponentPackagesError::InvalidReleaseMetadata);
}
@@ -157,6 +157,12 @@ pub fn recover_locked(storage: &JsonStorage) -> io::Result<()> {
});
}
for (path, bytes) in paths.iter().zip(snapshots) {
// Old elevated versions left some readable files owned by Administrators.
// An unchanged file is already restored; rewriting it can fail and strand
// an otherwise complete rollback, blocking every subsequent guarded read.
if optional_bytes(path)? == bytes {
continue;
}
match bytes {
Some(bytes) => safe_fs::write_restricted_atomic(path, &bytes)?,
None => remove_optional(path)?,
@@ -105,6 +105,38 @@ fn trusted_check_persists_redacted_state_recovers_backup_and_reports_stale() {
.expect("restored state must be JSON");
}
#[test]
fn large_multi_platform_release_selects_only_the_exact_windows_asset() {
let workspace = TestWorkspace::new();
let service = workspace.service();
let component = catalog_component(&service, ComponentId::SingBox);
let response = metadata_response(
&component,
"1.14.0",
"sing-box-1.14.0-windows-amd64.zip",
b"asset",
DigestMode::Trusted,
);
let mut metadata: Value = serde_json::from_slice(&response.body).unwrap();
// Real sing-box 1.14.0 publishes 167 assets. Response bytes are already bounded.
for index in 1..167 {
metadata["assets"].as_array_mut().unwrap().push(json!({
"id": 1000 + index,
"name": format!("other-platform-{index}.zip"),
"size": 10,
"browser_download_url": format!("https://github.com/SagerNet/sing-box/releases/download/v1.14.0/other-platform-{index}.zip")
}));
}
let transport = MockTransport::new(vec![ResponseSpec::ok(
serde_json::to_vec(&metadata).unwrap(),
)]);
let checked = service
.check_for_update(ComponentId::SingBox, CHECKED_AT, &transport)
.unwrap();
assert_eq!(checked.latest_known_version, "1.14.0");
assert_eq!(checked.trust, UpdateCheckTrust::Trusted);
}
#[test]
fn missing_or_malformed_digest_is_observable_and_never_downloaded() {
for (mode, expected) in [
@@ -38,6 +38,26 @@ fn interrupted_commit_restores_primary_and_backup_before_next_read() {
assert!(fixture.storage.read_profiles().unwrap().is_empty());
}
#[test]
fn rollback_leaves_unchanged_files_in_place() {
let fixture = Fixture::new();
let path = &fixture.storage.paths().profiles_file;
let transaction = ConfigurationTransaction::begin(&fixture.storage, None).unwrap();
// Deny replacement on Windows, while allowing the recovery code to read.
let mut options = fs::OpenOptions::new();
options.read(true);
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
options.share_mode(1);
}
let held = options.open(path).unwrap();
let modified = held.metadata().unwrap().modified().unwrap();
transaction.abort().unwrap();
assert_eq!(fs::metadata(path).unwrap().modified().unwrap(), modified);
assert!(read_guard(&fixture.storage).is_ok());
}
#[test]
fn shared_lock_rejects_second_writer_and_reader() {
let fixture = Fixture::new();
+84
View File
@@ -0,0 +1,84 @@
//! Explicit checks of real saved data/network. Never runs automatically in CI.
#![cfg(all(windows, debug_assertions))]
use proxywarden_lib::{
component_catalog::ComponentId,
component_packages::{
ComponentPackageService, NativePackageSignatureVerifier, ReqwestUpdateTransport,
},
configuration_transaction::read_guard,
privileged_jobs::{EpochClock, SystemEpochClock},
storage::{default_config_root, JsonStorage, StoragePaths},
subscription::{fetch_subscription_with_identity, SubscriptionFetchIdentity},
};
use std::path::PathBuf;
#[test]
#[ignore = "Real user data/network; requires an explicitly authorized operator action"]
fn installed_singbox_data_check() {
let action = std::env::var("PROXYWARDEN_LIVE_SINGBOX_ACTION")
.expect("set the explicitly authorized action");
let storage = JsonStorage::new(default_config_root());
match action.as_str() {
"recover" => {
let _guard = read_guard(&storage).expect("recover saved configuration");
println!("Configuration recovery succeeded");
}
"subscription" => {
let config = {
let _guard = read_guard(&storage).expect("read saved configuration");
storage
.read_local_singbox_config()
.expect("saved subscription settings")
};
let identity =
SubscriptionFetchIdentity::with_device_hwid(config.device_hwid.as_deref());
let result = fetch_subscription_with_identity(
config
.subscription_url
.as_deref()
.expect("saved subscription exists"),
&identity,
);
// Never include the subscription URL, response body, credentials or server names.
assert!(result.is_ok(), "Saved subscription fetch/parse failed");
let cache = result.unwrap();
assert!(
!cache.servers.is_empty(),
"subscription returned no servers"
);
println!(
"Saved subscription fetched and parsed: {} servers",
cache.servers.len()
);
}
"download" => {
let packages = ComponentPackageService::open(
PathBuf::from(r"C:\Program Files\ProxyWarden\bundled\components"),
&StoragePaths::default(),
)
.expect("installed offline catalog");
let transport = ReqwestUpdateTransport::new().expect("update transport");
let checked = packages
.check_for_update(
ComponentId::SingBox,
SystemEpochClock.now_epoch_seconds(),
&transport,
)
.expect("release metadata and independent digest");
println!(
"Latest release: {}; trust {:?}",
checked.latest_known_version, checked.trust
);
let package = packages
.download_checked_update(
ComponentId::SingBox,
&transport,
&NativePackageSignatureVerifier,
)
.expect("verified download into application cache");
println!("Downloaded and verified sing-box {}", package.version);
}
_ => panic!("unsupported explicit data-check action"),
}
}