diff --git a/app/igneum-app/src/config.rs b/app/igneum-app/src/config.rs index d55883c13..214ced9e5 100644 --- a/app/igneum-app/src/config.rs +++ b/app/igneum-app/src/config.rs @@ -500,3 +500,18 @@ mod tests { assert!(p.node_override_params.is_none()); } } + +#[cfg(test)] +mod fixture_tests { + /// `IGNEUM_TEST_SETTINGS= cargo test settings_fixture`: parses a real settings.json with this crate's + /// struct and prints what it read (6 October 2026: PC 1's copied file read as defaults; this names the field). + #[test] + fn settings_fixture_parses_when_given() { + let Ok(p) = std::env::var("IGNEUM_TEST_SETTINGS") else { return }; + let t = std::fs::read_to_string(&p).unwrap(); + match serde_json::from_str::(&t) { + Ok(s) => println!("parsed: address {} cards {} remote_jobs {} setup_done {}", s.address, s.cards.len(), s.remote_jobs, s.setup_done), + Err(e) => panic!("the crate refuses the file: {e}"), + } + } +} diff --git a/app/igneum-app/src/ota.rs b/app/igneum-app/src/ota.rs index b04d7fb88..7a7f12bee 100644 --- a/app/igneum-app/src/ota.rs +++ b/app/igneum-app/src/ota.rs @@ -184,7 +184,11 @@ impl Updater { if crate::platform::start_at_login_is_on() { let _ = crate::platform::set_start_at_login(true); } - firewall_first_run(shared); + // a measurement engine (--sweep) uses the installed app's node and asks for nothing: the rule is the + // installed app's (the dry run of 6 October 2026 raised a second UAC prompt from here) + if !shared.runtime.sweep_only { + firewall_first_run(shared); + } } u.failed_versions = std::fs::read_to_string(u.failed_path()).ok().and_then(|t| serde_json::from_str::>(&t).ok()).unwrap_or_default(); // the cached manifest: the rollback floor and the consensus override are known before the first check diff --git a/app/igneum-app/src/platform.rs b/app/igneum-app/src/platform.rs index a3e8e6941..181b2a0de 100644 --- a/app/igneum-app/src/platform.rs +++ b/app/igneum-app/src/platform.rs @@ -166,17 +166,26 @@ pub fn lock_permissions(path: &Path, dir: bool) { } #[cfg(windows)] { - let _ = dir; let user = std::env::var("USERNAME").unwrap_or_default(); if !user.is_empty() { - let _ = quiet(&mut Command::new(tool("icacls"))) - .arg(path) - .args(["/inheritance:r", "/grant:r", &format!("{user}:F")]) - .output(); + let _ = quiet(&mut Command::new(tool("icacls"))).arg(path).args(icacls_lock_args(dir, &user)).output(); } } } +/// The icacls arguments that lock a path to the user. A folder gets an INHERITABLE grant applied to everything +/// inside (`(OI)(CI)F`, `/T`): the old non-inheritable `user:F` cut the folder's inheritance and left any file that +/// was COPIED in before the engine started with no entry at all (6 October 2026, PC 1: a measurement engine's +/// settings.json, machine-id and wallet.json copied by a job read as nothing, so the engine ran on defaults with no +/// payout address; the engine's own files, written after the lock, got the creator's default DACL and hid it). +pub fn icacls_lock_args(dir: bool, user: &str) -> Vec { + if dir { + vec!["/inheritance:r".into(), "/grant:r".into(), format!("{user}:(OI)(CI)F"), "/T".into()] + } else { + vec!["/inheritance:r".into(), "/grant:r".into(), format!("{user}:F")] + } +} + /// Opens a URL in the default browser (the fallback when no window host runs). pub fn open_url(url: &str) { #[cfg(target_os = "macos")] @@ -500,6 +509,17 @@ pub fn quiet(cmd: &mut Command) -> &mut Command { cmd } +#[cfg(test)] +mod lock_tests { + #[test] + fn a_locked_folder_grants_the_user_inheritably_and_covers_what_is_inside() { + let d = super::icacls_lock_args(true, "Admin"); + assert_eq!(d, vec!["/inheritance:r", "/grant:r", "Admin:(OI)(CI)F", "/T"]); + let f = super::icacls_lock_args(false, "Admin"); + assert_eq!(f, vec!["/inheritance:r", "/grant:r", "Admin:F"]); + } +} + #[cfg(test)] mod tests { #[test] diff --git a/relay/playbooks/ember-tune-pc1.ps1 b/relay/playbooks/ember-tune-pc1.ps1 index fd0786c99..4ed51c772 100644 --- a/relay/playbooks/ember-tune-pc1.ps1 +++ b/relay/playbooks/ember-tune-pc1.ps1 @@ -71,7 +71,7 @@ Say ("engine: " + $exe + " (" + $ver + ")") Write-Output ("RESULT TUNE engine " + $ver + " sha256=" + (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash.ToLower()) if ($ver -notmatch 'igneum-app (\d+)\.(\d+)\.(\d+)') { Write-Output 'RESULT TUNE error=version_unknown'; exit 2 } -foreach ($f in @('settings.json', 'machine-id', 'wallet.json', 'tuning.json')) { +foreach ($f in @('settings.json', 'machine-id', 'wallet.json', 'tuning.json', 'firewall-rule.json')) { # the firewall flag too: an older kit then asks nothing $src = Join-Path $appDir $f if (Test-Path $src) { Copy-Item -LiteralPath $src -Destination (Join-Path $sApp $f) -Force } }