2026-10-13 18:04:02 +02:00
import { createServer } from 'node:http' ;
import { createRequire } from 'node:module' ;
import { mkdtempSync , rmSync , mkdirSync , writeFileSync , readFileSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { join , resolve } from 'node:path' ;
import { spawn , execFileSync } from 'node:child_process' ;
import { randomBytes } from 'node:crypto' ;
import { createApp } from '../apps/server/app.mjs' ;
import { startTelemetry } from '../apps/server/telemetry.mjs' ;
import { openStore , digest } from '../apps/server/store.mjs' ;
const require = createRequire ( import . meta . url ), electron = require ( 'electron' );
if ( process . env . SAFETYNET_TEST_OTLP_ENDPOINT ) process . env . OTEL_EXPORTER_OTLP_ENDPOINT = process . env . SAFETYNET_TEST_OTLP_ENDPOINT ;
const smokeTelemetry = startTelemetry ();
const temporary = mkdtempSync ( join ( tmpdir (), 'safetynet-smoke-' )); mkdirSync ( 'artifacts' ,{ recursive : true });
const evidenceKeyFile = join ( temporary , 'evidence.key' ); writeFileSync ( evidenceKeyFile , randomBytes ( 32 ),{ mode : 0o600 });
if ( process . platform === 'win32' ) execFileSync ( join ( process . env . SystemRoot , 'System32/WindowsPowerShell/v1.0/powershell.exe' ),[ '-NoProfile' , '-NonInteractive' , '-Command' , "$ErrorActionPreference='Stop';$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User;$acl=New-Object System.Security.AccessControl.FileSecurity;$acl.SetOwner($sid);$acl.SetAccessRuleProtection($true,$false);$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($sid,'FullControl','Allow')));Set-Acl -LiteralPath $env:TEST_EVIDENCE_KEY -AclObject $acl" ],{ windowsHide : true , env : { SystemRoot : process . env . SystemRoot , TEST_EVIDENCE_KEY : evidenceKeyFile }, stdio : 'pipe' });
2026-10-13 18:24:44 +02:00
const sink = createServer (( req , res )=>{ if ( req . url === '/download-safe' ){ res . setHeader ( 'Content-Type' , 'application/octet-stream' ); res . setHeader ( 'Content-Disposition' , 'attachment; filename="safe.bin"' ); res . end ( Buffer . from ([ 0 , 1 , 2 , 3 , 255 ])); return ;} res . setHeader ( 'Content-Type' , 'text/html' ); res . end ( '<!doctype html><title>Interaction fixture</title><pre id="clipboard-source">Synthetic public selection</pre><input id="public-input" type="text"><input id="private-input" type="password" value="never-export-this"><button id="public-button" onclick="this.dataset.clicked=String(Number(this.dataset.clicked||0)+1)">Synthetic control label</button><form id="public-form" onsubmit="event.preventDefault();this.dataset.submitted=String(Number(this.dataset.submitted||0)+1)"><input type="text" value="Synthetic private form field"><button id="submit-button" type="submit">Send form</button></form><form id="private-form" onsubmit="event.preventDefault()"><input type="password" value="never-export-this"><button id="private-submit">Private submit</button></form><a id="safe-download" href="/download-safe">Download</a>' );}). listen ( 0 , '127.0.0.1' ); await new Promise ( r => sink . once ( 'listening' , r ));
2026-10-13 18:04:02 +02:00
const store = openStore ( join ( temporary , 'db' )), code = randomBytes ( 32 ). toString ( 'base64url' ), adminToken = randomBytes ( 32 ). toString ( 'base64url' );
const inspectorToken = randomBytes ( 32 ). toString ( 'base64url' );
const inspectorProcess = spawn ( resolve ( '.runtime/windows/python/python.exe' ),[ 'apps/inspector/server.py' ],{ windowsHide : true , stdio : [ 'ignore' , 'pipe' , 'pipe' ], env : {... process . env , INSPECTOR_TOKEN : inspectorToken , INSPECTOR_PORT : '0' , INSPECTOR_PARENT_PID : String ( process . pid )}});
const inspectorPort = await new Promise (( resolve , reject )=>{
let output = '' ; const deadline = setTimeout (()=>{ inspectorProcess . kill (); reject ( Error ( 'Fixture inspector did not start' ));}, 40000 );
inspectorProcess . once ( 'exit' ,()=>{ clearTimeout ( deadline ); reject ( Error ( 'Fixture inspector exited before startup' ));});
inspectorProcess . on ( 'error' , reject );
inspectorProcess . stdout . on ( 'data' , chunk =>{
output += chunk ;
if ( output . includes ( '\n' )){
clearTimeout ( deadline );
try { resolve ( JSON . parse ( output . split ( '\n' )[ 0 ]). port );} catch { reject ( Error ( 'Fixture inspector startup rejected' ));}
}
});
inspectorProcess . stderr . on ( 'data' ,()=>{});
});
const p = store . get ( 'policy' ); p . browser . homeUrl = `http://127.0.0.1: ${ sink . address (). port } ` ; p . brand = { name : 'Acme Secure Browser' , color : '#245fbc' }; p . dlp . rules = [{ id : 'secret-roadmap' , label : 'Roadmap' , kind : 'literal' , value : 'Secret Roadmap' , action : 'block' }]; if ( process . env . SAFETYNET_TEST_OTLP_ENDPOINT ) p . diagnostics = {... p . diagnostics , enabled : true , mode : 'debug' , endpoint : process . env . SAFETYNET_TEST_OTLP_ENDPOINT , sampleRate : 1 , debugSampleRate : 1 , flushIntervalMs : 250 }; store . put ( 'policy' , p ); store . db . prepare ( 'INSERT INTO enrollments VALUES(?,?,?)' ). run ( digest ( code ), Date . now () + 600000 , 'smoke-user' );
p . evidence = { enabled : true , captureDownloads : true , captureInteractions : true }; p . browser . allowDownloads = true ; p . security . yara = { enabled : true , rules : [{ id : 'test-marker' , name : 'Controlled malicious marker' , source : 'rule Block_Test { strings: $a = "SAFETYNET_BLOCK_TEST" condition: $a }' }], maxScanBytes : 1048576 }; p . agents = { enabled : true , scopes : [ 'sessions' , 'navigate' , 'back' , 'forward' , 'reload' , 'read' , 'inspect' , 'scroll' , 'click' , 'type' , 'select' , 'check' , 'application:state' , 'settings:get' , 'settings:update' , 'bookmarks:list' , 'bookmarks:mutate' , 'tabs:create' , 'tabs:activate' , 'tabs:close' , 'tabs:group' ], allowedHosts : [ '127.0.0.1' ], allowSensitiveRead : false }; if ( process . env . SAFETYNET_TEST_OTLP_ENDPOINT ) p . diagnostics = {... p . diagnostics , enabled : true , mode : 'debug' , endpoint : process . env . SAFETYNET_TEST_OTLP_ENDPOINT , sampleRate : 1 , debugSampleRate : 1 , flushIntervalMs : 250 }; store . put ( 'policy' , p );
const config = { publicUrl : 'http://127.0.0.1' , secure : false , adminToken , evidenceKeyFile , requirePQManagement : true , requireIndependentPolicyApproval : true , inspectorUrl : `http://127.0.0.1: ${ inspectorPort } ` , inspectorToken }; const management = createApp ( store , config ). listen ( 0 , '127.0.0.1' ); await new Promise ( r => management . on ( 'listening' , r )); config . publicUrl = `http://127.0.0.1: ${ management . address (). port } ` ;
const reviewerToken = randomBytes ( 32 ). toString ( 'base64url' ); store . db . prepare ( 'INSERT INTO sessions VALUES(?,?,?)' ). run ( digest ( reviewerToken ), Date . now () + 600000 , JSON . stringify ({ sub : 'independent-fixture-reviewer' , role : 'admin' }));
const child = spawn ( electron ,[ 'scripts/interaction-evidence-electron.cjs' ],{ stdio : 'inherit' , windowsHide : true , env : {... process . env , SAFETYNET_SMOKE_SERVER : config . publicUrl , SAFETYNET_SMOKE_CODE : code , SAFETYNET_SMOKE_USERDATA : join ( temporary , 'browser' ), SAFETYNET_SMOKE_ARTIFACTS : resolve ( 'artifacts' ), SAFETYNET_SMOKE_ADMIN : adminToken , SAFETYNET_SMOKE_REVIEWER : reviewerToken }});
const timeout = setTimeout (()=> child . kill (), 180000 ); const exitCode = await new Promise ( r =>{ child . once ( 'exit' , code => r ( code ?? 1 )); child . once ( 'error' ,()=> r ( 1 ));}); clearTimeout ( timeout );
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 (); inspectorProcess . kill ();
// Only this mkdtemp-created fixture directory is removed.
if ( ! temporary . startsWith ( join ( tmpdir (), 'safetynet-smoke-' ))) throw Error ( 'Fixture cleanup boundary invalid' ); rmSync ( temporary ,{ recursive : true , force : true }); await smokeTelemetry ? . shutdown ();
if ( exitCode === 0 && process . env . SAFETYNET_TEST_OTLP_ENDPOINT ){
const proof = JSON . parse ( readFileSync ( resolve ( 'artifacts/interaction-evidence-native-proof.json' ), 'utf8' )), endpoint = process . env . SAFETYNET_TEST_OTLP_ENDPOINT ;
const health = await fetch ( endpoint + '/health' ). then ( r => r . json ()); if ( health . rejectedWrites !== 0 ) throw Error ( 'Collector reports rejected writes' );
await new Promise ( resolve => setTimeout ( resolve , 3000 ));
const trace = await fetch ( endpoint + '/api/trace/' + proof . traceIds [ 0 ]). then ( r => r . json ()), spans = trace . spans || [], root = spans . find ( s => s . operation === 'agent.request' );
if ( ! root ||! spans . some ( s => s . operation === 'agent.dispatch' && s . parentSpanId === root . spanId ) ||! [ 'evidence.challenge' , 'evidence.decrypt' , 'evidence.store' , 'evidence.encrypt' ]. every ( name => spans . some ( s => s . name === name && spans . some ( parent => parent . spanId === s . parentSpanId )))) throw Error ( 'Actual interaction evidence trace correlation missing' );
const browserStages = [ 'evidence.capture' , 'evidence.challenge' , 'evidence.encrypt' , 'evidence.send' , 'evidence.ack' ]; if ( ! browserStages . every ( operation => spans . some ( s => s . name === 'browser.evidence' && s . operation === operation && spans . some ( parent => parent . spanId === s . parentSpanId )))) throw Error ( 'Browser evidence stages missing or uncorrelated' );
2026-10-13 18:24:44 +02:00
const humanTraceProof = [];
for ( let i = 0 ; i < proof . humanTraceIds . length ; i ++ ){
const item = await fetch ( endpoint + '/api/trace/' + proof . humanTraceIds [ i ]). then ( r => r . json ()), humanSpans = item . spans || [];
const capture = humanSpans . find ( span => span . name === 'browser.evidence' && span . operation === 'evidence.capture' );
if ( ! capture ||! humanSpans . some ( span => span . name === 'evidence.store' ) ||! [ 'evidence.challenge' , 'evidence.encrypt' , 'evidence.send' , 'evidence.ack' ]. every ( operation => humanSpans . some ( span => span . name === 'browser.evidence' && span . operation === operation ))){ writeFileSync ( resolve ( 'artifacts/human-interaction-trace-error.json' ), JSON . stringify ({ traceId : item . traceId , eventId : proof . humanEventIds [ i ], spans : humanSpans . map (({ name , operation , spanId , parentSpanId , durationMs })=>({ name , operation , spanId , parentSpanId , durationMs }))}, null , 2 )); throw Error ( 'Physical human source evidence trace phases missing' );}
if ( ! humanSpans . filter ( span => span . parentSpanId ). every ( span => humanSpans . some ( parent => parent . spanId === span . parentSpanId ))) throw Error ( 'Physical human evidence trace parent missing' );
humanTraceProof . push ({ traceId : item . traceId , eventId : proof . humanEventIds [ i ], spanCount : humanSpans . length , captureMs : capture . durationMs , parentCorrelation : true , phases : humanSpans . filter ( span => span . name === 'browser.evidence' ). map (({ operation , durationMs })=>({ operation , durationMs }))});
}
writeFileSync ( resolve ( 'artifacts/human-interaction-trace-proof.json' ), JSON . stringify ({ humanTraceProof , collectorRejectedWrites : health . rejectedWrites , collectorStoredBytes : health . storedBytes }, null , 2 ));
2026-10-13 18:04:02 +02:00
writeFileSync ( resolve ( 'artifacts/interaction-evidence-trace-proof.json' ), JSON . stringify ({ traceId : trace . traceId , linkedNativeEventId : proof . eventIds [ 0 ], spanCount : spans . length , agentRootMs : root . durationMs , browserEvidenceStages : spans . filter ( s => s . name === 'browser.evidence' ). map (({ operation , durationMs , parentSpanId })=>({ operation , durationMs , parentSpanId })), protectedEvidenceStages : spans . filter ( s => s . name . startsWith ( 'evidence.' )). map (({ name , durationMs , parentSpanId })=>({ name , durationMs , parentSpanId })), parentCorrelation : true , collectorRejectedWrites : health . rejectedWrites , collectorStoredBytes : health . storedBytes }, null , 2 ));
}
process . exit ( exitCode );