31 lines
4.5 KiB
JavaScript
31 lines
4.5 KiB
JavaScript
import {createServer} from 'node:http';
|
|
import {createRequire} from 'node:module';
|
|
import {mkdtempSync,mkdirSync,rmSync} from 'node:fs';
|
|
import {tmpdir} from 'node:os';
|
|
import {join,resolve} from 'node:path';
|
|
import {spawn} from 'node:child_process';
|
|
import {randomBytes} from 'node:crypto';
|
|
import {createApp} from '../apps/server/app.mjs';
|
|
import {openStore,digest} from '../apps/server/store.mjs';
|
|
import {startTelemetry} from '../apps/server/telemetry.mjs';
|
|
const collector='http://127.0.0.1:4318';
|
|
if(!(await fetch(collector+'/health')).ok)throw Error('Protected collector unavailable');
|
|
process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry();
|
|
const temporary=mkdtempSync(join(tmpdir(),'safetynet-extended-'));mkdirSync('artifacts',{recursive:true});
|
|
if(!resolve(temporary).startsWith(resolve(tmpdir())+requireSeparator())||!temporary.split(/[\\/]/).at(-1).startsWith('safetynet-extended-'))throw Error('Unsafe temporary fixture path');
|
|
function requireSeparator(){return process.platform==='win32'?'\\':'/';}
|
|
const store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');
|
|
let forbiddenRequests=0,ownerlessRequests=0;const forbidden=createServer((_req,res)=>{forbiddenRequests++;res.end('Forbidden receiver');}).listen(0,'127.0.0.2');await new Promise(r=>forbidden.once('listening',r));
|
|
const forbiddenUrl=`http://127.0.0.2:${forbidden.address().port}/forbidden`;
|
|
const sink=createServer((req,res)=>{if(req.url==='/count'){res.setHeader('content-type','application/json');res.end(JSON.stringify({forbiddenRequests,ownerlessRequests}));return;}if(req.url==='/ownerless')ownerlessRequests++;if(req.url==='/redirect'){res.writeHead(302,{location:forbiddenUrl});res.end();return;}res.setHeader('content-type','text/html');res.end(req.url==='/protected-title'?'<!doctype html><title>Synthetic confidential inventory title</title><h1>Public actor fixture</h1>':'<!doctype html><title>Individual actor fixture</title><h1>Public actor fixture</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
|
const p=store.get('policy');p.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;
|
|
p.agents={...p.agents,enabled:true,identityMode:'individual',scopes:['sessions','navigate','history:list','history:go','window:state','window:control'],allowedHosts:['127.0.0.1','127.0.0.2'],grants:[{id:'reader-a',name:'Reader A',enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes:['sessions','navigate','history:list','history:go','window:state'],allowedHosts:['127.0.0.1'],generation:1},{id:'state-b',name:'State B',enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes:['history:list','history:go','window:state','window:control'],allowedHosts:['127.0.0.1'],generation:1}]};
|
|
p.dlp.rules.push({id:'inventory-confidential-fixture',label:'Synthetic inventory fixture',action:'block',kind:'literal',value:'Synthetic confidential inventory title'});
|
|
p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:collector,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'individual-fixture');
|
|
const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement:true};
|
|
const management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
|
const electron=createRequire(import.meta.url)('electron');
|
|
async function run(phase){const child=spawn(electron,['scripts/extended-agent-controls-smoke.cjs'],{windowsHide:true,stdio:'inherit',env:{...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ARTIFACTS:resolve('artifacts'),SAFETYNET_SMOKE_PHASE:phase,SAFETYNET_SMOKE_SINK:p.browser.homeUrl,SAFETYNET_SMOKE_FORBIDDEN:forbiddenUrl}});const deadline=setTimeout(()=>child.kill(),180000);const exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(deadline);return exit;}
|
|
let exit=await run('initial');
|
|
for(const server of [management,sink,forbidden])server.closeAllConnections();await Promise.all([new Promise(r=>management.close(r)),new Promise(r=>sink.close(r)),new Promise(r=>forbidden.close(r))]);store.db.close();await telemetry?.shutdown();rmSync(temporary,{recursive:true,force:true});process.exit(exit);
|