igneum/tools/windows/console-watch.ps1
igneum-labs 8bb638f843 Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so
The sweep (main's item 1): 199 tracked text files, 783 lines. The founder's full name, first name and possessive become "the founder" (sentence starts capitalised); the lowercase operating-system user name in WSL paths and commands becomes <user>; the second owner login becomes "the second owner login"; the three earlier businesses and the two other brands become "the other business", "the earlier entity", "the earlier business" and "another brand"; the Chrome profile rule names the igneum.network profile, not the profile's label. The standing commit login igneum-labs is not a founder term here: the fresh-repository step renames it in the history (docs/plans/history-rewrite.md, tools/repo/fresh-repo.sh).

The patterns never appear in plain text in the tree (a plaintext list would be the hit): tools/ci/founder-strings.b64 (perl regex, tab, a sample per row) is read by tools/ci/founder-strings-check.sh (every tracked text file, perl, known-failed first: the self-test plants each row's sample in a fixture and the hit must name the file), by tools/community/discord-hooks.mjs (the guard's founder and business rows; the test takes its fixtures from the samples) and by tools/repo/fresh-repo.sh (the business names of the rewrite rules). site/forbidden-strings.txt carries the same patterns as b64: lines, decoded case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs (whose fixture now plants an encoded made-up name). The check runs in the gate's tree checks on every merge.

Not in this commit, by main's word: the 105 commit messages and 40 personal-identity commits that need the history rewrite (listed, not run), and the secrets found by gitleaks over the history (reported with owners).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:39:50 +00:00

181 lines
13 KiB
PowerShell

# Console-window watcher for a Windows PC running the Igneum Miner app. A signed `run` job (app/igneum-app/src/jobrun.rs,
# shell powershell, not elevated): while a sampler thread enumerates the visible top-level windows (user32 EnumWindows,
# IsWindowVisible, GetWindowThreadProcessId, GetClassName, GetWindowText) and the console host processes (conhost,
# OpenConsole, WindowsTerminal, with their command lines and parents) every 30 ms, the main thread starts each
# candidate child the way a job script or the app does, and every window or host that appears during a probe is
# reported against it:
# RESULT terminal: ... the default-terminal delegation (HKCU\Console\%%Startup) and the host process counts
# RESULT self: ... the job's own console (hidden or not) and the conhost that serves it
# RESULT probe <n>: ... exit code, duration, how many windows and hosts appeared
# RESULT window: <process> [<class>] <title> (probe <n>)
# RESULT host: <name> pid <p> parent <process> cmd <command line> (probe <n>)
# Trust test (CLAUDE.md: a watcher is trusted only after a known-finished and a known-failed case): probe
# start-process-new-console MUST report a window (cmd in a new console); start-process-hidden is the same with
# -WindowStyle Hidden. 5 October 2026: written for PC 1 (ae432dc7, Windows 11 Pro 26200), where the founder saw
# "Windows Command Processor" windows whenever a remote job ran.
# packaging/ota/publish-jobs.sh add --kind run --target ae432dc7 --script tools/windows/console-watch.ps1 \
# --timeout-minutes 5 --title "PC 1: console window watcher" --deploy
$ErrorActionPreference = 'Continue'
$src = @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
public static class IgWin {
public delegate bool EnumProc(IntPtr h, IntPtr l);
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc p, IntPtr l);
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetClassName(IntPtr h, StringBuilder s, int n);
[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
public static List<string> Visible() {
var list = new List<string>();
EnumWindows((h, l) => {
if (!IsWindowVisible(h)) return true;
uint pid; GetWindowThreadProcessId(h, out pid);
var t = new StringBuilder(512); GetWindowText(h, t, 512);
var c = new StringBuilder(256); GetClassName(h, c, 256);
list.Add(((long)h).ToString() + "|" + pid + "|" + c + "|" + t);
return true;
}, IntPtr.Zero);
return list;
}
}
'@
try { Add-Type -TypeDefinition $src -ErrorAction Stop } catch { if (-not ([System.Management.Automation.PSTypeName]'IgWin').Type) { Write-Output ("RESULT error: Add-Type failed: " + $_); exit 2 } }
function Say([string] $m) { Write-Output $m }
function ProcName([int] $procId) { try { (Get-Process -Id $procId -ErrorAction Stop).ProcessName } catch { 'gone' } }
# ---- the default terminal and the hosts present before anything starts ------------------------------------------------
$CONHOST_ID = '{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}'
$DECIDE_ID = '{00000000-0000-0000-0000-000000000000}'
$k = Get-ItemProperty -Path 'HKCU:\Console\%%Startup' -ErrorAction SilentlyContinue
$dc = if ($k) { [string]$k.DelegationConsole } else { '(absent)' }
$dt = if ($k) { [string]$k.DelegationTerminal } else { '(absent)' }
$meaning = if ($dc -eq $CONHOST_ID) { 'Windows Console Host (conhost)' } elseif ($dc -eq $DECIDE_ID -or $dc -eq '(absent)') { 'Let Windows decide (Windows Terminal on Windows 11 22H2 and later when it is installed)' } else { 'a terminal package, Windows Terminal or its preview' }
$wtPkg = (Get-AppxPackage -Name 'Microsoft.WindowsTerminal*' -ErrorAction SilentlyContinue | ForEach-Object { $_.Name + ' ' + $_.Version }) -join ', '
Say ("RESULT terminal: DelegationConsole=" + $dc + " DelegationTerminal=" + $dt + " -> " + $meaning + "; Windows Terminal package: " + $(if ($wtPkg) { $wtPkg } else { 'none' }))
$os = Get-CimInstance Win32_OperatingSystem
Say ("RESULT os: " + $os.Caption + " build " + $os.BuildNumber + " user " + $env:USERNAME + " session " + [System.Diagnostics.Process]::GetCurrentProcess().SessionId)
$counts = @{}
foreach ($n in 'conhost', 'OpenConsole', 'WindowsTerminal', 'cmd', 'powershell', 'wsl', 'wslhost') { $counts[$n] = @(Get-Process -Name $n -ErrorAction SilentlyContinue).Count }
Say ("RESULT hosts-before: conhost " + $counts['conhost'] + " OpenConsole " + $counts['OpenConsole'] + " WindowsTerminal " + $counts['WindowsTerminal'] + " cmd " + $counts['cmd'] + " powershell " + $counts['powershell'] + " wsl " + $counts['wsl'] + " wslhost " + $counts['wslhost'])
# the job's own console and the chain above it
$me = [System.Diagnostics.Process]::GetCurrentProcess()
$meCim = Get-CimInstance Win32_Process -Filter "ProcessId=$($me.Id)"
$parent = if ($meCim) { ProcName $meCim.ParentProcessId } else { '?' }
$hwnd = [IgWin]::GetConsoleWindow()
$selfVis = if ($hwnd -ne [IntPtr]::Zero) { [IgWin]::IsWindowVisible($hwnd) } else { 'no window' }
$ownHost = Get-CimInstance Win32_Process -Filter "Name='conhost.exe' OR Name='OpenConsole.exe'" | Where-Object { $_.ParentProcessId -eq $me.Id -or $_.ParentProcessId -eq $meCim.ParentProcessId }
$ownLine = if ($ownHost) { ($ownHost | ForEach-Object { $_.Name + ' pid ' + $_.ProcessId + ' parent ' + (ProcName $_.ParentProcessId) + ' cmd ' + $_.CommandLine }) -join ' ; ' } else { 'none with this script or its parent as parent' }
Say ("RESULT self: powershell pid " + $me.Id + " parent " + $parent + " (pid " + $meCim.ParentProcessId + "); console hwnd " + $hwnd + " visible " + $selfVis + "; host " + $ownLine)
foreach ($w in [IgWin]::Visible()) {
$f = $w.Split('|', 4)
if ($f[2] -eq 'ConsoleWindowClass' -or $f[2] -eq 'CASCADIA_HOSTING_WINDOW_CLASS') { Say ("RESULT window-before: " + (ProcName ([int]$f[1])) + " [" + $f[2] + "] " + $f[3]) }
}
# ---- the sampler thread: every new visible window and every new console host, with the time it was first seen -------
$sync = [hashtable]::Synchronized(@{ win = [hashtable]::Synchronized(@{}); hosts = [hashtable]::Synchronized(@{}); stop = $false; ticks = 0; err = '' })
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
$rs.SessionStateProxy.SetVariable('sync', $sync)
$rs.SessionStateProxy.SetVariable('src', $src)
$sampler = [powershell]::Create()
$sampler.Runspace = $rs
[void]$sampler.AddScript({
try {
if (-not ([System.Management.Automation.PSTypeName]'IgWin').Type) { Add-Type -TypeDefinition $src }
$first = $true
while (-not $sync.stop) {
$now = Get-Date
foreach ($w in [IgWin]::Visible()) {
$f = $w.Split('|', 4)
if (-not $sync.win.ContainsKey($f[0])) {
$pn = try { (Get-Process -Id ([int]$f[1]) -ErrorAction Stop).ProcessName } catch { 'gone' }
$sync.win[$f[0]] = @{ t = $now; procId = [int]$f[1]; proc = $pn; cls = $f[2]; title = $f[3]; base = $first }
}
}
foreach ($p in @(Get-Process -Name conhost, OpenConsole, WindowsTerminal -ErrorAction SilentlyContinue)) {
if (-not $sync.hosts.ContainsKey($p.Id)) {
$ci = Get-CimInstance Win32_Process -Filter "ProcessId=$($p.Id)" -ErrorAction SilentlyContinue
$ppid = if ($ci) { $ci.ParentProcessId } else { 0 }
$ppn = try { (Get-Process -Id $ppid -ErrorAction Stop).ProcessName } catch { 'gone' }
$sync.hosts[$p.Id] = @{ t = $now; name = $p.ProcessName; cmd = $(if ($ci) { [string]$ci.CommandLine } else { '?' }); ppid = $ppid; pproc = $ppn; base = $first }
}
}
$first = $false
$sync.ticks++
Start-Sleep -Milliseconds 30
}
} catch { $sync.err = [string]$_ }
})
$handle = $sampler.BeginInvoke()
$t = 0
while ($sync.ticks -lt 2 -and $t -lt 100) { Start-Sleep -Milliseconds 50; $t++ }
if ($sync.ticks -lt 2) { Say ("RESULT error: the sampler did not start: " + $sync.err); exit 2 }
Say ("sampler running: " + $sync.win.Count + " visible windows and " + $sync.hosts.Count + " console hosts at the start")
# ---- probes: each one as a job script or the app would start it ----------------------------------------------------
$report = New-Object System.Collections.ArrayList
function Probe([string] $name, [scriptblock] $body) {
Start-Sleep -Milliseconds 400
$t0 = Get-Date
$global:LASTEXITCODE = 0
$err = ''
try { & $body 2>&1 | Out-Null } catch { $err = [string]$_ }
$rc = $LASTEXITCODE
Start-Sleep -Milliseconds 600
$t1 = Get-Date
$ms = [int]($t1 - $t0).TotalMilliseconds - 600
$wins = @($sync.win.GetEnumerator() | Where-Object { -not $_.Value.base -and $_.Value.t -ge $t0 -and $_.Value.t -le $t1 -and -not $_.Value.reported })
$hosts = @($sync.hosts.GetEnumerator() | Where-Object { -not $_.Value.base -and $_.Value.t -ge $t0 -and $_.Value.t -le $t1 -and -not $_.Value.reported })
Say ("RESULT probe " + $name + ": exit " + $rc + " in " + $ms + " ms, " + $wins.Count + " window(s), " + $hosts.Count + " host(s)" + $(if ($err) { "; error " + $err } else { '' }))
foreach ($w in $wins) { $w.Value.reported = $true; Say ("RESULT window: " + $w.Value.proc + " [" + $w.Value.cls + "] " + $w.Value.title + " (probe " + $name + ")") }
foreach ($h in $hosts) { $h.Value.reported = $true; Say ("RESULT host: " + $h.Value.name + " pid " + $h.Key + " parent " + $h.Value.pproc + " cmd " + $h.Value.cmd + " (probe " + $name + ")") }
}
$distro = 'Ubuntu-24.04'
$haveDistro = $false
try { $haveDistro = ((& wsl.exe -l -q 2>$null) -replace "`0", '' | Where-Object { $_.Trim() -eq $distro }).Count -gt 0 } catch {}
Say ("wsl distro " + $distro + ": " + $(if ($haveDistro) { 'present' } else { 'absent, the distro probes are skipped' }))
# a. the plain children a job script starts (CreateProcess, the console inherited)
Probe 'powershell-inherit' { & powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'Start-Sleep -Milliseconds 1200' }
Probe 'cmd-c-inherit' { & cmd.exe /c 'ping -n 3 127.0.0.1 >nul' }
Probe 'query-session' { & query.exe session }
Probe 'curl-version' { & curl.exe --version }
Probe 'nvidia-smi-L' { & nvidia-smi.exe -L }
Probe 'powershell-windowstyle-hidden-inherit' { & powershell.exe -NoProfile -WindowStyle Hidden -Command 'Start-Sleep -Milliseconds 1200' }
Probe 'wsl-status' { & wsl.exe --status }
if ($haveDistro) {
Probe 'wsl-distro-sleep' { & wsl.exe -d Ubuntu-24.04 -u root -- sleep 1 }
Probe 'wsl-interop-cmd' { & wsl.exe -d Ubuntu-24.04 -u root -- cmd.exe /c 'ping -n 3 127.0.0.1' }
Probe 'wsl-interop-powershell' { & wsl.exe -d Ubuntu-24.04 -u root -- powershell.exe -NoProfile -Command 'Start-Sleep -Milliseconds 1200' }
}
# b. the trust test: a new console (ShellExecute) must show; the same hidden
Probe 'start-process-new-console' { Start-Process -FilePath cmd.exe -ArgumentList '/c ping -n 3 127.0.0.1' -Wait }
Probe 'start-process-hidden' { Start-Process -FilePath cmd.exe -ArgumentList '/c ping -n 3 127.0.0.1' -WindowStyle Hidden -Wait }
# c. the elevated job's shape without the elevation: powershell in a new hidden console through ShellExecute
Probe 'start-process-powershell-hidden' { Start-Process -FilePath powershell.exe -ArgumentList '-NoProfile -ExecutionPolicy Bypass -Command Start-Sleep -Milliseconds 1200' -WindowStyle Hidden -Wait }
# d. candidate fixes: a headless conhost of our own around the child; the children of a headless session
Probe 'conhost-headless-cmd' { & conhost.exe --headless cmd.exe /c 'ping -n 3 127.0.0.1 >nul' }
if ($haveDistro) {
Probe 'conhost-headless-wsl-interop' { & conhost.exe --headless wsl.exe -d Ubuntu-24.04 -u root -- cmd.exe /c 'ping -n 3 127.0.0.1' }
}
Probe 'conhost-headless-start-process-hidden' { & conhost.exe --headless powershell.exe -NoProfile -Command "Start-Process -FilePath cmd.exe -ArgumentList '/c ping -n 3 127.0.0.1' -WindowStyle Hidden -Wait" }
# ---- the end: anything the probes did not claim ---------------------------------------------------------------------
Start-Sleep -Milliseconds 800
$sync.stop = $true
try { [void]$sampler.EndInvoke($handle) } catch {}
$sampler.Dispose(); $rs.Close()
$stray = @($sync.win.GetEnumerator() | Where-Object { -not $_.Value.base -and -not $_.Value.reported })
foreach ($w in $stray) { Say ("RESULT window: " + $w.Value.proc + " [" + $w.Value.cls + "] " + $w.Value.title + " (between probes)") }
$strayH = @($sync.hosts.GetEnumerator() | Where-Object { -not $_.Value.base -and -not $_.Value.reported })
foreach ($h in $strayH) { Say ("RESULT host: " + $h.Value.name + " pid " + $h.Key + " parent " + $h.Value.pproc + " cmd " + $h.Value.cmd + " (between probes)") }
$counts = @{}
foreach ($n in 'conhost', 'OpenConsole', 'WindowsTerminal') { $counts[$n] = @(Get-Process -Name $n -ErrorAction SilentlyContinue).Count }
Say ("RESULT hosts-after: conhost " + $counts['conhost'] + " OpenConsole " + $counts['OpenConsole'] + " WindowsTerminal " + $counts['WindowsTerminal'] + "; sampler ticks " + $sync.ticks + $(if ($sync.err) { "; sampler error " + $sync.err } else { '' }))
exit 0