import test from 'node:test'; import assert from 'node:assert/strict'; import { verifyPrincipal } from '../src/principal.mjs'; const nonce='a'.repeat(64), uid=process.getuid(); const paths={human:'/tree/human-cli.mjs',tester:'/tree/tester-cli.mjs',user:'/tree/user-cli.mjs',console:'/tree/console.mjs'}; function context(kind='human') { const variable=kind==='human'?'MOSAIC_BUS_CLI_NONCE':kind==='tester'?'MOSAIC_BUS_TESTER_NONCE':'MOSAIC_BUS_USER_NONCE'; const table=new Map([[50,{pid:50,ppid:40,startTime:'100',uid,argv:['node',paths[kind]],env:{[variable]:nonce}}], [40,{pid:40,ppid:1,startTime:'90',uid,argv:['node',paths.console,'serve'],env:{}}]]); return {peer:{pid:50,uid,gid:uid},paths,businesses:{demo:{human:'owner'}},launches:[], readProcess:pid=>{if(!table.has(pid))throw Error('gone');return {...table.get(pid)};}, readTesterNonce:()=>table.get(50).env.MOSAIC_BUS_TESTER_NONCE,table}; } const proof={pid:50,startTime:'100',nonce,business:'demo'}; test('principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body',()=>{ const c=context(); assert.deepEqual(verifyPrincipal({human:proof},c),{kind:'owner',business:'demo',id:'owner'}); for(const peer of [{pid:51,uid,gid:uid},{pid:50,uid:uid+1,gid:uid}])assert.throws(()=>verifyPrincipal({human:proof},{...c,peer}),/human-required/); assert.throws(()=>verifyPrincipal({human:{...proof,human:'other'}},c),/human-required/); assert.throws(()=>verifyPrincipal({human:proof,tester:proof},c),/invalid-envelope/); }); test('user requires its own nonce and full walk plus immediate same-tree Console parent',()=>{ const request={user:{...proof,user:'owner'}}; assert.deepEqual(verifyPrincipal(request,context('user')),{kind:'user',business:'demo',id:'owner'}); for(const change of [{argv:['bash']},{argv:['node','/other/console.mjs','serve']},{argv:['node',paths.console,'other']},{env:{CODEX_THREAD_ID:'agent'}}]) { const c=context('user');Object.assign(c.table.get(40),change); assert.throws(()=>verifyPrincipal(request,c),/human-required/); } const wrong=context('user');wrong.table.get(50).env={MOSAIC_BUS_CLI_NONCE:nonce}; assert.throws(()=>verifyPrincipal(request,wrong),/human-required/); const launched=context('user');launched.launches=[{pid:40,startTime:'90'}]; assert.throws(()=>verifyPrincipal(request,launched),/human-required/); const changed=context('user'), read=changed.readProcess;let n=0; changed.readProcess=pid=>{const p=read(pid);if(pid===40&&n++>0)p.startTime='91';return p;}; assert.throws(()=>verifyPrincipal(request,changed),/human-required/); }); test('tester deliberately admits agent parents but rejects managed runs and another nonce',()=>{ const c=context('tester');c.table.get(40).env={CODEX_THREAD_ID:'seat'}; assert.deepEqual(verifyPrincipal({tester:proof},c),{kind:'tester',business:'demo',id:'tester'}); c.table.get(40).env={MOSAIC_RUN_ID:'managed'}; assert.throws(()=>verifyPrincipal({tester:proof},c),/tester-required/); }); test('human leaf identity is reread after walking the parents',()=>{ const c=context(),read=c.readProcess;let n=0; c.readProcess=pid=>{const p=read(pid);if(pid===50&&n++>0)p.startTime='101';return p;}; assert.throws(()=>verifyPrincipal({human:proof},c),/human-required/); }); // Row 252 (#1753, design section 2, J3-J5): the Console owner. The same-tree console-cli wrapper with // its own nonce, a direct same-tree `serve` parent, the subject from the wrapper's spawn environment, // and that subject one of the configured owners (N1). const owner={provider:'fake-oidc',iss:'https://idp.test',sub:'owner-1'}; function consoleContext() { const c=context('user');c.paths={...paths,consoleCli:'/tree/console-cli.mjs'};c.consoleOwners=[{provider:'other',iss:'https://idp.test',sub:'owner-2'},owner]; Object.assign(c.table.get(50),{argv:['node','/tree/console-cli.mjs'],env:{MOSAIC_BUS_CONSOLE_NONCE:nonce,MOSAIC_BUS_CONSOLE_BUSINESS:'demo',MOSAIC_BUS_CONSOLE_PROVIDER:owner.provider,MOSAIC_BUS_CONSOLE_ISS:owner.iss,MOSAIC_BUS_CONSOLE_SUB:owner.sub}}); return c; } const consoleProof={...proof,...owner}; test('console requires its own wrapper, nonce and serve parent, and a configured owner from the spawn env',()=>{ assert.deepEqual(verifyPrincipal({console:consoleProof},consoleContext()),{kind:'console',business:'demo',id:'owner',via:'console',subject:owner}); // J3: the subject is the spawn's, never the body's alone, and must be a configured owner. for(const change of [{sub:'owner-2'},{iss:'https://evil.test'},{provider:'other'}]) assert.throws(()=>verifyPrincipal({console:{...consoleProof,...change}},consoleContext()),/human-required/); const unlisted=consoleContext();unlisted.consoleOwners=[]; assert.throws(()=>verifyPrincipal({console:consoleProof},unlisted),/human-required/); for(const extra of [{name:'Jason'},{human:'owner'},{user:'owner'},{sub:''},{sub:'x'.repeat(513)}]) assert.throws(()=>verifyPrincipal({console:{...consoleProof,...extra}},consoleContext()),/human-required/); // J4: the parent is the same-tree serve, direct, and not an agent. for(const change of [{argv:['bash']},{argv:['node','/other/console.mjs','serve']},{argv:['node',paths.console,'other']},{env:{CODEX_THREAD_ID:'agent'}},{uid:uid+1}]) { const c=consoleContext();Object.assign(c.table.get(40),change); assert.throws(()=>verifyPrincipal({console:consoleProof},c),/human-required/); } const launched=consoleContext();launched.launches=[{pid:40,startTime:'90'}]; assert.throws(()=>verifyPrincipal({console:consoleProof},launched),/human-required/); // J5: another nonce namespace, or the wrapper path of another kind, refuses. for(const env of [{MOSAIC_BUS_CLI_NONCE:nonce},{MOSAIC_BUS_USER_NONCE:nonce}]) { const c=consoleContext();const t=c.table.get(50);t.env={...t.env,...env};delete t.env.MOSAIC_BUS_CONSOLE_NONCE; assert.throws(()=>verifyPrincipal({console:consoleProof},c),/human-required/); } for(const argv of [['node',paths.human],['node',paths.user],['node','/other/console-cli.mjs']]) { const c=consoleContext();c.table.get(50).argv=argv; assert.throws(()=>verifyPrincipal({console:consoleProof},c),/human-required/); } // The Console's own nonce does not answer as the cli human or the user. assert.throws(()=>verifyPrincipal({human:proof},consoleContext()),/human-required/); assert.throws(()=>verifyPrincipal({user:{...proof,user:'owner'}},consoleContext()),/human-required/); // Each piece is read again after the walk: a parent or a spawn subject that changed refuses. for(const [pid,mutate] of [[40,p=>{p.startTime='91';}],[50,p=>{p.env={...p.env,MOSAIC_BUS_CONSOLE_SUB:'owner-2'};}],[50,p=>{p.ppid=41;}]]) { const c=consoleContext(),read=c.readProcess;let n=0; // The last read of each pid is verifyConsole's own reread: 40 is read 3 times, 50 is read 4 times. c.readProcess=x=>{const p=read(x);if(x===pid&&n++===(pid===40?2:3))mutate(p);return p;}; assert.throws(()=>verifyPrincipal({console:consoleProof},c),/human-required/); } for(const peer of [{pid:51,uid,gid:uid},{pid:50,uid:uid+1,gid:uid}])assert.throws(()=>verifyPrincipal({console:consoleProof},{...consoleContext(),peer}),/human-required/); assert.throws(()=>verifyPrincipal({console:consoleProof,human:proof},consoleContext()),/invalid-envelope/); });