2026-10-13 21:39:47 +02:00
import { createServer } from 'node:https' ;
import { createRequire } from 'node:module' ;
import { mkdtempSync , mkdirSync , rmSync , readFileSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { join , resolve , sep } from 'node:path' ;
import { spawn } from 'node:child_process' ;
import { randomBytes } from 'node:crypto' ;
import net from 'node:net' ;
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-agent-visual-' )); mkdirSync ( 'artifacts' ,{ recursive : true });
if ( ! resolve ( temporary ). startsWith ( resolve ( tmpdir ()) + sep ) ||! temporary . split ( /[\\/]/ ). at ( - 1 ). startsWith ( 'safetynet-agent-visual-' )) throw Error ( 'Unsafe temporary fixture path' );
let blockPNG = false ; const avFacts = { scans : 0 , pngScans : 0 , pngBlocks : 0 }, avSockets = new Set (), pngSignature = Buffer . from ([ 137 , 80 , 78 , 71 , 13 , 10 , 26 , 10 ]); const av = net . createServer ( socket =>{ avSockets . add ( socket ); socket . on ( 'close' ,()=> avSockets . delete ( socket )); socket . on ( 'error' ,()=>{}); let input = Buffer . alloc ( 0 ), done = false ; socket . on ( 'data' , chunk =>{ input = Buffer . concat ([ input , chunk ]); if ( input . length > 4194304 ){ socket . destroy (); return ;} if ( done || input . indexOf ( '\r\n\r\n' ) < 0 ) return ; const options = input . subarray ( 0 , 7 ). toString () === 'OPTIONS' ; if ( ! options &&! input . subarray ( - 5 ). equals ( Buffer . from ( '0\r\n\r\n' ))) return ; done = true ; if ( options ){ input . fill ( 0 ); socket . end ( 'ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nAllow: 204\r\nISTag: "visual-fixture"\r\nEncapsulated: null-body=0\r\n\r\n' ); return ;} avFacts . scans ++ ; const png = input . indexOf ( pngSignature ) >= 0 ; if ( png ) avFacts . pngScans ++ ; const blocked = png && blockPNG ; if ( blocked ) avFacts . pngBlocks ++ ; input . fill ( 0 ); socket . end ( blocked ? 'ICAP/1.0 204 No Content\r\nISTag: "visual-fixture"\r\nX-Infection-Found: Type=0; Threat=synthetic-visual;\r\n\r\n' : 'ICAP/1.0 204 No Content\r\nISTag: "visual-fixture"\r\n\r\n' );});}). listen ( 0 , '127.0.0.1' ); await new Promise ( r => av . once ( 'listening' , r ));
2026-10-13 23:19:22 +02:00
const sink = createServer ({ cert : readFileSync ( 'tests/fixtures/icap/cert.pem' ), key : readFileSync ( 'tests/fixtures/icap/key.pem' )},( req , res )=>{ if ( req . url === '/av-stats' ){ res . setHeader ( 'content-type' , 'application/json' ); res . end ( JSON . stringify ( avFacts )); return ;} if ( req . url === '/av-block' ) blockPNG = true ; else if ([ '/public' , '/canvas' , '/sensitive' , '/closed-shadow' , '/open-shadow' , '/frames' ]. includes ( req . url )) blockPNG = false ; res . setHeader ( 'content-type' , 'text/html' ); const base = '<!doctype html><meta name="viewport" content="width=device-width"><style>body{margin:0;background:white;color:#123;font:28px Arial}h1{margin:70px}canvas{display:block}</style><title>Public visual fixture</title>' ; if ( req . url === '/canvas' ) res . end ( base + '<canvas width="1200" height="360"></canvas><script>const c=document.querySelector("canvas").getContext("2d");c.fillStyle="white";c.fillRect(0,0,1200,360);c.fillStyle="black";c.font="bold 48px Arial";c.fillText("CLASSIFIED ORCHID DOCUMENT",40,170);</script>' ); else if ( req . url === '/sensitive' ) res . end ( base + '<h1>Public form</h1><input type="password" value="synthetic-hidden-field" style="position:absolute;left:-9999px"><script>document.querySelectorAll=()=>[]</script>' ); else if ( req . url === '/closed-shadow' ) res . end ( base + `<div id=host></div><script>document.getElementById("host").attachShadow({mode:"closed"}).innerHTML='<input type="password" value="synthetic-closed-shadow-password">';document.querySelectorAll=()=>[]</script>` ); else if ( req . url === '/open-shadow' ) res . end ( base + `<div id=host></div><script>document.getElementById("host").attachShadow({mode:"open"}).innerHTML='<p style="margin:70px;font:28px Arial">Public shadow content</p>'</script>` ); else if ( req . url === '/frames' ) res . end ( base + '<h1>Public parent</h1><iframe src="/public"></iframe>' ); else res . end ( base + '<h1>Public approved visual page</h1><p style="margin:70px">Research and documentation workspace.</p>' );}). listen ( 0 , '127.0.0.1' ); await new Promise ( r => sink . once ( 'listening' , r ));
2026-10-13 21:39:47 +02:00
const origin = `https://127.0.0.1: ${ sink . address (). port } ` , store = openStore ( join ( temporary , 'db' )), code = randomBytes ( 32 ). toString ( 'base64url' ), adminToken = randomBytes ( 32 ). toString ( 'base64url' );
const p = store . get ( 'policy' ); p . browser . homeUrl = origin + '/public' ; p . inspection . documentAnalysisEnabled = false ; p . dlp . rules . push ({ id : 'visual-confidential-fixture' , label : 'Synthetic visual confidential rule' , kind : 'literal' , value : 'CLASSIFIED ORCHID DOCUMENT' , action : 'block' });
p . enterprise . icap = {... p . enterprise . icap , enabled : true , url : `icap://127.0.0.1: ${ av . address (). port } /scan` , scanUploads : true , scanDownloads : false , scanClipboard : false , scanAI : false };
p . agents = {... p . agents , enabled : true , identityMode : 'individual' , scopes : [ 'sessions' ], allowedHosts : [ '127.0.0.1' ], grants : [{ id : 'visual-a' , name : 'Visual A' , enabled : true , expiresAt : new Date ( Date . now () + 3600000 ). toISOString (), scopes : [ 'sessions' ], allowedHosts : [ '127.0.0.1' ], generation : 1 },{ id : 'visual-b' , name : 'Visual B' , enabled : true , expiresAt : new Date ( Date . now () + 3600000 ). toISOString (), scopes : [ 'sessions' ], allowedHosts : [ '127.0.0.1' ], generation : 1 }]};
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 , 'vault-fixture' );
const config = { secure : false , publicUrl : 'http://127.0.0.1' , adminToken , requirePQManagement : true , requireIndependentPolicyApproval : 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 reviewer = randomBytes ( 32 ). toString ( 'base64url' ); store . db . prepare ( 'INSERT INTO sessions VALUES(?,?,?)' ). run ( digest ( reviewer ), Date . now () + 600000 , JSON . stringify ({ sub : 'independent-vault-reviewer' , role : 'admin' }));
const child = spawn ( createRequire ( import . meta . url )( 'electron' ),[ 'scripts/agent-visual-export-smoke.cjs' ],{ windowsHide : true , stdio : [ 'ignore' , 'pipe' , 'pipe' ], env : {... process . env , SAFETYNET_SMOKE_SERVER : config . publicUrl , SAFETYNET_SMOKE_CODE : code , SAFETYNET_SMOKE_ADMIN : adminToken , SAFETYNET_SMOKE_REVIEWER : reviewer , SAFETYNET_SMOKE_USERDATA : join ( temporary , 'browser' ), SAFETYNET_SMOKE_ARTIFACTS : resolve ( 'artifacts' ), SAFETYNET_SMOKE_ORIGIN : origin }}); child . stdout . on ( 'data' , v => process . stdout . write ( v )); child . stderr . on ( 'data' , v => process . stderr . write ( v )); const deadline = setTimeout (()=> child . kill (), 240000 ); const exit = await new Promise ( r =>{ child . once ( 'exit' , v => r ( v ?? 1 )); child . once ( 'error' ,()=> r ( 1 ));}); clearTimeout ( deadline );
for ( const socket of avSockets ) socket . destroy (); await new Promise ( r => av . close ( r )); for ( const server of [ management , sink ]) server . closeAllConnections (); await Promise . all ([ new Promise ( r => management . close ( r )), new Promise ( r => sink . close ( r ))]); store . db . close (); await telemetry ? . shutdown (); rmSync ( temporary ,{ recursive : true , force : true }); if ( exit === 0 && ( avFacts . pngScans < 3 || avFacts . pngBlocks !== 1 )) throw Error ( 'Actual visual AV exchanges incomplete' ); process . exit ( exit );