2026-10-13 10:33:31 +02:00
import assert from 'node:assert/strict' ;
import { createServer } from 'node:http' ;
import { createRequire } from 'node:module' ;
import { mkdtempSync , rmSync , mkdirSync , writeFileSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { join , resolve , sep , basename } from 'node:path' ;
import { spawn } from 'node:child_process' ;
import { randomBytes } from 'node:crypto' ;
import { createApp } from '../apps/server/app.mjs' ;
2026-10-13 16:58:15 +02:00
import { startTelemetry } from '../apps/server/telemetry.mjs' ;
2026-10-13 10:33:31 +02:00
import { openStore , digest } from '../apps/server/store.mjs' ;
const require = createRequire ( import . meta . url ), directory = mkdtempSync ( join ( tmpdir (), 'safetynet-native-tabs-' ));
2026-10-13 16:58:15 +02:00
let sink , management , collector , store , child , sdk ; const previousEndpoint = process . env . OTEL_EXPORTER_OTLP_ENDPOINT ; const requests = {}, posts = [], diagnosticRequests = []; let privateSentinels = [];
2026-10-13 18:04:02 +02:00
// Backend and browser deliberately share this test receiver. Count requests per
// fixed OTLP resource identity so backend flushes cannot impersonate browser exports.
function exportCountsByService ( records ){ const counts = { browser : 0 , controlPlane : 0 , unknown : 0 }; for ( const record of records ){ const groups = [...( record . payload . resourceSpans || []),...( record . payload . resourceLogs || []),...( record . payload . resourceMetrics || [])], services = new Set ( groups . map ( group => group . resource ? . attributes ? . find ( a => a . key === 'service.name' ) ? . value ? . stringValue )); if ( services . has ( 'safetynet-browser' )) counts . browser ++ ; if ( services . has ( 'safetynet-control-plane' )) counts . controlPlane ++ ; if ( ! services . size || [... services ]. some ( v => v !== 'safetynet-browser' && v !== 'safetynet-control-plane' )) counts . unknown ++ ;} return counts ;}
const diagnosticStatus = ()=>{ const spans = diagnosticRequests . filter ( r => r . path . endsWith ( '/traces' )). flatMap ( r => r . payload . resourceSpans || []). flatMap ( r => r . scopeSpans || []). flatMap ( r => r . spans || []); const logs = diagnosticRequests . filter ( r => r . path . endsWith ( '/logs' )). flatMap ( r => r . payload . resourceLogs || []). flatMap ( r => r . scopeLogs || []). flatMap ( r => r . logRecords || []); return { exports : diagnosticRequests . length , exportsByService : exportCountsByService ( diagnosticRequests ), names : [... new Set ( spans . map ( span => span . name ))], spans : spans . length , logs : logs . length , timings : spans . every ( span => BigInt ( span . endTimeUnixNano ) >= BigInt ( span . startTimeUnixNano ) && span . attributes . some ( a => a . key === 'safetynet.duration.ms' )), parentChild : spans . some ( child => child . parentSpanId && spans . some ( parent => parent . spanId === child . parentSpanId && parent . traceId === child . traceId )), crossProcessTrace : spans . some ( child => child . name === 'server.request' && spans . some ( parent => parent . name === 'browser.management' && parent . spanId === child . parentSpanId && parent . traceId === child . traceId )), crossProcessTraceIds : [... new Set ( spans . filter ( child => child . name === 'server.request' && spans . some ( parent => parent . name === 'browser.management' && parent . spanId === child . parentSpanId && parent . traceId === child . traceId )). map ( span => span . traceId ))], correlatedLogs : logs . some ( log => log . traceId && spans . some ( span => span . traceId === log . traceId && span . spanId === log . spanId )), sensitivePresent : privateSentinels . some ( secret => JSON . stringify ( diagnosticRequests ). includes ( secret ))};};
2026-10-13 10:33:31 +02:00
try {
2026-10-13 16:58:15 +02:00
collector = createServer (( req , res )=>{ if ( req . method === 'GET' && req . url === '/status' ){ res . setHeader ( 'content-type' , 'application/json' ); res . end ( JSON . stringify ( diagnosticStatus ())); return ;} const chunks = []; req . on ( 'data' , chunk => chunks . push ( chunk )); req . on ( 'end' ,()=>{ diagnosticRequests . push ({ path : req . url , payload : JSON . parse ( Buffer . concat ( chunks ). toString ())}); res . setHeader ( 'content-type' , 'application/json' ); res . end ( '{}' );});}). listen ( 0 , '127.0.0.1' ); await new Promise ( r => collector . once ( 'listening' , r )); const collectorUrl = `http://127.0.0.1: ${ collector . address (). port } ` ; process . env . OTEL_EXPORTER_OTLP_ENDPOINT = collectorUrl ; sdk = startTelemetry ();
2026-10-13 10:33:31 +02:00
sink = createServer (( req , res )=>{ const path = new URL ( req . url , 'http://localhost' ). pathname ; requests [ path ] = ( requests [ path ] || 0 ) + 1 ;
if ( path === '/counts' ){ res . setHeader ( 'content-type' , 'application/json' ); res . end ( JSON . stringify ({ requests , posts })); return ;}
if ( req . method === 'POST' ){ posts . push ( path ); req . resume (); res . end ( 'received' ); return ;}
res . setHeader ( 'content-type' , 'text/html' ); res . end ( `<!doctype html><title> ${ path } </title><h1>Native tab ${ path } </h1><input id="remember"><a id="next" href=" ${ path === '/' ? '/a-two' : '/b-two' } ">Next page</a><div style="height:4000px">Unstyled fixture content</div>` );
}). listen ( 0 , '127.0.0.1' ); await new Promise ( r => sink . once ( 'listening' , r )); const url = `http://127.0.0.1: ${ sink . address (). port } ` ;
store = openStore ( join ( directory , 'management' )); const policy = store . get ( 'policy' ); policy . diagnostics = {... policy . diagnostics , enabled : true , mode : 'debug' , endpoint : collectorUrl , sampleRate : 1 , debugSampleRate : 1 , flushIntervalMs : 250 }; policy . browser . homeUrl = url ; policy . browser . allowedHosts = [ '127.0.0.1' ]; policy . dlp . rules = [{ id : 'fixture-secret' , label : 'Fixture secret' , kind : 'literal' , value : 'TAB_FIXTURE_SECRET' , action : 'block' }]; store . put ( 'policy' , policy );
const code = randomBytes ( 32 ). toString ( 'base64url' ), adminToken = randomBytes ( 32 ). toString ( 'base64url' ); privateSentinels = [ code , adminToken , 'TAB_FIXTURE_SECRET' ]; store . db . prepare ( 'INSERT INTO enrollments VALUES(?,?,?)' ). run ( digest ( code ), Date . now () + 600000 , 'synthetic-native-tabs-user' );
const config = { adminToken , publicUrl : 'http://127.0.0.1' , secure : false }; 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 env = {... process . env , SAFETYNET_TABS_COLLECTOR : collectorUrl , SAFETYNET_TABS_SERVER : config . publicUrl , SAFETYNET_TABS_CODE : code , SAFETYNET_TABS_ADMIN : adminToken , SAFETYNET_TABS_SINK : url , SAFETYNET_TABS_USERDATA : join ( directory , 'browser' ), SAFETYNET_SMOKE_USERDATA : join ( directory , 'browser' )}; delete env . ELECTRON_RUN_AS_NODE ;
child = spawn ( require ( 'electron' ),[ 'scripts/native-tabs-smoke.cjs' ],{ env , windowsHide : true , stdio : [ 'ignore' , 'pipe' , 'pipe' ]}); child . stdout . on ( 'data' , b => process . stdout . write ( b )); child . stderr . on ( 'data' , b =>{ const message = String ( b ). replaceAll ( code , '[fixture enrollment]' ). replaceAll ( adminToken , '[fixture admin]' ); process . stderr . write ( message );});
const timer = setTimeout (()=> child . kill (), 120000 ); const codeExit = await new Promise ( r =>{ child . once ( 'exit' , c => r ( c ?? 1 )); child . once ( 'error' ,()=> r ( 1 ));}); clearTimeout ( timer ); assert . equal ( codeExit , 0 , 'Actual native tabs proof failed' );
2026-10-13 16:58:15 +02:00
assert . equal ( posts . length , 0 , 'Blocked fixture data reached receiver' ); await sdk . shutdown (); sdk = null ; const diagnostics = diagnosticStatus (); assert . ok ( diagnostics . spans > 0 && diagnostics . logs > 0 && diagnostics . timings && diagnostics . parentChild && diagnostics . correlatedLogs ); assert . equal ( diagnostics . sensitivePresent , false ); assert . equal ( diagnostics . crossProcessTrace , true , 'Browser management span must parent actual backend HTTP request' ); for ( const name of [ 'browser.ipc' , 'browser.navigation' , 'browser.dns' , 'browser.request' ]) assert . ok ( diagnostics . names . includes ( name ), `Missing native timing ${ name } ` ); writeFileSync ( 'artifacts/native-browser-diagnostics-proof.json' , JSON . stringify ( diagnostics , null , 2 )); mkdirSync ( 'artifacts' ,{ recursive : true }); writeFileSync ( 'artifacts/native-tabs-receiver-proof.json' , JSON . stringify ({ noBlockedPosts : true , requests , posts }, null , 2 ));
} finally { child ? . kill (); await sdk ? . shutdown (); if ( previousEndpoint === undefined ) delete process . env . OTEL_EXPORTER_OTLP_ENDPOINT ; else process . env . OTEL_EXPORTER_OTLP_ENDPOINT = previousEndpoint ; for ( const server of [ management , sink , collector ]) if ( server ) await new Promise ( r =>{ server . closeAllConnections ? .(); server . close ( r );}); store ? . db . close (); const target = resolve ( directory ); if ( ! target . startsWith ( resolve ( tmpdir ()) + sep ) ||! basename ( target ). startsWith ( 'safetynet-native-tabs-' )) throw Error ( 'Unsafe fixture cleanup' ); rmSync ( target ,{ recursive : true , force : true , maxRetries : 10 , retryDelay : 100 });}