import test from 'node:test'; import assert from 'node:assert/strict'; import {fork} from 'node:child_process'; import {once} from 'node:events'; import fs from 'node:fs'; import {createRequire} from 'node:module'; import {fileURLToPath} from 'node:url'; import https from 'node:https'; import {X509Certificate,generateKeyPairSync} from 'node:crypto'; import {defaultPolicy,signPolicy} from '../packages/core/policy.mjs'; const require=createRequire(import.meta.url),{createManagedProxy}=require('../apps/network-broker/managed-proxy.cjs'),{createPolicyAuthority}=require('../apps/network-broker/policy-authority.cjs'),{createHttpsForwarder}=require('../apps/network-broker/https-forwarder.cjs'); const {createSyntheticAudit}=require('./fixtures/broker-durable-audit.cjs'); const cert=fs.readFileSync(new URL('./fixtures/icap/cert.pem',import.meta.url)),key=fs.readFileSync(new URL('./fixtures/icap/key.pem',import.meta.url)); function bounded(promise,ms=15000){let timer;return Promise.race([promise,new Promise((_,reject)=>timer=setTimeout(()=>reject(Error('interop-deadline')),ms))]).finally(()=>clearTimeout(timer));} test('stock Electron authenticates Basic proxy and sends only signed-policy approved browser content',{ skip:process.platform!=='win32'||process.env.SAFETYNET_NATIVE_BROKER_ELECTRON!=='1',timeout:60000 },async t=>{ assert.equal(process.execPath.toLowerCase(),fileURLToPath(new URL('../.runtime/windows/broker/node.exe',import.meta.url)).toLowerCase()); const received=[];let leakedProxyHeader=false,connections=0; const site=https.createServer({key,cert},async(req,res)=>{ leakedProxyHeader ||= !!req.headers['proxy-authorization']; const chunks=[];for await(const chunk of req)chunks.push(chunk); if(req.method==='POST'){received.push(Buffer.concat(chunks).toString());res.writeHead(201,{'content-type':'text/plain'});res.end('accepted');} else {res.setHeader('content-type','text/html');res.end('

Local interop fixture

');} });site.on('connection',()=>connections++);site.listen(0,'127.0.0.1');await once(site,'listening'); const port=site.address().port,destination=`localhost:${port}`,origin=`https://${destination}`; t.after(async()=>{site.closeAllConnections();await new Promise(resolve=>site.close(resolve));}); const keys=generateKeyPairSync('ed25519'),publicKey=keys.publicKey.export({type:'spki',format:'pem'}),privateKey=keys.privateKey.export({type:'pkcs8',format:'pem'}); const p=defaultPolicy();p.assignment={deviceId:'interop-device',groups:[]};p.dlp.rules=[{id:'company-secret',label:'Sensitive',kind:'literal',value:'PROJECT SECRET',action:'block'}]; const authority=await createPolicyAuthority({publicKey,deviceId:'interop-device'}),snapshot=authority.accept(signPolicy(p,privateKey)); const forward=createHttpsForwarder({authority:snapshot.token,current:authority.current,resolveRoute:()=>({approved:true,address:'127.0.0.1',port}),ca:cert,maxResponseBytes:16777216}); const audit=createSyntheticAudit(t); const proxy=await createManagedProxy({policyAuthority:authority,persistAudit:audit.persist,resolveCertificate:binding=>binding.authority===destination?{authority:destination,key,cert}:undefined,forward}); const proxyPort=await proxy.listen();t.after(()=>proxy.close()); const artifactRoot=fileURLToPath(new URL('../artifacts/broker-electron-interop/',import.meta.url));fs.mkdirSync(artifactRoot,{recursive:true}); const userData=fs.mkdtempSync(artifactRoot+'profile-'); const cleanEnvironment=Object.fromEntries(['SystemRoot','WINDIR','TEMP','TMP','USERPROFILE','HOMEDRIVE','HOMEPATH','LOCALAPPDATA','APPDATA','PATH'].filter(name=>process.env[name]!==undefined).map(name=>[name,process.env[name]])); const child=fork(fileURLToPath(new URL('./fixtures/broker-electron-interop.cjs',import.meta.url)),[],{execPath:require('electron'),execArgv:[],env:cleanEnvironment,windowsHide:true,stdio:['ignore','ignore','ignore','ipc']}); t.after(()=>{if(child.exitCode===null)child.kill();}); const ready=bounded(once(child,'message'));child.send({type:'initialize',config:{proxyPort,origin,userData,credentials:proxy.credentials,fingerprint:new X509Certificate(cert).fingerprint256}}); const [initialized]=await ready;assert.equal(initialized.type,'ready');assert.ok(initialized.loginCount>=1);assert.match(initialized.electron,/^44\./); const operation=async type=>{const result=bounded(once(child,'message'));child.send({type});const [message]=await result;assert.equal(message.type,'result');return message.result;}; assert.deepEqual(await operation('public'),{status:201,body:'accepted'});assert.deepEqual(received,['public browser content']); const beforeSecret=connections; const blocked=await operation('secret');assert.ok(blocked.blocked||blocked.status===403);assert.deepEqual(received,['public browser content']);assert.equal(connections,beforeSecret); const next=structuredClone(p);next.version=2;authority.accept(signPolicy(next,privateKey)); const revoked=await operation('revoked');assert.ok(revoked.blocked||revoked.status===403);assert.deepEqual(received,['public browser content']);assert.equal(leakedProxyHeader,false);assert.equal(connections,beforeSecret); const exit=bounded(once(child,'exit'));child.send({type:'stop'});await exit; fs.mkdirSync('artifacts/broker-electron-interop',{recursive:true}); fs.writeFileSync(`artifacts/broker-electron-interop/proof-${new Date().toISOString().replace(/:/g,'-')}.json`,JSON.stringify({schemaVersion:1,electron:initialized.electron,chromium:initialized.chromium,brokerNode:process.version,proxyAuthentication:'Basic',loginEvents:initialized.loginCount,authorizedPosts:received.length,authorizedBytes:Buffer.byteLength(received[0]),secretPosts:0,revokedPosts:0,upstreamConnectionsBeforeSecret:beforeSecret,upstreamConnectionsAfterBlocks:connections,proxyCredentialsLeaked:leakedProxyHeader,isolatedFixture:true},null,2)); });