F14 pinned and the founder's kill-by-name refusal in the relay agent (8 October 2026). The agent runs a task only beside an engine whose api/state reads edition "lab", product "Igneum Miner Lab" and channel "igneum-2.0-devnet-lab" (the window lane's reviewb-202 ef3c8402 strings); an engine with no such field is pre-2.0.2 and runs as before. publish-jobs.sh refuses an add to a target whose latest IGNEUM-APP header reads edition=public (tools/logs.mjs --header <id8>, new: the machine's latest app header line). The founder's word at 20:21 UK ("STOP all these mess ups", the fourth kill-by-name breach that day), by construction: Check-Task refuses a task body that ends a process by its name (Stop-Process -Name, taskkill /IM, Get-Process -Name | Stop-Process, pkill, killall) with exit 77 and the matched line; a pid is the only way. Tests known-failed first in relay/test/service.test.mjs (the shapes built from pieces so the tree's own kill-by-name check reads no kill)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
363428388d
commit
0f54f1bdb5
4 changed files with 59 additions and 11 deletions
|
|
@ -135,6 +135,19 @@ if [ "$CMD" = add ] && [ "${KIND:-}" = restart ] && [ "${WHAT:-app}" = app ] &&
|
|||
fi
|
||||
case "$CMD" in add|list|remove|sign|verify) ;; *) sed -n '2,35p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; esac
|
||||
|
||||
# F14: the intake header of a 2.0.2+ engine reads `edition=lab|public` (the window lane's reviewb-202); a target whose last
|
||||
# upload says edition=public is refused on every add (the public miner has no jobs feed; a job for it is a mistake or worse).
|
||||
# Pre-2.0.2 headers carry no edition and pass. Reads tools/logs.mjs --rotation's header parse when the Mac has DATABASE_URL.
|
||||
target_edition() { # <machine id8> -> lab | public | '' (unknown or no DATABASE_URL)
|
||||
[ -f "$HOME/.config/igneum/env" ] || { echo ""; return; }
|
||||
node "$ROOT/tools/logs.mjs" --header "$1" 2>/dev/null | sed -n 's/.*edition=\([a-z]*\).*/\1/p' | head -1
|
||||
}
|
||||
if [ "$CMD" = add ] && [ -n "${TARGET:-}" ] && [ "$TARGET" != all ]; then
|
||||
for t in ${TARGET//,/ }; do
|
||||
ed="$(target_edition "$t")"
|
||||
if [ "$ed" = public ]; then echo "refused: $t last uploaded as edition=public (the public miner carries no remote execution, F14); only a lab build takes jobs" >&2; exit 2; fi
|
||||
done
|
||||
fi
|
||||
case "$JOBS_CHANNEL" in
|
||||
lab) KEY="$LAB_KEY"; PUB="$LAB_PUB"; [ -f "$LAB_KEY" ] || { echo "no $LAB_KEY: the lab channel signs with the lab root only" >&2; exit 1; } ;;
|
||||
public) ;;
|
||||
|
|
|
|||
|
|
@ -134,18 +134,21 @@ function App-Version {
|
|||
if (-not (Test-Path $urlFile)) { return '' }
|
||||
try { $u = (Get-Content $urlFile -Raw).Trim(); $st = Invoke-RestMethod -Uri ($u + 'api/state') -TimeoutSec 5 -UseBasicParsing; return [string]$st.version } catch { return '' }
|
||||
}
|
||||
function App-Product {
|
||||
function App-Edition {
|
||||
# F14 (the founder's ruling, 8 October 2026): the public build carries no remote execution, so this agent runs nothing beside
|
||||
# a public engine. The engine says what it is in api/state .product ("Igneum Miner Lab" | "Igneum Miner") from 2.0.2; an
|
||||
# engine with no product field is older than that and runs as before. '' when nothing answers.
|
||||
# a public engine. From 2.0.2 (the window lane's reviewb-202 ef3c8402) api/state carries `edition` = "lab" | "public",
|
||||
# `product` = "Igneum Miner Lab" | "Igneum Miner" and `channel` = "igneum-2.0-devnet-lab" | "igneum-2.0-devnet"; an engine
|
||||
# with none of them is older than 2.0.2 and runs as before. Returns @{edition; product; channel} or $null when nothing answers.
|
||||
$urlFile = Join-Path $env:LOCALAPPDATA 'igneum\app\app.url'
|
||||
if (-not (Test-Path $urlFile)) { return '' }
|
||||
try { $u = (Get-Content $urlFile -Raw).Trim(); $st = Invoke-RestMethod -Uri ($u + 'api/state') -TimeoutSec 5 -UseBasicParsing; if ($null -ne $st.product) { return [string]$st.product } else { return '' } } catch { return '' }
|
||||
if (-not (Test-Path $urlFile)) { return $null }
|
||||
try { $u = (Get-Content $urlFile -Raw).Trim(); $st = Invoke-RestMethod -Uri ($u + 'api/state') -TimeoutSec 5 -UseBasicParsing; return @{ edition = [string]$st.edition; product = [string]$st.product; channel = [string]$st.channel } } catch { return $null }
|
||||
}
|
||||
function Lab-Refusal {
|
||||
$p = App-Product
|
||||
if ($p -and $p -ne 'Igneum Miner Lab') { return ('the engine beside this agent is the public build (' + $p + '); the public miner carries no remote execution (F14), install the lab build on a fleet PC') }
|
||||
return ''
|
||||
$e = App-Edition
|
||||
if ($null -eq $e) { return '' }
|
||||
if (-not $e.edition -and -not $e.product) { return '' } # pre-2.0.2: no field, runs as before
|
||||
if ($e.edition -eq 'lab' -and $e.product -eq 'Igneum Miner Lab' -and $e.channel -eq 'igneum-2.0-devnet-lab') { return '' }
|
||||
return ('the engine beside this agent is not the lab build (edition "' + $e.edition + '", product "' + $e.product + '", channel "' + $e.channel + '"); the public miner carries no remote execution (F14): install Igneum Miner Lab on a fleet PC')
|
||||
}
|
||||
function Start-App {
|
||||
# MF-11: start the installed Igneum Miner and read back that an engine answers. Never elevated: an elevated agent starts it
|
||||
|
|
@ -194,6 +197,14 @@ function Run-Canon($task) {
|
|||
$f = $task.flags
|
||||
return ("igneum-relay-run/1`nto=" + $task.to + "`nnonce=" + $f.nonce + "`nelevated=" + (Flag-Text $f.elevated) + "`nreboot_continue=" + (Flag-Text $f.reboot_continue) + "`nreboot=" + (Flag-Text $f.reboot) + "`nbody_sha256=" + (Sha256-Hex ([Text.Encoding]::UTF8.GetBytes("$($task.body)"))) + "`n")
|
||||
}
|
||||
function Kill-By-Name-Refusal([string] $body) {
|
||||
# the founder's word, 8 October 2026, 20:21 UK ("STOP all these mess ups"; the fourth kill-by-name breach that day): by
|
||||
# construction, a task body that ends a process by its name is refused here, never run. A pid (Stop-Process -Id, taskkill
|
||||
# /PID) is the only way; the relay's own agent and the app's own quit are reached through their files and APIs.
|
||||
$patterns = @('(?im)^\s*[^#\r\n]*\bStop-Process\b[^\r\n]*-Name\b', '(?im)^\s*[^#\r\n]*\btaskkill(\.exe)?\b[^\r\n]*/IM\b', '(?im)^\s*[^#\r\n]*\bGet-Process\b[^\r\n]*-Name\b[^\r\n]*\|\s*Stop-Process', '(?im)^\s*[^#\r\n]*\b(pkill|killall)\b')
|
||||
foreach ($p in $patterns) { if ($body -match $p) { return ('the task ends a process by its NAME (' + ($Matches[0].Trim()).Substring(0, [math]::Min(80, ($Matches[0].Trim()).Length)) + '); by the founder''s word of 8 October 2026 a process is ended by its pid only, never by a name') } }
|
||||
return ''
|
||||
}
|
||||
function Check-Task($task) {
|
||||
# '' when the task may run, else why not (X23: nothing runs on the token alone)
|
||||
if (-not $MachineSecret) { return 'this PC has no machine secret; nothing runs until make-clients.sh --machine put machine-secret.txt here' }
|
||||
|
|
@ -203,6 +214,8 @@ function Check-Task($task) {
|
|||
if ((Test-Path $NonceFile) -and (Select-String -Path $NonceFile -Pattern ("^" + $f.nonce + "$") -Quiet)) { return 'nonce already executed on this PC' }
|
||||
$want = Hmac-Hex $MachineSecret (Run-Canon $task)
|
||||
if ($want -ne "$($f.mac)") { return 'the machine tag does not verify: not signed for this PC, or changed after signing' }
|
||||
$kb = Kill-By-Name-Refusal ([string]$task.body)
|
||||
if ($kb) { return $kb }
|
||||
return ''
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,20 @@ test('the installer fills the manifest and registers, runs and reads back the ta
|
|||
// F14 (the founder's ruling, 8 October 2026): the agent runs nothing beside a public engine; an engine without the product
|
||||
// field (pre-2.0.2) runs as before. Known-failed first.
|
||||
const agentLab = read('relay/clients/igneum-agent.ps1');
|
||||
assert.match(agentLab, /function App-Product/, 'the engine says what it is (api/state .product)');
|
||||
assert.match(agentLab, /\$p -ne 'Igneum Miner Lab'/, 'only the lab product runs tasks');
|
||||
assert.match(agentLab, /function App-Edition/, 'the engine says what it is (api/state edition, product, channel)');
|
||||
assert.match(agentLab, /\$e\.edition -eq 'lab' -and \$e\.product -eq 'Igneum Miner Lab' -and \$e\.channel -eq 'igneum-2\.0-devnet-lab'/, 'the window lane\'s exact strings (reviewb-202 ef3c8402): all three must read lab');
|
||||
assert.match(agentLab, /if \(-not \$why\) \{ \$why = Lab-Refusal \}/, 'the refusal sits in the task path before anything runs');
|
||||
assert.match(agentLab, /if \(\$null -ne \$st\.product\)/, 'no field: an older engine, allowed');
|
||||
assert.match(agentLab, /if \(-not \$e\.edition -and -not \$e\.product\) \{ return '' \}/, 'no field: an older engine, allowed');
|
||||
assert.match(agentLab, /install Igneum Miner Lab on a fleet PC/, 'the refusal names the remedy');
|
||||
// the founder's word (8 October 2026, 20:21 UK): a task that ends a process by its name is refused by construction
|
||||
assert.match(agentLab, /function Kill-By-Name-Refusal/, 'the agent refuses kill-by-name task bodies');
|
||||
const shapes = [['Stop-Process -Na', 'me igneum-app -Force'], ['task', 'kill /IM igneumd.exe /F'], ['Get-Process -Na', 'me igneum-miner | Stop-Process'], ['pk', 'ill -f igneumd'], ['kill', 'all igneum-app']].map(p => p.join(''));
|
||||
for (const bad of shapes) {
|
||||
const pats = [/^\s*[^#\r\n]*\bStop-Process\b[^\r\n]*-Name\b/im, /^\s*[^#\r\n]*\btaskkill(\.exe)?\b[^\r\n]*\/IM\b/im, /^\s*[^#\r\n]*\bGet-Process\b[^\r\n]*-Name\b[^\r\n]*\|\s*Stop-Process/im, /^\s*[^#\r\n]*\b(pkill|killall)\b/im];
|
||||
assert.ok(pats.some(p => p.test(bad)), `refused shape: ${bad}`);
|
||||
}
|
||||
assert.ok(![/\bStop-Process\b[^\r\n]*-Name\b/im].some(p => p.test('Stop-Process -Id $p.ProcessId -Force')), 'a pid is the allowed way');
|
||||
assert.match(agentLab, /\$kb = Kill-By-Name-Refusal \(\[string\]\$task\.body\)/, 'the check runs inside Check-Task before any body runs');
|
||||
|
||||
assert.doesNotMatch(s, /#Requires -RunAsAdministrator/, 'a per-user task needs no administrator (PC 2 has nobody to click a prompt)');
|
||||
assert.match(s, /\.Replace\('__RUNLEVEL__', \$level\)/);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
// intake key's fingerprint and the downloads folder's fingerprint), against the
|
||||
// fingerprints of ~/.config/igneum/log-intake-key.next and dl-token.next; exit 1
|
||||
// while any machine still reports with the old values
|
||||
// node tools/logs.mjs --header <id8> the latest app upload's IGNEUM-APP header line for that machine (win-<id8> or
|
||||
// mac-<id8>); F14: publish-jobs.sh reads its edition=lab|public before an add
|
||||
// node tools/logs.mjs --self-test the header parser on sample lines
|
||||
// Reads DATABASE_URL from ~/.config/igneum/env. No dependencies: Neon HTTP SQL over fetch.
|
||||
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
||||
|
|
@ -77,6 +79,16 @@ async function sql(query, params = []) {
|
|||
|
||||
const [runId, flag] = process.argv.slice(2);
|
||||
|
||||
if (runId === '--header') {
|
||||
const id8 = (flag || '').trim();
|
||||
if (!/^[0-9a-f]{8}$/.test(id8)) { console.error('--header <id8>'); process.exit(2); }
|
||||
const rows = await sql(`SELECT label, received_at, lines FROM miner_logs WHERE label IN ($1, $2) ORDER BY received_at DESC LIMIT 1`, [`win-${id8}`, `mac-${id8}`]);
|
||||
if (!rows.length) { console.log(''); process.exit(1); }
|
||||
const line = String(rows[0].lines || '').split('\n').map(l => l.replace(/^\d+(?:\.\d+)?\s+/, '')).find(l => l.startsWith('IGNEUM-APP version=')) || '';
|
||||
console.log(line);
|
||||
process.exit(line ? 0 : 1);
|
||||
}
|
||||
|
||||
if (runId === '--rotation') {
|
||||
const cfg = `${homedir()}/.config/igneum`;
|
||||
const want = { key: fingerprintFile(`${cfg}/log-intake-key.next`) || fingerprintFile(`${cfg}/log-intake-key`), folder: fingerprintFile(`${cfg}/dl-token.next`) || fingerprintFile(`${cfg}/dl-token`) };
|
||||
|
|
|
|||
Loading…
Reference in a new issue