diff --git a/agents/gander/work/queue-196/BUILD.md b/agents/gander/work/queue-196/BUILD.md index ce3dde23e..77454ecbd 100644 --- a/agents/gander/work/queue-196/BUILD.md +++ b/agents/gander/work/queue-196/BUILD.md @@ -104,3 +104,4 @@ changes are planned before the batch. M1-M29 remain prepared and unexecuted. 2026-10-21 r2 stop 2: base killer invocations 14/0 under 30 seconds each; M23-M27 killed, M28 survived due rebase making its generic mutation target the earlier SSO insertion. No M29 or golden suite run. Every restored hash and Git status clean; leftover process sweep count 0. Slot returned to Sage in gander-0035. Full per-step counts, base/mutant times and kill table in r2/run2/. Awaiting ruling before any fix or rerun; third stop goes to Mos. 2026-10-21 sage-2072 product test: added the merge-scoped SSO/socket conversion case without --send, asserting pointer argv order, rendered unit argv order, status agreement and no secret-file reads. Removing socket from sso_for_target is its planned RED case (M28b). Focused RED/GREEN remains pending the busy gate: Herb's suite was active at the preflight, so no test started. Runtime code unchanged. Mos mos-1197 owner-subject ruling recorded in queue-252/BUILD-NOTES.md. +2026-10-21 sage-2072/2077 driver: unique string-target guard preflights every definition before any tests and before creating scratch state; M28 names without_proof, M28b names kept, M5 names the parse catch, M25 names append flags, M29 uses the unique full line. Static audit accepts all 30 definitions; zero/duplicate targets refuse. Against 64539c96b, M5 old/narrowed bytes match; M25 differs and joins the next batch. No mutation tests ran in this static check. diff --git a/agents/gander/work/queue-196/mutant-guard.mjs b/agents/gander/work/queue-196/mutant-guard.mjs new file mode 100644 index 000000000..9c8b12ddf --- /dev/null +++ b/agents/gander/work/queue-196/mutant-guard.mjs @@ -0,0 +1,16 @@ +// Synchronous mutation preparation only. No file is written until every replacement is unique. +export function guardMutation(input, mutate, { auditOnly = false } = {}) { + const targets = []; + class UniqueText extends String { + replace(target, replacement) { return this.applyReplacement('replace', target, replacement); } + replaceAll(target, replacement) { return this.applyReplacement('replaceAll', target, replacement); } + applyReplacement(method, target, replacement) { + if (typeof target !== 'string' || !target) throw Error('mutation target must be a nonempty string'); + const source = String(this), count = source.split(target).length - 1; + targets.push({ method, target, count }); + if (count !== 1 && !auditOnly) throw Error(`mutation target occurs ${count} times, expected exactly once: ${target}`); + return new UniqueText(String.prototype[method].call(source, target, replacement)); + } + } + return { text: String(mutate(new UniqueText(input))), targets }; +} diff --git a/agents/gander/work/queue-196/mutants.mjs b/agents/gander/work/queue-196/mutants.mjs index a3342f63d..583fc4101 100644 --- a/agents/gander/work/queue-196/mutants.mjs +++ b/agents/gander/work/queue-196/mutants.mjs @@ -1,3 +1,4 @@ +import { guardMutation } from './mutant-guard.mjs'; // Authorized batch only. Mutate isolated scratch copies, never the candidate or installed tree. import {mkdtempSync,cpSync,readFileSync,writeFileSync,rmSync,symlinkSync,existsSync,mkdirSync} from 'node:fs'; import {tmpdir} from 'node:os';import {join,resolve} from 'node:path'; @@ -5,7 +6,6 @@ import {spawnSync,execFileSync} from 'node:child_process';import {createHash} fr import {fileURLToPath} from 'node:url'; const root=fileURLToPath(new URL('../../../../',import.meta.url)); const out=join(root,'agents/gander/work/queue-196/evidence/mutants');mkdirSync(out,{recursive:true}); -const scratch=mkdtempSync(join(tmpdir(),'g196-mut-')); const p='packages/bus/src/principal.mjs',h='packages/bus/src/human.mjs',w='packages/webui/src/tester-socket.mjs',s='packages/bus/src/server.mjs',peer='packages/bus/src/peercred.mjs'; const principal='packages/bus/tests/principal.test.mjs',socket='packages/webui/tests/tester-socket.test.mjs',transport='packages/bus/tests/peer-transport.test.mjs',interpreter='packages/bus/tests/interpreter.test.mjs'; const release=['--test-name-pattern=row196 explicit socket','packages/cli/tests/stack-release.test.mjs']; @@ -14,7 +14,7 @@ const specs=[ ['M2',w,'socket mode widened',socket,t=>t.replace('chmodSync(temporary, 0o600)','chmodSync(temporary, 0o666)')], ['M3','packages/webui/src/serve.mjs','sign-in accepted on tester',socket,t=>t.replace("if (tester && (Object.hasOwn(req.headers, 'authorization')", "if (false && (Object.hasOwn(req.headers, 'authorization')")], ['M4',p,'copied PID accepted',principal,t=>t.replace('peer.pid !== proof.pid','false')], - ['M5',peer,'helper parse error ignored','packages/bus/tests/peercred.test.mjs',t=>t.replace('} catch { finish(true); }','} catch { finish(false, {pid:2,uid:0,gid:0}); }')], + ['M5',peer,'helper parse error ignored','packages/bus/tests/peercred.test.mjs',t=>t.replace('finish(false, Object.freeze(p));\n } catch { finish(true); }','finish(false, Object.freeze(p));\n } catch { finish(false, {pid:2,uid:0,gid:0}); }')], ['M6',p,'foreign uid accepted',principal,t=>t.replace('peer.uid !== process.getuid()','false')], ['M7',w,'unsafe parent accepted',socket,t=>t.replace('(dir.mode & 0o077)','false')], ['M8',h,'agent environment walk skipped',principal,t=>t.replace('current.env !== null && MARKERS.some','false && MARKERS.some')], @@ -34,19 +34,25 @@ const specs=[ ['M22',s,'cap requests invoke helper',transport,t=>t.replace(' if (kind) {',' if (r.cap) await peers.read(socket);\n if (kind) {')], ['M23','packages/bus/src/interpreter.mjs','restart retains stale identity',interpreter,t=>t.replace('export function recordInterpreterStart(directory, record) {','export function recordInterpreterStart(directory, record) {\n try { readFileSync(join(directory,"peercred-starts.jsonl")); return; } catch {}')], ['M24',w,'listener binds final path directly',socket,t=>t.replace("const temporary = join(parent, '.' + randomBytes(4).toString('hex') + '.s');",'const temporary = path;')], - ['M25','packages/bus/src/interpreter.mjs','FIFO receipt blocks before type refusal',['--test-name-pattern=non-regular startup receipt',interpreter],t=>t.replaceAll(' | constants.O_NONBLOCK','')], + ['M25','packages/bus/src/interpreter.mjs','FIFO receipt blocks before type refusal',['--test-name-pattern=non-regular startup receipt',interpreter],t=>t.replace('constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600','constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600')], ['M26',peer,'missing helper accepted at startup',['--test-name-pattern=missing or non-regular peer helper','packages/bus/tests/peercred.test.mjs'],t=>t.replace('if (!s.isFile() || s.isSymbolicLink()) throw Error();','if (false) throw Error();').replace('catch { throw refused(); }','catch {}')], ['M27',peer,'shutdown returns before helpers are reaped',['--test-name-pattern=shutdown waits','packages/bus/tests/peercred.test.mjs'],t=>t.replace(" await Promise.all([...children].map(child => new Promise(resolve => {\n child.once('close', resolve);\n child.kill('SIGKILL');\n })));"," for (const child of children) child.kill('SIGKILL');")], - ['M28','scripts/stack-release.sh','proof pair placed after tester socket flag',release,t=>t.replace('--send | --tester-port | --tester-socket) break','--send | --tester-port) break')], - ['M29','scripts/stack-release.sh','unsafe tester path reaches service stop',release,t=>t.split('\n').filter(line=>!line.includes('|| check_refuse tester-socket-path')).join('\n')], + ['M28','scripts/stack-release.sh','proof pair placed after tester socket flag',release,t=>t.replace('case "${without_proof[i]}" in --send | --tester-port | --tester-socket) break','case "${without_proof[i]}" in --send | --tester-port) break')], + ['M28b','scripts/stack-release.sh','SSO placed after tester socket flag',['--test-name-pattern=row196 SSO socket','packages/cli/tests/stack-release.test.mjs'],t=>t.replace('case "${kept[i]}" in --send | --tester-port | --tester-socket) break','case "${kept[i]}" in --send | --tester-port) break')], + ['M29','scripts/stack-release.sh','unsafe tester path reaches service stop',release,t=>t.replace(t.split('\n').find(line=>line.includes('|| check_refuse tester-socket-path'))+'\n','')], ]; const hash=x=>createHash('sha256').update(x).digest('hex'); const results=[]; +for (const [id,file,,,mutate] of specs) { + try { guardMutation(readFileSync(join(root,file),'utf8'),mutate); } + catch(error) { throw Error(id+': '+error.message); } +} +const scratch=mkdtempSync(join(tmpdir(),'g196-mut-')); try { for(const name of ['packages','scripts','adapters','roles','contracts','RELEASE','package.json','package-lock.json','.git'])if(existsSync(join(root,name)))cpSync(join(root,name),join(scratch,name),{recursive:true,filter:src=>!src.includes('/node_modules/')&&!src.endsWith('/node_modules')}); symlinkSync(join(root,'node_modules'),join(scratch,'node_modules')); for(const [id,file,description,test,mutate] of specs) { - const at=join(scratch,file),original=readFileSync(at,'utf8'),before=hash(original),changed=mutate(original); + const at=join(scratch,file),original=readFileSync(at,'utf8'),before=hash(original),changed=guardMutation(original,mutate).text; if(changed===original)throw Error(id+' did not change bytes'); writeFileSync(at,changed); const syntax=spawnSync(file.endsWith('.sh')?'bash':process.execPath,[file.endsWith('.sh')?'-n':'--check',at],{encoding:'utf8'}); diff --git a/agents/gander/work/queue-196/r2/driver.mjs b/agents/gander/work/queue-196/r2/driver.mjs index 508fa4ad3..44b8b1988 100644 --- a/agents/gander/work/queue-196/r2/driver.mjs +++ b/agents/gander/work/queue-196/r2/driver.mjs @@ -1,3 +1,4 @@ +import { guardMutation } from '../mutant-guard.mjs'; // Authorized batch only. Mutate isolated scratch copies, never the candidate or installed tree. import {mkdtempSync,cpSync,readFileSync,writeFileSync,rmSync,symlinkSync,existsSync,mkdirSync} from 'node:fs'; import {tmpdir} from 'node:os';import {join,resolve} from 'node:path'; @@ -5,7 +6,6 @@ import {spawnSync,execFileSync} from 'node:child_process';import {createHash} fr import {fileURLToPath} from 'node:url'; const root='/mnt/storage/scratch/tmp/g196-tree-tn651z'; const out='/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/agents/gander/work/queue-196/r2/mutants';mkdirSync(out,{recursive:true}); -const scratch=mkdtempSync(join(tmpdir(),'g196-mut-')); const p='packages/bus/src/principal.mjs',h='packages/bus/src/human.mjs',w='packages/webui/src/tester-socket.mjs',s='packages/bus/src/server.mjs',peer='packages/bus/src/peercred.mjs'; const principal='packages/bus/tests/principal.test.mjs',socket='packages/webui/tests/tester-socket.test.mjs',transport='packages/bus/tests/peer-transport.test.mjs',interpreter='packages/bus/tests/interpreter.test.mjs'; const release=['--test-name-pattern=row196 explicit socket','packages/cli/tests/stack-release.test.mjs']; @@ -14,7 +14,7 @@ const specs=[ ['M2',w,'socket mode widened',socket,t=>t.replace('chmodSync(temporary, 0o600)','chmodSync(temporary, 0o666)')], ['M3','packages/webui/src/serve.mjs','sign-in accepted on tester',socket,t=>t.replace("if (tester && (Object.hasOwn(req.headers, 'authorization')", "if (false && (Object.hasOwn(req.headers, 'authorization')")], ['M4',p,'copied PID accepted',principal,t=>t.replace('peer.pid !== proof.pid','false')], - ['M5',peer,'helper parse error ignored','packages/bus/tests/peercred.test.mjs',t=>t.replace('} catch { finish(true); }','} catch { finish(false, {pid:2,uid:0,gid:0}); }')], + ['M5',peer,'helper parse error ignored','packages/bus/tests/peercred.test.mjs',t=>t.replace('finish(false, Object.freeze(p));\n } catch { finish(true); }','finish(false, Object.freeze(p));\n } catch { finish(false, {pid:2,uid:0,gid:0}); }')], ['M6',p,'foreign uid accepted',principal,t=>t.replace('peer.uid !== process.getuid()','false')], ['M7',w,'unsafe parent accepted',socket,t=>t.replace('(dir.mode & 0o077)','false')], ['M8',h,'agent environment walk skipped',principal,t=>t.replace('current.env !== null && MARKERS.some','false && MARKERS.some')], @@ -34,20 +34,26 @@ const specs=[ ['M22',s,'cap requests invoke helper',transport,t=>t.replace(' if (kind) {',' if (r.cap) await peers.read(socket);\n if (kind) {')], ['M23','packages/bus/src/interpreter.mjs','restart retains stale identity',interpreter,t=>t.replace('export function recordInterpreterStart(directory, record) {','export function recordInterpreterStart(directory, record) {\n try { readFileSync(join(directory,"peercred-starts.jsonl")); return; } catch {}')], ['M24',w,'listener binds final path directly',socket,t=>t.replace("const temporary = join(parent, '.' + randomBytes(4).toString('hex') + '.s');",'const temporary = path;')], - ['M25','packages/bus/src/interpreter.mjs','FIFO receipt blocks before type refusal',['--test-name-pattern=non-regular startup receipt',interpreter],t=>t.replaceAll(' | constants.O_NONBLOCK','')], + ['M25','packages/bus/src/interpreter.mjs','FIFO receipt blocks before type refusal',['--test-name-pattern=non-regular startup receipt',interpreter],t=>t.replace('constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600','constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600')], ['M26',peer,'missing helper accepted at startup',['--test-name-pattern=missing or non-regular peer helper','packages/bus/tests/peercred.test.mjs'],t=>t.replace('if (!s.isFile() || s.isSymbolicLink()) throw Error();','if (false) throw Error();').replace('catch { throw refused(); }','catch {}')], ['M27',peer,'shutdown returns before helpers are reaped',['--test-name-pattern=shutdown waits','packages/bus/tests/peercred.test.mjs'],t=>t.replace(" await Promise.all([...children].map(child => new Promise(resolve => {\n child.once('close', resolve);\n child.kill('SIGKILL');\n })));"," for (const child of children) child.kill('SIGKILL');")], - ['M28','scripts/stack-release.sh','proof pair placed after tester socket flag',release,t=>t.replace('--send | --tester-port | --tester-socket) break','--send | --tester-port) break')], - ['M29','scripts/stack-release.sh','unsafe tester path reaches service stop',release,t=>t.split('\n').filter(line=>!line.includes('|| check_refuse tester-socket-path')).join('\n')], + ['M28','scripts/stack-release.sh','proof pair placed after tester socket flag',release,t=>t.replace('case "${without_proof[i]}" in --send | --tester-port | --tester-socket) break','case "${without_proof[i]}" in --send | --tester-port) break')], + ['M28b','scripts/stack-release.sh','SSO placed after tester socket flag',['--test-name-pattern=row196 SSO socket','packages/cli/tests/stack-release.test.mjs'],t=>t.replace('case "${kept[i]}" in --send | --tester-port | --tester-socket) break','case "${kept[i]}" in --send | --tester-port) break')], + ['M29','scripts/stack-release.sh','unsafe tester path reaches service stop',release,t=>t.replace(t.split('\n').find(line=>line.includes('|| check_refuse tester-socket-path'))+'\n','')], ]; const hash=x=>createHash('sha256').update(x).digest('hex'); const results=[]; +for (const [id,file,,,mutate] of specs) { + try { guardMutation(readFileSync(join(root,file),'utf8'),mutate); } + catch(error) { throw Error(id+': '+error.message); } +} +const scratch=mkdtempSync(join(tmpdir(),'g196-mut-')); try { for(const name of ['packages','scripts','adapters','roles','contracts','RELEASE','package.json','package-lock.json','.gitignore'])if(existsSync(join(root,name)))cpSync(join(root,name),join(scratch,name),{recursive:true,filter:src=>!src.includes('/node_modules/')&&!src.endsWith('/node_modules')}); symlinkSync(join(root,'node_modules'),join(scratch,'node_modules')); for(const args of [['init','-q'],['add','.'],['-c','user.name=fixture','-c','user.email=fixture@invalid','-c','commit.gpgsign=false','-c','core.hooksPath=/dev/null','commit','-qm','candidate snapshot']])execFileSync('git',args,{cwd:scratch}); for(const [id,file,description,test,mutate] of specs) { - const at=join(scratch,file),original=readFileSync(at,'utf8'),before=hash(original),changed=mutate(original); + const at=join(scratch,file),original=readFileSync(at,'utf8'),before=hash(original),changed=guardMutation(original,mutate).text; if(changed===original)throw Error(id+' did not change bytes'); writeFileSync(at,changed); const syntax=spawnSync(file.endsWith('.sh')?'bash':process.execPath,[file.endsWith('.sh')?'-n':'--check',at],{encoding:'utf8'}); diff --git a/agents/gander/work/queue-196/r2/run3/M25-narrowed.mjs b/agents/gander/work/queue-196/r2/run3/M25-narrowed.mjs new file mode 100644 index 000000000..d09c4596e --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/M25-narrowed.mjs @@ -0,0 +1,67 @@ +import { realpathSync, statSync, readFileSync, openSync, fstatSync, writeSync, fsyncSync, closeSync, constants } from 'node:fs'; +import { dirname, join, sep } from 'node:path'; +import { createHash } from 'node:crypto'; +import { execFileSync } from 'node:child_process'; + +const systemPython = '/usr/bin/python3'; +const defaultIO = { realpath: realpathSync, stat: statSync, readFile: readFileSync, exec: execFileSync }; +const below = (path, parent) => path === parent || path.startsWith(parent + sep); +// Trusted dependency injection is for offline tests, never a CLI/env interpreter override. +export function inspectInterpreter({ io = defaultIO } = {}) { + try { + const path = io.realpath(systemPython); + const homes = ['/home', '/root', ...String(io.readFile('/etc/passwd', 'utf8')).split('\n') + .map(line => line.split(':')[5]).filter(p => p?.startsWith('/') && p !== '/')]; + if (!path.startsWith('/') || homes.some(home => below(path, home))) throw Error(); + // Check both the system entry's parent and every resolved component. /usr/bin/python3 + // may be a system symlink; its containing directory and resolved target must be safe. + for (const start of [dirname(systemPython), path]) { + for (let at = start;; at = dirname(at)) { + const s = io.stat(at); + if (s.uid !== 0 || (s.mode & 0o022) || (at === path ? !s.isFile() : !s.isDirectory())) throw Error(); + if (at === '/') break; + } + } + const data = io.readFile(path); + if (!Buffer.isBuffer(data) || !data.subarray(0, 4).equals(Buffer.from([127, 69, 76, 70]))) throw Error(); + const version = JSON.parse(String(io.exec(path, ['-I', '-S', '-c', 'import json,sys,socket;assert hasattr(socket,"SO_PEERCRED");print(json.dumps(list(sys.version_info[:3])))'], + { env: {}, timeout: 1000, maxBuffer: 256, encoding: 'utf8' }))); + if (!Array.isArray(version) || version.length !== 3 || !version.every(Number.isSafeInteger) || + version[0] < 3 || (version[0] === 3 && version[1] < 10)) throw Error(); + const s = io.stat(path); + return Object.freeze({ path, version: Object.freeze(version), dev: s.dev, ino: s.ino, + sha256: createHash('sha256').update(data).digest('hex') }); + } catch { throw Error('interpreter-unsafe'); } +} + +export function verifyInterpreterReceipt(directory, expected, since) { + let fd; + try { + fd = openSync(join(directory, 'peercred-starts.jsonl'), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK); + const s = fstatSync(fd); + if (!s.isFile() || s.uid !== process.getuid() || (s.mode & 0o077) || s.nlink !== 1) throw Error(); + const text = readFileSync(fd, 'utf8'); + if (!text.endsWith('\n')) throw Error(); + const actual = JSON.parse(text.trimEnd().split('\n').at(-1)); + if (actual.copy !== new URL('../../../', import.meta.url).pathname || !(Date.parse(actual.at) >= Number(since)) || + Date.parse(actual.at) > Date.now() + 1000 || ['path', 'sha256', 'dev', 'ino', 'version'].some(k => JSON.stringify(actual[k]) !== JSON.stringify(expected[k]))) throw Error(); + return true; + } catch { throw Error('interpreter-receipt-mismatch'); } + finally { if (fd !== undefined) closeSync(fd); } +} + +export function recordInterpreterStart(directory, record) { + let fd; + try { + const dir = statSync(directory); + if (!dir.isDirectory() || dir.uid !== process.getuid() || (dir.mode & 0o077)) throw Error(); + fd = openSync(join(directory, 'peercred-starts.jsonl'), + constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600); + const s = fstatSync(fd); + if (!s.isFile() || s.uid !== process.getuid() || (s.mode & 0o077) || s.nlink !== 1) throw Error(); + const line = Buffer.from(JSON.stringify({ at: new Date().toISOString(), copy: new URL('../../../', import.meta.url).pathname, ...record }) + '\n'); + if (writeSync(fd, line) !== line.length) throw Error(); + fsyncSync(fd); + } catch { throw Error('interpreter-receipt-unsafe'); } + finally { if (fd !== undefined) closeSync(fd); } +} diff --git a/agents/gander/work/queue-196/r2/run3/M25-old.mjs b/agents/gander/work/queue-196/r2/run3/M25-old.mjs new file mode 100644 index 000000000..e887f915b --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/M25-old.mjs @@ -0,0 +1,67 @@ +import { realpathSync, statSync, readFileSync, openSync, fstatSync, writeSync, fsyncSync, closeSync, constants } from 'node:fs'; +import { dirname, join, sep } from 'node:path'; +import { createHash } from 'node:crypto'; +import { execFileSync } from 'node:child_process'; + +const systemPython = '/usr/bin/python3'; +const defaultIO = { realpath: realpathSync, stat: statSync, readFile: readFileSync, exec: execFileSync }; +const below = (path, parent) => path === parent || path.startsWith(parent + sep); +// Trusted dependency injection is for offline tests, never a CLI/env interpreter override. +export function inspectInterpreter({ io = defaultIO } = {}) { + try { + const path = io.realpath(systemPython); + const homes = ['/home', '/root', ...String(io.readFile('/etc/passwd', 'utf8')).split('\n') + .map(line => line.split(':')[5]).filter(p => p?.startsWith('/') && p !== '/')]; + if (!path.startsWith('/') || homes.some(home => below(path, home))) throw Error(); + // Check both the system entry's parent and every resolved component. /usr/bin/python3 + // may be a system symlink; its containing directory and resolved target must be safe. + for (const start of [dirname(systemPython), path]) { + for (let at = start;; at = dirname(at)) { + const s = io.stat(at); + if (s.uid !== 0 || (s.mode & 0o022) || (at === path ? !s.isFile() : !s.isDirectory())) throw Error(); + if (at === '/') break; + } + } + const data = io.readFile(path); + if (!Buffer.isBuffer(data) || !data.subarray(0, 4).equals(Buffer.from([127, 69, 76, 70]))) throw Error(); + const version = JSON.parse(String(io.exec(path, ['-I', '-S', '-c', 'import json,sys,socket;assert hasattr(socket,"SO_PEERCRED");print(json.dumps(list(sys.version_info[:3])))'], + { env: {}, timeout: 1000, maxBuffer: 256, encoding: 'utf8' }))); + if (!Array.isArray(version) || version.length !== 3 || !version.every(Number.isSafeInteger) || + version[0] < 3 || (version[0] === 3 && version[1] < 10)) throw Error(); + const s = io.stat(path); + return Object.freeze({ path, version: Object.freeze(version), dev: s.dev, ino: s.ino, + sha256: createHash('sha256').update(data).digest('hex') }); + } catch { throw Error('interpreter-unsafe'); } +} + +export function verifyInterpreterReceipt(directory, expected, since) { + let fd; + try { + fd = openSync(join(directory, 'peercred-starts.jsonl'), constants.O_RDONLY | constants.O_NOFOLLOW); + const s = fstatSync(fd); + if (!s.isFile() || s.uid !== process.getuid() || (s.mode & 0o077) || s.nlink !== 1) throw Error(); + const text = readFileSync(fd, 'utf8'); + if (!text.endsWith('\n')) throw Error(); + const actual = JSON.parse(text.trimEnd().split('\n').at(-1)); + if (actual.copy !== new URL('../../../', import.meta.url).pathname || !(Date.parse(actual.at) >= Number(since)) || + Date.parse(actual.at) > Date.now() + 1000 || ['path', 'sha256', 'dev', 'ino', 'version'].some(k => JSON.stringify(actual[k]) !== JSON.stringify(expected[k]))) throw Error(); + return true; + } catch { throw Error('interpreter-receipt-mismatch'); } + finally { if (fd !== undefined) closeSync(fd); } +} + +export function recordInterpreterStart(directory, record) { + let fd; + try { + const dir = statSync(directory); + if (!dir.isDirectory() || dir.uid !== process.getuid() || (dir.mode & 0o077)) throw Error(); + fd = openSync(join(directory, 'peercred-starts.jsonl'), + constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600); + const s = fstatSync(fd); + if (!s.isFile() || s.uid !== process.getuid() || (s.mode & 0o077) || s.nlink !== 1) throw Error(); + const line = Buffer.from(JSON.stringify({ at: new Date().toISOString(), copy: new URL('../../../', import.meta.url).pathname, ...record }) + '\n'); + if (writeSync(fd, line) !== line.length) throw Error(); + fsyncSync(fd); + } catch { throw Error('interpreter-receipt-unsafe'); } + finally { if (fd !== undefined) closeSync(fd); } +} diff --git a/agents/gander/work/queue-196/r2/run3/M5-narrowed.mjs b/agents/gander/work/queue-196/r2/run3/M5-narrowed.mjs new file mode 100644 index 000000000..9ae73a7be --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/M5-narrowed.mjs @@ -0,0 +1,58 @@ +import { spawn as nodeSpawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { lstatSync } from 'node:fs'; +import { BusError } from './broker.mjs'; + +const helper = fileURLToPath(new URL('./peercred.py', import.meta.url)); +const refused = () => new BusError('peercred-required'); +export function createPeerReader({ interpreter, spawn = nodeSpawn, timeout = 1000, stat = lstatSync } = {}) { + try { const s = stat(helper); if (!s.isFile() || s.isSymbolicLink()) throw Error(); } + catch { throw refused(); } + const children = new Set(); + let closed = false; + return { + read(socket) { + if (closed || children.size >= 16) return Promise.reject(refused()); + return new Promise((resolve, reject) => { + let child, timer, output = '', settled = false, invalid = false; + const finish = (error, value) => { + if (settled) return; + settled = true; + clearTimeout(timer); + error ? reject(refused()) : resolve(value); + }; + try { + // Keep this connection's Socket in this closure. Never borrow another pending fd. + child = spawn(interpreter.path, ['-I', '-S', helper], + { env: {}, stdio: ['ignore', 'pipe', 'pipe', socket] }); + children.add(child); + timer = setTimeout(() => { child.kill('SIGKILL'); finish(true); }, timeout); + child.stdout.on('data', data => { + output += data.toString('utf8'); + if (Buffer.byteLength(output) > 256) { invalid = true; child.kill('SIGKILL'); finish(true); } + }); + child.stderr.on('data', () => { invalid = true; }); + child.on('error', () => { children.delete(child); finish(true); }); + child.on('close', (code, signal) => { + children.delete(child); + if (settled) return; + try { + if (closed || invalid || code !== 0 || signal) throw Error(); + const p = JSON.parse(output); + if (!p || Array.isArray(p) || Object.keys(p).sort().join(',') !== 'gid,pid,uid' || + !['pid', 'uid', 'gid'].every(k => Number.isSafeInteger(p[k])) || p.pid < 2 || p.uid < 0 || p.gid < 0) throw Error(); + finish(false, Object.freeze(p)); + } catch { finish(false, {pid:2,uid:0,gid:0}); } + }); + } catch { finish(true); } + }); + }, + async close() { + closed = true; + await Promise.all([...children].map(child => new Promise(resolve => { + child.once('close', resolve); + child.kill('SIGKILL'); + }))); + }, + }; +} diff --git a/agents/gander/work/queue-196/r2/run3/M5-old.mjs b/agents/gander/work/queue-196/r2/run3/M5-old.mjs new file mode 100644 index 000000000..9ae73a7be --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/M5-old.mjs @@ -0,0 +1,58 @@ +import { spawn as nodeSpawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { lstatSync } from 'node:fs'; +import { BusError } from './broker.mjs'; + +const helper = fileURLToPath(new URL('./peercred.py', import.meta.url)); +const refused = () => new BusError('peercred-required'); +export function createPeerReader({ interpreter, spawn = nodeSpawn, timeout = 1000, stat = lstatSync } = {}) { + try { const s = stat(helper); if (!s.isFile() || s.isSymbolicLink()) throw Error(); } + catch { throw refused(); } + const children = new Set(); + let closed = false; + return { + read(socket) { + if (closed || children.size >= 16) return Promise.reject(refused()); + return new Promise((resolve, reject) => { + let child, timer, output = '', settled = false, invalid = false; + const finish = (error, value) => { + if (settled) return; + settled = true; + clearTimeout(timer); + error ? reject(refused()) : resolve(value); + }; + try { + // Keep this connection's Socket in this closure. Never borrow another pending fd. + child = spawn(interpreter.path, ['-I', '-S', helper], + { env: {}, stdio: ['ignore', 'pipe', 'pipe', socket] }); + children.add(child); + timer = setTimeout(() => { child.kill('SIGKILL'); finish(true); }, timeout); + child.stdout.on('data', data => { + output += data.toString('utf8'); + if (Buffer.byteLength(output) > 256) { invalid = true; child.kill('SIGKILL'); finish(true); } + }); + child.stderr.on('data', () => { invalid = true; }); + child.on('error', () => { children.delete(child); finish(true); }); + child.on('close', (code, signal) => { + children.delete(child); + if (settled) return; + try { + if (closed || invalid || code !== 0 || signal) throw Error(); + const p = JSON.parse(output); + if (!p || Array.isArray(p) || Object.keys(p).sort().join(',') !== 'gid,pid,uid' || + !['pid', 'uid', 'gid'].every(k => Number.isSafeInteger(p[k])) || p.pid < 2 || p.uid < 0 || p.gid < 0) throw Error(); + finish(false, Object.freeze(p)); + } catch { finish(false, {pid:2,uid:0,gid:0}); } + }); + } catch { finish(true); } + }); + }, + async close() { + closed = true; + await Promise.all([...children].map(child => new Promise(resolve => { + child.once('close', resolve); + child.kill('SIGKILL'); + }))); + }, + }; +} diff --git a/agents/gander/work/queue-196/r2/run3/audit-initial.mjs b/agents/gander/work/queue-196/r2/run3/audit-initial.mjs new file mode 100644 index 000000000..53878bd66 --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/audit-initial.mjs @@ -0,0 +1,15 @@ +import {readFileSync,writeFileSync} from 'node:fs';import vm from 'node:vm';import {join} from 'node:path'; +const source=readFileSync('agents/gander/work/queue-196/r2/driver.mjs','utf8'); +const context=vm.createContext({});vm.runInContext(source.slice(source.indexOf("const p='"),source.indexOf('const hash='))+';globalThis.rows=specs;',context); +const rows=[]; +// The mutate functions originate in the VM, so instrument that realm's string methods. +context.audit=[]; +vm.runInContext(`for(const method of ['replace','replaceAll']){const original=String.prototype[method];String.prototype[method]=function(target,...rest){const text=String(this);if(typeof target==='string')audit.push({method,target,count:text.split(target).length-1});return original.call(this,target,...rest);};}`,context); +for(const [id,file,description,test,mutate] of context.rows){ + context.audit.length=0;const original=readFileSync(join('/mnt/storage/scratch/tmp/g196-tree-tn651z',file),'utf8'); + // Force the input primitive to be boxed by the VM realm before mutation. + context.input=original;context.fn=mutate;vm.runInContext('fn(input)',context); + rows.push({id,file,targets:[...context.audit],nonUnique:context.audit.filter(x=>x.count!==1)}); +} +writeFileSync('agents/gander/work/queue-196/r2/run3/uniqueness-initial.json',JSON.stringify(rows,null,2)+'\n'); +for(const row of rows)if(row.nonUnique.length)console.log(JSON.stringify(row)); diff --git a/agents/gander/work/queue-196/r2/run3/audit.mjs b/agents/gander/work/queue-196/r2/run3/audit.mjs new file mode 100644 index 000000000..732bbb1fe --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/audit.mjs @@ -0,0 +1,17 @@ +import {readFileSync,writeFileSync} from 'node:fs';import vm from 'node:vm';import {join} from 'node:path';import {createHash} from 'node:crypto'; +import {guardMutation} from '../../mutant-guard.mjs'; +const root='/mnt/storage/scratch/tmp/g196-tree-tn651z',out=new URL('.',import.meta.url).pathname; +const source=readFileSync('agents/gander/work/queue-196/r2/driver.mjs','utf8'); +const context=vm.createContext({});vm.runInContext(source.slice(source.indexOf("const p='"),source.indexOf('const hash='))+';globalThis.rows=specs;',context); +const rows=[]; +for(const [id,file,,,mutate] of context.rows){const original=readFileSync(join(root,file),'utf8');const result=guardMutation(original,mutate);rows.push({id,file,targets:result.targets,accepted:true});} +writeFileSync(out+'uniqueness-final.json',JSON.stringify(rows,null,2)+'\n');console.log(rows.length+' definitions pass uniqueness guard; no mutation tests run'); +const sha=s=>createHash('sha256').update(s).digest('hex'),pairs=[]; +for(const id of ['M5','M25']){ + const row=context.rows.find(r=>r[0]===id),original=readFileSync(join(root,row[1]),'utf8'); + const old=id==='M5'?original.replace('} catch { finish(true); }','} catch { finish(false, {pid:2,uid:0,gid:0}); }'):original.replaceAll(' | constants.O_NONBLOCK',''); + const narrowed=guardMutation(original,row[4]).text; + writeFileSync(out+id+'-old.mjs',old);writeFileSync(out+id+'-narrowed.mjs',narrowed); + pairs.push({id,product:'64539c96b',oldSha256:sha(old),narrowedSha256:sha(narrowed),identical:old===narrowed}); +} +writeFileSync(out+'narrowing-sha256.json',JSON.stringify(pairs,null,2)+'\n');console.log(JSON.stringify(pairs,null,2)); diff --git a/agents/gander/work/queue-196/r2/run3/guard-refusals.json b/agents/gander/work/queue-196/r2/run3/guard-refusals.json new file mode 100644 index 000000000..b651de85f --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/guard-refusals.json @@ -0,0 +1,25 @@ +[ + { + "count": 0, + "refused": true + }, + { + "count": 2, + "refused": true + }, + { + "chainedUniqueTargets": [ + { + "method": "replace", + "target": "left", + "count": 1 + }, + { + "method": "replace", + "target": "right", + "count": 1 + } + ], + "accepted": true + } +] diff --git a/agents/gander/work/queue-196/r2/run3/narrowing-sha256.json b/agents/gander/work/queue-196/r2/run3/narrowing-sha256.json new file mode 100644 index 000000000..441d2ec26 --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/narrowing-sha256.json @@ -0,0 +1,16 @@ +[ + { + "id": "M5", + "product": "64539c96b", + "oldSha256": "803bd73ccaf0730a7ab69c70d513257484df788fa96c13c3c4ed87664b67c45a", + "narrowedSha256": "803bd73ccaf0730a7ab69c70d513257484df788fa96c13c3c4ed87664b67c45a", + "identical": true + }, + { + "id": "M25", + "product": "64539c96b", + "oldSha256": "2668be95c72562a76987ed3cdffdc8ee029ac15bd12eb8b23f4a541eb4c53e28", + "narrowedSha256": "cd20a831fbaecbd1aedcbb634062786099266d0a6196d9b8987706dfd5a6aa1c", + "identical": false + } +] diff --git a/agents/gander/work/queue-196/r2/run3/uniqueness-final.json b/agents/gander/work/queue-196/r2/run3/uniqueness-final.json new file mode 100644 index 000000000..dce764008 --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/uniqueness-final.json @@ -0,0 +1,372 @@ +[ + { + "id": "M1", + "file": "packages/webui/src/serve.mjs", + "targets": [ + { + "method": "replace", + "target": "if (tester) return bindTesterSocket(server, testerSocket);", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M2", + "file": "packages/webui/src/tester-socket.mjs", + "targets": [ + { + "method": "replace", + "target": "chmodSync(temporary, 0o600)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M3", + "file": "packages/webui/src/serve.mjs", + "targets": [ + { + "method": "replace", + "target": "if (tester && (Object.hasOwn(req.headers, 'authorization')", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M4", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "peer.pid !== proof.pid", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M5", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": "finish(false, Object.freeze(p));\n } catch { finish(true); }", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M6", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "peer.uid !== process.getuid()", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M7", + "file": "packages/webui/src/tester-socket.mjs", + "targets": [ + { + "method": "replace", + "target": "(dir.mode & 0o077)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M8", + "file": "packages/bus/src/human.mjs", + "targets": [ + { + "method": "replace", + "target": "current.env !== null && MARKERS.some", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M9", + "file": "packages/bus/src/human.mjs", + "targets": [ + { + "method": "replace", + "target": "if (again.startTime !== proof.startTime || again.ppid !== p.ppid)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M10", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "if (parent.uid !== peer.uid || parent.argv[1] !== resolve(paths.console) || parent.argv[2] !== 'serve')", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M11", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "import { resolve }", + "count": 1 + }, + { + "method": "replace", + "target": "parent.argv[1] !== resolve(paths.console)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M12", + "file": "packages/bus/src/human.mjs", + "targets": [ + { + "method": "replace", + "target": "Object.keys(proof).some((k) => !['pid', 'startTime', 'nonce', 'business'].includes(k))", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M13", + "file": "packages/bus/src/server.mjs", + "targets": [ + { + "method": "replace", + "target": "broker.disconnect(boundCap)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M14", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "verifyHuman(checked, { cliPath: paths[kind], launches, readProcess,\n nonceKey: kind === 'user' ? 'MOSAIC_BUS_USER_NONCE' : 'MOSAIC_BUS_CLI_NONCE' })", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M15", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "RUN_CONSOLE=(\"${without_tester[@]}\" --tester-socket)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M16", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "RUN_CONSOLE=(\"${without_tester[@]}\") DROP_TESTER=\"--tester-socket", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M17", + "file": "packages/bus/src/broker.mjs", + "targets": [ + { + "method": "replace", + "target": "if (this.#business(record.business).vars?.['bus.tester'] !== true)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M18", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "return { kind: 'user', business, id: proof.user }", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M19", + "file": "packages/cli/src/tester-http.mjs", + "targets": [ + { + "method": "replace", + "target": "socketPath: testerSocketPath(dataRoot)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M20", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": "export function createPeerReader", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M21", + "file": "packages/bus/src/interpreter.mjs", + "targets": [ + { + "method": "replace", + "target": "s.uid !== 0 || (s.mode & 0o022)", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M22", + "file": "packages/bus/src/server.mjs", + "targets": [ + { + "method": "replace", + "target": " if (kind) {", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M23", + "file": "packages/bus/src/interpreter.mjs", + "targets": [ + { + "method": "replace", + "target": "export function recordInterpreterStart(directory, record) {", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M24", + "file": "packages/webui/src/tester-socket.mjs", + "targets": [ + { + "method": "replace", + "target": "const temporary = join(parent, '.' + randomBytes(4).toString('hex') + '.s');", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M25", + "file": "packages/bus/src/interpreter.mjs", + "targets": [ + { + "method": "replace", + "target": "constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M26", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": "if (!s.isFile() || s.isSymbolicLink()) throw Error();", + "count": 1 + }, + { + "method": "replace", + "target": "catch { throw refused(); }", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M27", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": " await Promise.all([...children].map(child => new Promise(resolve => {\n child.once('close', resolve);\n child.kill('SIGKILL');\n })));", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M28", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "case \"${without_proof[i]}\" in --send | --tester-port | --tester-socket) break", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M28b", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "case \"${kept[i]}\" in --send | --tester-port | --tester-socket) break", + "count": 1 + } + ], + "accepted": true + }, + { + "id": "M29", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": " node -e 'const fs=require(\"fs\"),p=require(\"path\"),root=process.argv[1],parent=p.join(root,\"console\");if(!p.isAbsolute(root)||Buffer.byteLength(p.join(parent,\"tester.sock\"))>107)process.exit(1);try{const s=fs.lstatSync(parent);if(!s.isDirectory()||s.isSymbolicLink()||s.uid!==process.getuid()||(s.mode&0o077))process.exit(1);}catch(e){if(e.code!==\"ENOENT\")process.exit(1);}' -- \"$DATA\" || check_refuse tester-socket-path \"private tester path is too long or its parent is unsafe\"\n", + "count": 1 + } + ], + "accepted": true + } +] diff --git a/agents/gander/work/queue-196/r2/run3/uniqueness-initial.json b/agents/gander/work/queue-196/r2/run3/uniqueness-initial.json new file mode 100644 index 000000000..eb1bc7368 --- /dev/null +++ b/agents/gander/work/queue-196/r2/run3/uniqueness-initial.json @@ -0,0 +1,372 @@ +[ + { + "id": "M1", + "file": "packages/webui/src/serve.mjs", + "targets": [ + { + "method": "replace", + "target": "if (tester) return bindTesterSocket(server, testerSocket);", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M2", + "file": "packages/webui/src/tester-socket.mjs", + "targets": [ + { + "method": "replace", + "target": "chmodSync(temporary, 0o600)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M3", + "file": "packages/webui/src/serve.mjs", + "targets": [ + { + "method": "replace", + "target": "if (tester && (Object.hasOwn(req.headers, 'authorization')", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M4", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "peer.pid !== proof.pid", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M5", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": "} catch { finish(true); }", + "count": 2 + } + ], + "nonUnique": [ + { + "method": "replace", + "target": "} catch { finish(true); }", + "count": 2 + } + ] + }, + { + "id": "M6", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "peer.uid !== process.getuid()", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M7", + "file": "packages/webui/src/tester-socket.mjs", + "targets": [ + { + "method": "replace", + "target": "(dir.mode & 0o077)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M8", + "file": "packages/bus/src/human.mjs", + "targets": [ + { + "method": "replace", + "target": "current.env !== null && MARKERS.some", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M9", + "file": "packages/bus/src/human.mjs", + "targets": [ + { + "method": "replace", + "target": "if (again.startTime !== proof.startTime || again.ppid !== p.ppid)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M10", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "if (parent.uid !== peer.uid || parent.argv[1] !== resolve(paths.console) || parent.argv[2] !== 'serve')", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M11", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "import { resolve }", + "count": 1 + }, + { + "method": "replace", + "target": "parent.argv[1] !== resolve(paths.console)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M12", + "file": "packages/bus/src/human.mjs", + "targets": [ + { + "method": "replace", + "target": "Object.keys(proof).some((k) => !['pid', 'startTime', 'nonce', 'business'].includes(k))", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M13", + "file": "packages/bus/src/server.mjs", + "targets": [ + { + "method": "replace", + "target": "broker.disconnect(boundCap)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M14", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "verifyHuman(checked, { cliPath: paths[kind], launches, readProcess,\n nonceKey: kind === 'user' ? 'MOSAIC_BUS_USER_NONCE' : 'MOSAIC_BUS_CLI_NONCE' })", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M15", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "RUN_CONSOLE=(\"${without_tester[@]}\" --tester-socket)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M16", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "RUN_CONSOLE=(\"${without_tester[@]}\") DROP_TESTER=\"--tester-socket", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M17", + "file": "packages/bus/src/broker.mjs", + "targets": [ + { + "method": "replace", + "target": "if (this.#business(record.business).vars?.['bus.tester'] !== true)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M18", + "file": "packages/bus/src/principal.mjs", + "targets": [ + { + "method": "replace", + "target": "return { kind: 'user', business, id: proof.user }", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M19", + "file": "packages/cli/src/tester-http.mjs", + "targets": [ + { + "method": "replace", + "target": "socketPath: testerSocketPath(dataRoot)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M20", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": "export function createPeerReader", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M21", + "file": "packages/bus/src/interpreter.mjs", + "targets": [ + { + "method": "replace", + "target": "s.uid !== 0 || (s.mode & 0o022)", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M22", + "file": "packages/bus/src/server.mjs", + "targets": [ + { + "method": "replace", + "target": " if (kind) {", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M23", + "file": "packages/bus/src/interpreter.mjs", + "targets": [ + { + "method": "replace", + "target": "export function recordInterpreterStart(directory, record) {", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M24", + "file": "packages/webui/src/tester-socket.mjs", + "targets": [ + { + "method": "replace", + "target": "const temporary = join(parent, '.' + randomBytes(4).toString('hex') + '.s');", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M25", + "file": "packages/bus/src/interpreter.mjs", + "targets": [ + { + "method": "replaceAll", + "target": " | constants.O_NONBLOCK", + "count": 2 + } + ], + "nonUnique": [ + { + "method": "replaceAll", + "target": " | constants.O_NONBLOCK", + "count": 2 + } + ] + }, + { + "id": "M26", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": "if (!s.isFile() || s.isSymbolicLink()) throw Error();", + "count": 1 + }, + { + "method": "replace", + "target": "catch { throw refused(); }", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M27", + "file": "packages/bus/src/peercred.mjs", + "targets": [ + { + "method": "replace", + "target": " await Promise.all([...children].map(child => new Promise(resolve => {\n child.once('close', resolve);\n child.kill('SIGKILL');\n })));", + "count": 1 + } + ], + "nonUnique": [] + }, + { + "id": "M28", + "file": "scripts/stack-release.sh", + "targets": [ + { + "method": "replace", + "target": "--send | --tester-port | --tester-socket) break", + "count": 2 + } + ], + "nonUnique": [ + { + "method": "replace", + "target": "--send | --tester-port | --tester-socket) break", + "count": 2 + } + ] + }, + { + "id": "M29", + "file": "scripts/stack-release.sh", + "targets": [], + "nonUnique": [] + } +]