'use strict'; const {app,BrowserWindow}=require('electron'); const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'); app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs'); const wait=ms=>new Promise(r=>setTimeout(r,ms)),headers={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`,'content-type':'application/json'}; const startupAt=Date.now(); function safeSnapshot(stage){return {stage,elapsedMs:Date.now()-startupAt,phase:process.env.SAFETYNET_SMOKE_PHASE,windows:BrowserWindow.getAllWindows().map(w=>({destroyed:w.isDestroyed(),loading:w.webContents.isLoading(),urlKind:w.webContents.getURL().endsWith('index.html')?'shell':w.webContents.getURL()===''?'empty':'other',children:w.contentView.children.length})),metrics:app.getAppMetrics().map(m=>({type:m.type,cpu:m.cpu.percentCPUUsage,idleWakeupsPerSecond:m.cpu.idleWakeupsPerSecond,memoryWorkingSetSize:m.memory.workingSetSize}))};} async function until(fn,stage='native-wait'){const started=Date.now();for(let n=0;n<200;n++){const result=await fn();if(result)return result;await wait(100);}fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-timeout-'+process.env.SAFETYNET_SMOKE_PHASE+'.json'),JSON.stringify({...safeSnapshot(stage),waitElapsedMs:Date.now()-started},null,2));throw Error('Individual native fixture timed out: '+stage);} const overview=async()=> (await fetch(process.env.SAFETYNET_SMOKE_SERVER+'/api/admin/overview',{headers})).json(); app.whenReady().then(async()=>{let page;try{ const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;},'initial-shell-load'); fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-startup-'+process.env.SAFETYNET_SMOKE_PHASE+'.json'),JSON.stringify(safeSnapshot('shell-ready'),null,2)); if(process.env.SAFETYNET_SMOKE_PHASE==='restart'){ await until(async()=> (await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready); page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents; assert.equal((await win.webContents.executeJavaScript('window.safetynet.agentConnection("reader-a")')).ok,false,'Restart accepted restored generation below durable floor'); const state=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;assert.ok(Array.isArray(state.agentGrants));assert.ok(!state.agentGrants.some(g=>g.id==='reader-a'),'Restored old generation visible after restart');assert.ok(state.agentGrants.some(g=>g.id==='state-b'),'Unaffected actor missing after restart'); const oldFile=path.join(app.getPath('userData'),'old-generation-fixture.json'),requestFile=path.join(app.getPath('userData'),'restart-request.json');fs.writeFileSync(requestFile,JSON.stringify({action:'sessions',args:{}})); const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',oldFile,'--request',requestFile],{windowsHide:true,stdio:['ignore','ignore','ignore']});assert.equal(await new Promise(r=>child.once('exit',r)),1,'Old descriptor reached restarted listener'); const proofFile=path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-native-proof.json'),proof=JSON.parse(fs.readFileSync(proofFile,'utf8'));proof.durableGenerationFloorAfterRestart=true;proof.oldDescriptorRejectedAfterRestart=true;fs.writeFileSync(proofFile,JSON.stringify(proof,null,2)); require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);console.log('Individual generation floor restart proof passed');app.exit(0);return; } page=win.contentView.children.find(v=>v.webContents!==win.webContents)?.webContents; const enroll=await win.webContents.executeJavaScript(`window.safetynet.enroll(${JSON.stringify({server:process.env.SAFETYNET_SMOKE_SERVER,code:process.env.SAFETYNET_SMOKE_CODE})})`);assert.equal(enroll.ok,true); page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()); assert.equal((await win.webContents.executeJavaScript('window.safetynet.agentConnection()')).ok,false); await win.webContents.executeJavaScript('document.getElementById("agents").click()');await until(()=>win.webContents.executeJavaScript('document.getElementById("agent-pairing")?.open')); assert.deepEqual(await win.webContents.executeJavaScript('Array.from(document.querySelectorAll(".agent-grant-choice strong"),e=>e.textContent)'),['Reader A','State B']); fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-pairing-ui.png'),(await win.webContents.capturePage()).toPNG()); for(const index of [0,1]){await win.webContents.executeJavaScript(`document.querySelectorAll('.agent-grant-choice')[${index}].click()`);await until(()=>win.webContents.executeJavaScript(`document.getElementById('agent-pairing-result').textContent.startsWith(${JSON.stringify('Identity: '+(index===0?'Reader A':'State B'))})`));} await win.webContents.executeJavaScript('document.getElementById("agent-pairing").close()'); const pair=async id=>{const r=await win.webContents.executeJavaScript(`window.safetynet.agentConnection(${JSON.stringify(id)})`);assert.equal(r.ok,true);return r.data;}; const a=await pair('reader-a'),b=await pair('state-b');assert.notEqual(a.credentialPath,b.credentialPath);assert.equal(fs.existsSync(path.join(app.getPath('userData'),'agent-connection.json')),false); fs.copyFileSync(a.credentialPath,path.join(app.getPath('userData'),'old-generation-fixture.json')); for(const c of [a,b]){const descriptor=JSON.parse(fs.readFileSync(c.credentialPath,'utf8'));assert.equal(descriptor.format,'windows-dpapi-v1');assert.equal(Object.hasOwn(descriptor,'token'),false);} const requestFile=path.join(app.getPath('userData'),'individual-fixture-request.json'); async function call(connection,action,args={}){fs.writeFileSync(requestFile,JSON.stringify({action,args}));const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.credentialPath,'--request',requestFile],{windowsHide:true,stdio:['ignore','pipe','pipe']});let output='';child.stdout.on('data',v=>output+=v);child.stderr.on('data',()=>{});const exit=await new Promise(r=>{child.once('exit',r);child.once('error',()=>r(1));});return {exit,result:exit===0?JSON.parse(output).result:undefined};} const sessions=await call(a,'sessions');assert.equal(sessions.exit,0);const sessionId=sessions.result[0].id; assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_SINK+'/protected-title'})).exit,0); await until(()=>page.getTitle()==='Synthetic confidential inventory title','protected-title-load'); const protectedInventory=await call(a,'sessions');assert.equal(protectedInventory.exit,0);assert.equal(protectedInventory.result.find(row=>row.id===sessionId)?.title,'Protected page','Page-controlled confidential title escaped inventory classification'); assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_SINK+'/allowed'})).exit,0); assert.equal((await call(a,'read',{sessionId})).exit,0);assert.equal((await call(a,'application:state')).exit,1);assert.equal((await call(b,'application:state')).exit,0);assert.equal((await call(b,'read',{sessionId})).exit,1); assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_SINK+'/allowed'})).exit,0,'Approved host navigation failed'); assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_FORBIDDEN})).exit,1,'Actor reached forbidden host'); assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_SINK+'/redirect'})).exit,1,'Actor followed forbidden redirect'); assert.equal((await (await fetch(process.env.SAFETYNET_SMOKE_SINK+'/count')).json()).forbiddenRequests,0,'Forbidden receiver received actual network request'); const humanNavigation=await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(process.env.SAFETYNET_SMOKE_SINK+'/human')})`);assert.equal(humanNavigation.ok,true,'Human navigation failed');await until(()=>!page.isLoading()); assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(process.env.SAFETYNET_SMOKE_SINK+'/count')}).then(r=>r.ok)`),true,'Owned tab request failed after human navigation'); const managedSession=require('electron').session.fromPartition('persist:safetynet-managed');let ownerlessRejected=false;try{await managedSession.fetch(process.env.SAFETYNET_SMOKE_SINK+'/ownerless');}catch{ownerlessRejected=true;}assert.equal(ownerlessRejected,true,'Ownerless session request escaped individual-mode ownership guard'); assert.equal((await (await fetch(process.env.SAFETYNET_SMOKE_SINK+'/count')).json()).ownerlessRequests,0,'Ownerless request reached receiver'); const change=async modify=>{const policy=(await overview()).policy;modify(policy);policy.version++;const r=await fetch(process.env.SAFETYNET_SMOKE_SERVER+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(policy)});assert.equal(r.status,200);await until(async()=>{const s=await win.webContents.executeJavaScript('window.safetynet.state()');return s.data.ready&&s.data.policyVersion===policy.version;});}; await change(p=>{p.agents.grants.find(g=>g.id==='reader-a').enabled=false;});assert.equal((await call(a,'sessions')).exit,1);assert.equal((await call(b,'application:state')).exit,0); await change(p=>{const g=p.agents.grants.find(g=>g.id==='reader-a');g.enabled=true;g.generation=2;});const generationTwo=await pair('reader-a');assert.equal(generationTwo.generation,2);assert.equal((await call(generationTwo,'sessions')).exit,0); await change(p=>{p.agents.grants.find(g=>g.id==='reader-a').generation=1;});assert.equal((await win.webContents.executeJavaScript('window.safetynet.agentConnection("reader-a")')).ok,false,'Live rollback bypassed generation floor'); const events=await until(async()=>{const e=(await overview()).events.filter(e=>e.metadata.agent?.principalId);return e.some(e=>e.metadata.agent.principalId==='reader-a'&&e.kind==='agent.action')&&e.some(e=>e.metadata.agent.principalId==='state-b'&&e.kind==='agent.action')&&e;}); await wait(1500);let traceProof; for(const event of events.filter(e=>e.kind==='agent.action'&&e.metadata.traceId)){const r=await fetch('http://127.0.0.1:4318/api/trace/'+event.metadata.traceId);if(!r.ok)continue;const trace=await r.json();const spans=trace.spans||trace; if(Array.isArray(spans)&&spans.some(s=>s.name==='browser.agent'&&s.operation==='agent.request')){traceProof={traceId:event.metadata.traceId,principalId:event.metadata.agent.principalId,stages:spans.map(s=>({name:s.name,operation:s.operation,parentSpanId:s.parentSpanId,spanId:s.spanId,durationMs:s.durationMs}))};break;}} assert.ok(traceProof,'Actual agent request trace missing'); const root=traceProof.stages.find(s=>s.operation==='agent.request');assert.equal(root.parentSpanId,null); for(const operation of ['agent.decrypt','agent.authorize','agent.dispatch','agent.project','agent.audit','agent.encrypt'])assert.ok(traceProof.stages.some(s=>s.operation===operation&&s.parentSpanId===root.spanId&&Number.isFinite(s.durationMs)&&s.durationMs>=0),'Missing correlated stage '+operation); fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-native-proof.json'),JSON.stringify({actualElectron:true,classifiedInventoryRedactsTitlePreservesId:true,explicitGrantSelection:true,renderedGrantChoices:true,uiPairingBothActors:true,separateDPAPIDescriptors:true,noLegacyDescriptor:true,twoActors:true,scopeIntersection:true,approvedHostNavigation:true,forbiddenHostDeniedBeforeTransmission:true,forbiddenRedirectDeniedBeforeTransmission:true,forbiddenReceiverRequests:0,humanNavigationPreserved:true,ownedTabFetchAfterHumanNavigation:true,ownerlessSessionFetchBlockedBeforeTransmission:true,ownerlessReceiverRequests:0,targetedRevocation:true,unaffectedPeer:true,liveGenerationRollbackDenied:true,attributedAudit:true,traceProof},null,2));console.log('Individual agent native proof passed'); require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0); }catch(error){console.error(error.stack);if(page)require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});