One administrator approval, ever (Josh, 6 October 2026 11:50 UTC): the first Power control approval also registers the per-user scheduled task 'Igneum Power Helper' (RunLevel Highest, no trigger, action = the app's own exe --power-helper); every later cap and every tune's helper starts the task and writes the command file, no prompt, across restarts, updates and reboots; Power control off sends remove and the task unregisters itself; the helper runs only fixed verbs with digit-only nvidia-smi arguments (threat note in ember-tune.md 7a); 4 tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-josh 2026-10-06 12:50:49 +01:00
parent 5a261e7894
commit 53b50e81ba
4 changed files with 376 additions and 3 deletions

View file

@ -596,6 +596,10 @@ pub struct Engine {
quit_source: &'static str,
/// the over-the-air updater never runs: IGNEUM_APP_NO_OTA=1 or --sweep (a second engine beside the installed app)
no_ota: bool,
/// the Igneum Power Helper task is registered (src/powertask.rs): once, ever; None = not asked yet
power_task: Option<bool>,
/// the running tune helper is the task (quit ends it; no SweepHelperDone comes from a thread)
sweep_helper_is_task: bool,
/// the request number the vendor tool last carried out (the run's acknowledgement)
tune_acked: Option<u64>,
/// cards whose confirm check found a better neighbour: the full plan runs next
@ -700,6 +704,8 @@ impl Engine {
sweep: None,
quit_source: "unknown",
no_ota,
power_task: None,
sweep_helper_is_task: false,
tune_acked: None,
tune_full_due: std::collections::HashSet::new(),
sweep_pending: None,
@ -989,6 +995,7 @@ impl Engine {
self.clock_next_https = Instant::now() + Duration::from_secs(6);
}
Cmd::PowerApplied(what, r, readback) => {
self.power_task = None; // the one elevated step may have registered the task: ask again next time
self.power_busy = false;
self.power_via_host = None;
// the truth is what nvidia-smi reads back, not whether the prompt said yes
@ -1779,8 +1786,45 @@ impl Engine {
self.power_busy = true;
self.power_restore_pending = true;
self.shared.log(&format!("power cap ({why}): {}", cmds.join(" & ")));
let line = cmds.join(" & ");
let what = what.join(", ");
// once, ever (src/powertask.rs): a registered task sets the caps with no prompt; the readback judges it
if cfg!(windows) && self.power_task_registered() {
let pairs: Vec<(String, u64)> = self.st().mining.cards.iter().filter(|c| c.vendor == "nvidia" && c.enabled && c.present() && c.power_default_w > 0.0 && !c.power_applied).map(|c| (c.device.clone(), requested_watts(c).round() as u64)).collect();
let dir = self.sweep_dir();
let shared = self.shared.clone();
self.shared.log("power cap: through the Igneum Power Helper task (no prompt)");
std::thread::spawn(move || {
let r = crate::powertask::start().and_then(|_| {
let _ = std::fs::create_dir_all(&dir);
let mut seq = crate::platform::unix_now() % 1_000_000;
let mut text = String::new();
for (dev, w) in &pairs {
seq += 1;
text.push_str(&format!("{seq} dev {dev}\n"));
seq += 1;
text.push_str(&format!("{seq} pl {w}\n"));
}
std::fs::write(dir.join("cmd.txt"), text).map_err(|e| e.to_string())
});
std::thread::sleep(Duration::from_secs(6));
let back: std::collections::HashMap<String, f64> = crate::detect::nvidia_power_limits().into_iter().map(|(k, v)| (k, v.1)).collect();
shared.send(Cmd::PowerApplied(what, r, back));
});
return;
}
// the first approval registers the task in the same elevated step as the caps (Windows), so no later step
// needs a prompt: the registration script is written next to the command file
let line = if cfg!(windows) {
let dir = self.sweep_dir();
let _ = std::fs::create_dir_all(&dir);
let script = dir.join("register-power-task.ps1");
match std::env::current_exe().map(|exe| std::fs::write(&script, [b"\xEF\xBB\xBF".as_slice(), crate::powertask::register_script(&exe).as_bytes()].concat())) {
Ok(Ok(())) => format!("{} & \"{}\" -NoProfile -ExecutionPolicy Bypass -File \"{}\"", cmds.join(" & "), crate::platform::tool("powershell").display(), script.display()),
_ => cmds.join(" & "),
}
} else {
cmds.join(" & ")
};
let want: std::collections::HashMap<String, f64> = self.st().mining.cards.iter().filter(|c| c.vendor == "nvidia" && c.enabled && c.present() && c.power_default_w > 0.0).map(|c| (c.device.clone(), requested_watts(c))).collect();
if self.wrapper && cfg!(windows) {
// the window host has a UI context: it shows the administrator prompt and reports back on stdin
@ -1830,6 +1874,14 @@ impl Engine {
self.shared.log(&format!("GPU power limits left as set (they reset at the next reboot; no prompt on quit): {}", cmds.join(" & ")));
}
/// Is the Igneum Power Helper task registered (src/powertask.rs)? Asked once per run and after every elevated step.
fn power_task_registered(&mut self) -> bool {
if self.power_task.is_none() {
self.power_task = Some(crate::powertask::registered());
}
self.power_task.unwrap_or(false)
}
/// Power control (config.rs power_control): may the engine ask for administrator rights for the cap or the sweep?
/// The elevated PC sweep job (--sweep) sets caps directly and counts as allowed.
fn elevation_allowed(&self) -> bool {
@ -1854,6 +1906,16 @@ impl Engine {
if self.sweep.is_some() || self.sweep_pending.is_some() {
self.sweep_abort("power control is off");
}
if cfg!(windows) && self.power_task_registered() {
// the kill switch: the task unregisters itself (elevated) and exits; nothing is left behind
let dir = self.sweep_dir();
let _ = std::fs::write(dir.join("cmd.txt"), "remove\n");
match crate::powertask::start() {
Ok(()) => self.shared.log("power control off: the Igneum Power Helper task removes itself"),
Err(e) => self.shared.log(&format!("power control off: the task could not be started to remove itself ({e}); remove it in Task Scheduler")),
}
self.power_task = None;
}
self.shared.event(if note.starts_with("power control off:") { "error" } else { "info" }, note);
}
@ -2343,8 +2405,18 @@ impl Engine {
std::fs::write(&script, crate::sweep::helper_script_unix()).map_err(|e| e.to_string())?;
format!("sh \"{}\" \"{}\" \"{}\" {} {}", script.display(), dir.display(), smi, c.device, restore)
};
if cfg!(windows) && self.power_task_registered() {
// once, ever: the registered task is the helper; it reads the same command file, no prompt
let _ = std::fs::write(dir.join("cmd.txt"), format!("{} dev {}\n", crate::platform::unix_now() % 1_000_000, c.device));
crate::powertask::start()?;
self.shared.log("tune helper: the Igneum Power Helper task (no prompt)");
self.sweep_helper = true;
self.sweep_helper_is_task = true;
return Ok(());
}
self.shared.log(&format!("tune helper (administrator prompt): {line}"));
self.sweep_helper = true;
self.sweep_helper_is_task = false;
let shared = self.shared.clone();
std::thread::spawn(move || {
let r = crate::platform::run_elevated(&line);
@ -2356,6 +2428,11 @@ impl Engine {
fn sweep_helper_quit(&mut self) {
if self.sweep_helper {
let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), "quit\n");
if self.sweep_helper_is_task {
// the task exits on quit and reports nothing back; the next tune starts it again
self.sweep_helper = false;
self.sweep_helper_is_task = false;
}
}
}
@ -2400,7 +2477,8 @@ impl Engine {
// measure only: nothing is set; the run notices the missing acknowledgement and measures
return;
}
let cmd = format!("{seq} pl {w}\n{seq} {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() });
let dev = device.to_string();
let cmd = format!("{seq}0 dev {dev}\n{seq}1 pl {w}\n{seq}2 {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() });
let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), cmd);
// the helper polls twice a second and nvidia-smi answers within a second or two
std::thread::spawn(move || {

View file

@ -34,6 +34,7 @@ mod verifier;
mod wslhost;
mod sweep;
mod ember;
mod powertask;
mod watchdog;
use std::io::{BufRead, Write};
@ -53,6 +54,12 @@ fn main() {
println!("igneum-app {}", engine::VERSION);
return;
}
if args.iter().any(|a| a == "--power-helper") {
// the scheduled task's action (src/powertask.rs): elevated, runs only digit-argument nvidia-smi commands
// from <app data>/app/sweep/cmd.txt, exits on quit, remove or 20 idle minutes
let dir = powertask::sweep_dir(&platform::data_root().join("app"));
std::process::exit(powertask::run_helper(&dir));
}
if args.iter().any(|a| a == "--launch") {
if let Some(dir) = std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.to_path_buf())) {
let host = dir.join("Igneum Miner.exe");

View file

@ -0,0 +1,265 @@
//! One administrator approval, ever (Josh, 6 October 2026, 11:50 UTC, after clicking the third prompt of the morning:
//! "can we make sure all these popups are not needed in future?").
//!
//! What 0.3.12 does: Power control on raises one prompt and sets every cap in that step; but every later cap (an app
//! start, a reboot, a slider move) and every tune's helper is another elevated launch, so another prompt. This module
//! makes the first approval the last: the one elevated step also registers a per-user Windows scheduled task,
//! `Igneum Power Helper`, principal = the signed-in user, RunLevel Highest, no trigger, whose action is this very
//! executable with `--power-helper`. A task the user owns can be STARTED by the user's unelevated processes without a
//! prompt (`Start-ScheduledTask`), and it runs elevated; so every later cap and tune starts the task and talks to it
//! through the command file `<app data>/app/sweep/cmd.txt` (the protocol the 0.3.9 helper scripts spoke: `<seq> pl
//! <watts>`, `<seq> lgc <MHz>`, `<seq> rgc`, `quit`; plus `remove`, the kill switch). The task survives app restarts,
//! updates (the per-user installer replaces the exe in place; the task's action path is the install folder) and
//! reboots (a task, not a process). Power control off starts the task once and sends `remove`: the helper unregisters
//! the task (elevated) and exits; nothing is left behind.
//!
//! Threat note (what the helper will and will not run):
//! - The action is fixed at registration: the app's own exe in the install folder with `--power-helper`. The task
//! has no trigger and no arguments from outside; only `Start-ScheduledTask` by the owning user starts it.
//! - The helper reads ONE file, `<app data>/app/sweep/cmd.txt`, in the user's own profile. Every command it accepts
//! is a fixed verb with digit-only arguments: `pl <watts>` runs `nvidia-smi -i <dev> -pl <watts>`, `lgc <MHz>` runs
//! `nvidia-smi -i <dev> -lgc 0,<MHz>`, `rgc` runs `nvidia-smi -i <dev> -rgc`, `quit` ends it, `remove` unregisters
//! the task and ends it. The device index is digits only too (`dev <n>` sets it). No shell, no path, no string from
//! the file reaches a process: `Command::new(nvidia-smi).args([...])`, never `cmd /c`.
//! - nvidia-smi is resolved to the driver's install path (platform::tool), never from PATH.
//! - What an attacker running as the user gains: the power limit and the clock cap of the user's own NVIDIA cards,
//! within the ranges the driver allows, which the same user could set with one approved prompt anyway. Nothing
//! else: no file, no process, no registry, no other binary.
//! - The helper exits after 20 idle minutes; a stale command file is cleared at start (sequence numbers must rise).
//! - Linux keeps pkexec per step (no scheduled task); macOS has no cap to set.
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
/// The task name in the Windows Task Scheduler (per user).
pub const TASK_NAME: &str = "Igneum Power Helper";
/// The helper ends after this long without a new command.
pub const IDLE_S: u64 = 20 * 60;
/// One parsed command from cmd.txt.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HelperCmd {
Dev(String),
PowerLimit(u64),
ClockCap(u64),
ClockReset,
Quit,
Remove,
}
/// Parses one line: `<seq> <verb> [<digits>]` (the 0.3.9 form `<seq> <watts>` reads as a power limit; `quit` and
/// `remove` need no sequence). Anything that is not a fixed verb with digit-only arguments is None.
pub fn parse_line(line: &str) -> Option<(u64, HelperCmd)> {
let t = line.trim();
if t == "quit" {
return Some((0, HelperCmd::Quit));
}
if t == "remove" {
return Some((0, HelperCmd::Remove));
}
let p: Vec<&str> = t.split_whitespace().collect();
let digits = |s: &str| !s.is_empty() && s.len() <= 6 && s.chars().all(|c| c.is_ascii_digit());
let seq: u64 = p.first().filter(|s| digits(s)).and_then(|s| s.parse().ok())?;
match p.as_slice() {
[_, w] if digits(w) => Some((seq, HelperCmd::PowerLimit(w.parse().ok()?))),
[_, "pl", w] if digits(w) => Some((seq, HelperCmd::PowerLimit(w.parse().ok()?))),
[_, "lgc", m] if digits(m) => Some((seq, HelperCmd::ClockCap(m.parse().ok()?))),
[_, "rgc"] => Some((seq, HelperCmd::ClockReset)),
[_, "dev", d] if digits(d) => Some((seq, HelperCmd::Dev(d.to_string()))),
_ => None,
}
}
/// The nvidia-smi arguments a command becomes (None for the verbs that run nothing).
pub fn smi_args(dev: &str, c: &HelperCmd) -> Option<Vec<String>> {
match c {
HelperCmd::PowerLimit(w) => Some(vec!["-i".into(), dev.into(), "-pl".into(), w.to_string()]),
HelperCmd::ClockCap(m) => Some(vec!["-i".into(), dev.into(), "-lgc".into(), format!("0,{m}")]),
HelperCmd::ClockReset => Some(vec!["-i".into(), dev.into(), "-rgc".into()]),
_ => None,
}
}
/// The PowerShell that registers the task (run inside the ONE elevated step, with the caps). `exe` is this
/// executable's path in the install folder. Principal: the signed-in user, interactive logon, highest run level; no
/// trigger; may start on battery; one hour limit per run; multiple starts are ignored while one runs.
pub fn register_script(exe: &Path) -> String {
let exe = exe.display().to_string().replace('\'', "''");
format!(
"$a = New-ScheduledTaskAction -Execute '{exe}' -Argument '--power-helper' -WorkingDirectory '{dir}'\r\n\
$p = New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Highest\r\n\
$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 1) -MultipleInstances IgnoreNew -Hidden\r\n\
Register-ScheduledTask -TaskName '{name}' -Action $a -Principal $p -Settings $s -Force | Out-Null\r\n\
exit 0\r\n",
dir = exe.rfind(['\\', '/']).map(|i| exe[..i].to_string()).unwrap_or_default(),
name = TASK_NAME
)
}
/// The PowerShell that starts the task from an unelevated process (no prompt: the user owns the task).
pub fn start_command() -> String {
format!("Start-ScheduledTask -TaskName '{TASK_NAME}'; exit 0")
}
/// The PowerShell that says whether the task is registered (exit 0) or not (exit 1).
pub fn query_command() -> String {
format!("if (Get-ScheduledTask -TaskName '{TASK_NAME}' -ErrorAction SilentlyContinue) {{ exit 0 }} else {{ exit 1 }}")
}
/// The PowerShell the helper itself runs (elevated) on `remove`: the task goes, nothing is left.
pub fn remove_command() -> String {
format!("Unregister-ScheduledTask -TaskName '{TASK_NAME}' -Confirm:$false; exit 0")
}
/// Is the task registered? Windows only; false elsewhere.
pub fn registered() -> bool {
if !cfg!(windows) {
return false;
}
let mut c = std::process::Command::new(crate::platform::tool("powershell"));
c.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &query_command()]);
crate::platform::quiet(&mut c);
c.status().map(|s| s.success()).unwrap_or(false)
}
/// Starts the task (no prompt). Ok when Start-ScheduledTask returned 0.
pub fn start() -> Result<(), String> {
let mut c = std::process::Command::new(crate::platform::tool("powershell"));
c.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &start_command()]);
crate::platform::quiet(&mut c);
let out = c.output().map_err(|e| e.to_string())?;
if out.status.success() {
Ok(())
} else {
Err(format!("Start-ScheduledTask failed: {}", String::from_utf8_lossy(&out.stderr).trim()))
}
}
/// The helper process (`igneum-app --power-helper`): polls `<dir>/cmd.txt` twice a second, runs the parsed commands
/// through nvidia-smi, logs what it ran to `<dir>/helper.log`, ends on `quit`, on `remove` (after unregistering the
/// task) or after 20 idle minutes. `dir` is `<app data>/app/sweep`.
pub fn run_helper(dir: &Path) -> i32 {
let _ = std::fs::create_dir_all(dir);
let cmd_file = dir.join("cmd.txt");
let log_file = dir.join("helper.log");
let log = |line: &str| {
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new().append(true).create(true).open(&log_file) {
let _ = writeln!(f, "{} {line}", crate::platform::unix_now());
}
};
log("helper started (scheduled task, elevated)");
// a stale file from an earlier run is not a command: only lines after the start count
let mut last_seq: u64 = std::fs::read_to_string(&cmd_file).ok().and_then(|t| t.lines().filter_map(parse_line).map(|(s, _)| s).max()).unwrap_or(0);
let mut last_text = String::new();
let mut dev = "0".to_string();
let mut idle = Instant::now();
let smi = crate::platform::tool("nvidia-smi");
loop {
let text = std::fs::read_to_string(&cmd_file).unwrap_or_default();
if text != last_text {
last_text = text.clone();
for (seq, c) in text.lines().filter_map(parse_line) {
match c {
HelperCmd::Quit => {
log("quit");
return 0;
}
HelperCmd::Remove => {
let mut p = std::process::Command::new(crate::platform::tool("powershell"));
p.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &remove_command()]);
crate::platform::quiet(&mut p);
let ok = p.status().map(|s| s.success()).unwrap_or(false);
log(&format!("remove: the task is {}", if ok { "unregistered" } else { "still registered (Unregister-ScheduledTask failed)" }));
return if ok { 0 } else { 1 };
}
_ if seq <= last_seq => continue,
HelperCmd::Dev(d) => {
last_seq = seq;
idle = Instant::now();
dev = d;
log(&format!("{seq} dev {dev}"));
}
other => {
last_seq = seq;
idle = Instant::now();
let args = smi_args(&dev, &other).unwrap_or_default();
let mut p = std::process::Command::new(&smi);
p.args(&args);
crate::platform::quiet(&mut p);
let out = p.output().map(|o| format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr))).unwrap_or_else(|e| e.to_string());
log(&format!("{seq} nvidia-smi {} : {}", args.join(" "), out.replace('\n', " ").trim()));
}
}
}
}
if idle.elapsed() >= Duration::from_secs(IDLE_S) {
log("idle 20 min: exit (the engine starts the task again when it needs it)");
return 0;
}
std::thread::sleep(Duration::from_millis(500));
}
}
/// Where the command file lives for a data root.
pub fn sweep_dir(app_dir: &Path) -> PathBuf {
app_dir.join("sweep")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_fixed_verbs_with_digit_arguments_parse() {
assert_eq!(parse_line("7 pl 460"), Some((7, HelperCmd::PowerLimit(460))));
assert_eq!(parse_line("8 lgc 2472"), Some((8, HelperCmd::ClockCap(2472))));
assert_eq!(parse_line("9 rgc"), Some((9, HelperCmd::ClockReset)));
assert_eq!(parse_line("3 dev 1"), Some((3, HelperCmd::Dev("1".into()))));
assert_eq!(parse_line("5 403"), Some((5, HelperCmd::PowerLimit(403))), "the 0.3.9 form");
assert_eq!(parse_line("quit"), Some((0, HelperCmd::Quit)));
assert_eq!(parse_line("remove"), Some((0, HelperCmd::Remove)));
// nothing else: no shell, no path, no string argument, no oversized number
for bad in ["7 pl 460; calc", "7 pl -460", "7 pl 4.60", "7 lgc 0,2472", "7 rm C:\\x", "x pl 460", "7 pl", "7 lgc 12345678", "7 dev ../1", "", "7 pl 460 extra"] {
assert_eq!(parse_line(bad), None, "{bad:?}");
}
}
#[test]
fn the_arguments_reach_nvidia_smi_as_a_list_never_a_shell() {
assert_eq!(smi_args("0", &HelperCmd::PowerLimit(460)).unwrap(), vec!["-i", "0", "-pl", "460"]);
assert_eq!(smi_args("1", &HelperCmd::ClockCap(2472)).unwrap(), vec!["-i", "1", "-lgc", "0,2472"]);
assert_eq!(smi_args("1", &HelperCmd::ClockReset).unwrap(), vec!["-i", "1", "-rgc"]);
assert_eq!(smi_args("0", &HelperCmd::Quit), None);
assert_eq!(smi_args("0", &HelperCmd::Remove), None);
assert_eq!(smi_args("0", &HelperCmd::Dev("1".into())), None);
}
#[test]
fn the_registration_is_per_user_highest_no_trigger_fixed_action() {
let s = register_script(Path::new(r"C:\Users\Admin\AppData\Local\Programs\Igneum Miner\igneum-app.exe"));
assert!(s.contains("-Execute 'C:\\Users\\Admin\\AppData\\Local\\Programs\\Igneum Miner\\igneum-app.exe' -Argument '--power-helper'"), "{s}");
assert!(s.contains("-WorkingDirectory 'C:\\Users\\Admin\\AppData\\Local\\Programs\\Igneum Miner'"), "{s}");
assert!(s.contains("-RunLevel Highest") && s.contains("-LogonType Interactive"), "{s}");
assert!(s.contains("[System.Security.Principal.WindowsIdentity]::GetCurrent().Name"), "the signed-in user, never a literal");
assert!(!s.contains("-Trigger"), "no trigger: only the app starts it");
assert!(s.contains("-MultipleInstances IgnoreNew") && s.contains("-ExecutionTimeLimit"), "{s}");
assert!(s.contains(&format!("-TaskName '{TASK_NAME}'")));
// a quote in the path cannot break out of the literal
let q = register_script(Path::new(r"C:\it's\igneum-app.exe"));
assert!(q.contains("'C:\\it''s\\igneum-app.exe'"), "{q}");
assert!(start_command().starts_with("Start-ScheduledTask -TaskName 'Igneum Power Helper'"));
assert!(remove_command().starts_with("Unregister-ScheduledTask -TaskName 'Igneum Power Helper' -Confirm:$false"));
assert!(query_command().contains("Get-ScheduledTask -TaskName 'Igneum Power Helper'"));
}
#[test]
fn a_stale_command_file_does_not_run_at_start() {
// the helper's start reads the highest sequence already in the file and runs nothing below or at it
let text = "3 pl 460\n4 lgc 2472\n";
let last = text.lines().filter_map(parse_line).map(|(s, _)| s).max().unwrap_or(0);
assert_eq!(last, 4);
let newer: Vec<_> = "3 pl 460\n4 lgc 2472\n5 rgc\n".lines().filter_map(parse_line).filter(|(s, _)| *s > last).collect();
assert_eq!(newer, vec![(5, HelperCmd::ClockReset)]);
}
}

View file

@ -161,13 +161,36 @@ maximum, 14,001 MHz memory; 9070 XT present on bus 98 with OFFSET ranges `gmax_r
10`). The offset finding changed the AMD mapping (054e041): an offset clock range closes the clock knob and the power
ladder runs on a percent scale bounded by `plimit_range`. The re-run follows the 0.3.11 rollout.
## 7a. One administrator approval, ever (0.3.13; Josh, 6 October 2026, 11:50 UTC)
What 0.3.12 does: Power control on raises one prompt and sets every cap in that step; every later cap (an app start, a
reboot, a slider move) and every tune's helper is another elevated launch, so another prompt. Not "once, ever".
What `src/powertask.rs` does: the first approval's elevated step also registers a per-user Windows scheduled task,
`Igneum Power Helper` (principal = the signed-in user, interactive logon, RunLevel Highest, no trigger, hidden, one
hour limit, new starts ignored while one runs), whose action is the app's own exe in the install folder with
`--power-helper`. A task the user owns is started by the user's unelevated engine with `Start-ScheduledTask`, no
prompt, and runs elevated. Every later cap and every tune's helper starts the task and writes the command file
`<app data>/app/sweep/cmd.txt` (`<seq> dev <n>`, `<seq> pl <W>`, `<seq> lgc <MHz>`, `<seq> rgc`, `quit`). The task
survives app restarts, updates (the per-user installer replaces the exe in place; the task's action path is the
install folder) and reboots. Power control off starts the task once and sends `remove`: the helper unregisters the
task (elevated) and exits; nothing is left behind. Linux keeps pkexec per step; macOS has no cap.
Threat note: the helper runs only fixed verbs with digit-only arguments through `Command::new(nvidia-smi).args`
(the driver's own path, never PATH, never a shell); a line that is anything else is ignored; the sequence must rise
(a stale file runs nothing); an attacker running as the user gains the power limit and clock cap of the user's own
NVIDIA cards inside the driver's ranges, which the same user could set with one approved prompt anyway; no file,
process, registry key or other binary is reachable through it. Tests: `powertask::tests` (the parser refuses every
non-digit or extra argument, the arguments reach nvidia-smi as a list, the registration is per-user, highest,
trigger-less and quote-safe, a stale command file runs nothing).
## 8a. Next-cut notes (for the 0.3.12 shipper)
| Commit | What | Where |
|---|---|---|
| b671c8b | every `quit:` names its source; Power control alone decides; no cap at start under `--sweep` | main.rs, server.rs, engine.rs (separable) |
| e600e63 | a second engine never runs the updater (`IGNEUM_APP_NO_OTA`, implied by `--sweep`) | engine.rs (6 lines, separable) |
| 1e9550e (this commit, amended) | the elevated job path's output file is followed while the script runs, so the 5-minute progress reports carry its lines (a 35-minute run that never mined showed only "script running" on 6 October 2026); the tune playbook's watchdog fails a run that mines nothing within 120 s of its first status line, with the engine's last log line in the RESULT | jobrun.rs `follow_file`, relay/playbooks/ember-tune-pc1.ps1 |
| 1e9550e | the elevated job path's output file is followed while the script runs, so the 5-minute progress reports carry its lines (a 35-minute run that never mined showed only "script running" on 6 October 2026); the tune playbook's watchdog fails a run that mines nothing within 120 s of its first status line, with the engine's last log line in the RESULT | jobrun.rs `follow_file`, relay/playbooks/ember-tune-pc1.ps1 |
## 9. Open