2026-10-13 10:33:31 +02:00
import test from 'node:test' ;
import assert from 'node:assert/strict' ;
import { mkdtempSync , readFileSync , rmSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { join } from 'node:path' ;
import { openStore } from '../apps/server/store.mjs' ;
import { startSIEM } from '../apps/server/siem.mjs' ;
import { toECS , toCEF } from '../packages/core/siem.mjs' ;
test ( 'CEF escapes headers and extension delimiters; ECS excludes raw content' ,()=>{
const e = { id : 1 , time : new Date (). toISOString (), device_id : 'device' , kind : 'dlp.block' , metadata : { ruleId : 'a=b\nc' , policyVersion : 2 , prompt : 'must never be exported' }};
const cef = toCEF ( e , 'install' ); assert . ok ( cef . includes ( 'cs1=a\\=b\\nc' )); assert . ok ( cef . startsWith ( 'CEF:0|' )); assert . equal ( cef . includes ( 'must never' ), false );
const ecs = toECS ( e , 'install' ); assert . equal ( ecs . event . id , 'install:1' ); assert . equal ( JSON . stringify ( ecs ). includes ( 'must never' ), false ); assert . deepEqual ( ecs . event . type ,[ 'denied' ]);
});
test ( 'durable file exporter resumes after restart and advances only written events' , async t =>{
const directory = mkdtempSync ( join ( tmpdir (), 'safetynet-siem-' )), store = openStore ( directory );
let exporter ; t . after (()=>{ exporter ? . stop (); store . db . close (); rmSync ( directory ,{ recursive : true , force : true });});
store . event ( 'device' , 'dlp.block' ,{ ruleId : 'secret' , policyVersion : 1 }); exporter = startSIEM ( store , directory ,{}); await exporter . flush ();
assert . equal ( exporter . status (). cursor , 1 ); exporter . stop ();
store . event ( null , 'policy.published' ,{ actor : 'admin' , version : 2 }); exporter = startSIEM ( store , directory ,{}); await exporter . flush (); assert . equal ( exporter . status (). cursor , 2 );
const rows = readFileSync ( join ( directory , 'security.ndjson' ), 'utf8' ). trim (). split ( '\n' ). map ( v => JSON . parse ( v )); assert . equal ( rows . length , 2 ); assert . notEqual ( rows [ 0 ]. event . id , rows [ 1 ]. event . id );
});
test ( 'remote SIEM export refuses plaintext endpoints' , t =>{
const directory = mkdtempSync ( join ( tmpdir (), 'safetynet-siem-' )), store = openStore ( directory );
t . after (()=>{ store . db . close (); rmSync ( directory ,{ recursive : true , force : true });}); assert . throws (()=> startSIEM ( store , directory ,{ SIEM_HTTP_URL : 'http://example.com' }), /HTTPS/ );
});
test ( 'ECS and CEF correlate validated trace identities without exporting arbitrary trace text' ,()=>{
const e = { id : 1 , time : new Date (). toISOString (), kind : 'clipboard.copy' , metadata : { traceId : '1234567890abcdef1234567890abcdef' , spanId : '1234567890abcdef' }};
assert . equal ( toECS ( e ). trace . id , e . metadata . traceId ); assert . match ( toCEF ( e ), /cs4Label=TraceId/ );
e . metadata . traceId = 'token=synthetic-secret' ; e . metadata . spanId = 'invalid' ; assert . equal ( toECS ( e ). trace , undefined ); assert . equal ( toCEF ( e ). includes ( 'synthetic-secret' ), false );
});
test ( 'denied agent actions and correlated Sigma alerts export durably without raw source content' , async t =>{
const directory = mkdtempSync ( join ( tmpdir (), 'safetynet-sigma-' )), store = openStore ( directory ); let exporter ; t . after (()=>{ exporter ? . stop (); store . db . close (); rmSync ( directory ,{ recursive : true , force : true });});
const p = store . get ( 'policy' ); p . security . sigma . rules = [{ id : 'agent-denial' , title : 'Agent denial' , level : 'high' , detection : { selection : { kind : 'agent.denied' }, condition : 'selection' }}]; store . put ( 'policy' , p );
store . db . exec ( 'BEGIN IMMEDIATE' ); store . event ( 'device' , 'agent.denied' ,{ ruleId : 'agent:read:blocked' , policyVersion : 1 , prompt : 'excluded raw content' }); store . db . exec ( 'COMMIT' );
const stored = store . db . prepare ( 'SELECT * FROM events ORDER BY id' ). all (). map ( e =>({... e , metadata : JSON . parse ( e . metadata )})); assert . equal ( stored . length , 2 ); assert . equal ( stored [ 1 ]. kind , 'sigma.alert' ); assert . deepEqual ( stored [ 1 ]. metadata ,{ ruleId : 'agent-denial' , level : 'high' , sourceEventId : stored [ 0 ]. id }); assert . equal ( store . db . prepare ( 'SELECT count(*) AS n FROM alerts' ). get (). n , 1 );
assert . deepEqual ( toECS ( stored [ 0 ], 'install' ). event . type ,[ 'denied' ]); const alert = toECS ( stored [ 1 ], 'install' ); assert . equal ( alert . event . kind , 'alert' ); assert . deepEqual ( alert . event . category ,[ 'intrusion_detection' ]); assert . deepEqual ( alert . event . type ,[ 'info' ]); assert . equal ( alert . event . severity , 7 ); assert . equal ( alert . safetynet . source_event_id , `install: ${ stored [ 0 ]. id } ` ); assert . match ( toCEF ( stored [ 1 ], 'install' ), /cs2Label=SourceEventId cs2=install:1/ );
exporter = startSIEM ( store , directory ,{}); await exporter . flush (); const rows = readFileSync ( join ( directory , 'security.ndjson' ), 'utf8' ). trim (). split ( '\n' ). map ( JSON . parse ); assert . equal ( rows . length , 2 ); assert . equal ( rows [ 1 ]. event . action , 'sigma.alert' ); assert . equal ( rows [ 1 ]. safetynet . source_event_id , rows [ 0 ]. event . id ); assert . equal ( JSON . stringify ( rows ). includes ( 'excluded raw content' ), false );
store . db . exec ( 'BEGIN IMMEDIATE' ); store . event ( 'device' , 'agent.denied' ,{ ruleId : 'agent:read:blocked' }); store . db . exec ( 'ROLLBACK' ); assert . equal ( store . db . prepare ( 'SELECT count(*) AS n FROM events' ). get (). n , 2 ); assert . equal ( store . db . prepare ( 'SELECT count(*) AS n FROM alerts' ). get (). n , 1 );
});
2026-10-13 16:58:15 +02:00
test ( 'administrator forwarding toggle stops HTTPS export, preserves audit, resumes cursor after restart' , async t =>{
const { createServer } = await import ( 'node:https' );
const certPath = new URL ( './fixtures/icap/cert.pem' , import . meta . url ), keyPath = new URL ( './fixtures/icap/key.pem' , import . meta . url );
const batches = []; let exporter ;
const receiver = createServer ({ cert : readFileSync ( certPath ), key : readFileSync ( keyPath )},( req , res )=>{ let body = '' ; req . on ( 'data' , chunk => body += chunk ); req . on ( 'end' ,()=>{ batches . push ( body . trim (). split ( '\n' ). map ( JSON . parse )); res . end ( '{}' );});});
await new Promise ( resolve => receiver . listen ( 0 , '127.0.0.1' , resolve ));
const directory = mkdtempSync ( join ( tmpdir (), 'safetynet-siem-toggle-' )), store = openStore ( directory );
t . after ( async ()=>{ exporter ? . stop (); await new Promise ( resolve => receiver . close ( resolve )); store . db . close (); rmSync ( directory ,{ recursive : true , force : true });});
const env = { SIEM_HTTP_URL : `https://127.0.0.1: ${ receiver . address (). port } /events` , SIEM_CA_FILE : certPath };
store . event ( 'device' , 'download.scan' ,{ ruleId : 'synthetic' }); exporter = startSIEM ( store , directory , env ); await exporter . flush (); assert . equal ( exporter . status (). cursor , 1 );
const disabled = exporter . setEnabled ({ enabled : false , expectedRevision : 1 }, 'admin' ); assert . equal ( disabled . enabled , false );
store . event ( 'device' , 'clipboard.copy' ,{}); await exporter . flush (); assert . equal ( batches . length , 1 ); assert . equal ( exporter . status (). cursor , 1 ); assert . equal ( exporter . status (). pending , 2 );
assert . throws (()=> exporter . setEnabled ({ enabled : true , expectedRevision : 1 }, 'admin' ), /revision conflict/ );
exporter . stop (); exporter = startSIEM ( store , directory , env ); await exporter . flush (); assert . equal ( exporter . status (). enabled , false ); assert . equal ( batches . length , 1 );
exporter . setEnabled ({ enabled : true , expectedRevision : 2 }, 'admin' ); await exporter . flush (); assert . equal ( batches . length , 2 ); assert . equal ( exporter . status (). pending , 0 );
assert . deepEqual ( batches . flat (). map ( e => e . event . id ),[ 1 , 2 , 3 , 4 ]. map ( id => ` ${ store . get ( 'installationId' ) } : ${ id } ` ));
assert . equal ( store . db . prepare ( "SELECT count(*) AS n FROM events WHERE kind='siem.forwarding-changed'" ). get (). n , 2 );
assert . equal ( JSON . stringify ( exporter . status ()). includes ( '/events' ), false );
});
test ( 'disabling forwarding aborts an unacknowledged HTTPS request without advancing cursor' , async t =>{
const { createServer } = await import ( 'node:https' ); let arrived , closed ; const arrival = new Promise ( resolve => arrived = resolve ), closure = new Promise ( resolve => closed = resolve );
const receiver = createServer ({ cert : readFileSync ( new URL ( './fixtures/icap/cert.pem' , import . meta . url )), key : readFileSync ( new URL ( './fixtures/icap/key.pem' , import . meta . url ))},( req , res )=>{ req . resume (); res . on ( 'close' , closed ); req . on ( 'end' , arrived );});
await new Promise ( resolve => receiver . listen ( 0 , '127.0.0.1' , resolve ));
const directory = mkdtempSync ( join ( tmpdir (), 'safetynet-siem-abort-' )), store = openStore ( directory ); let exporter ;
t . after ( async ()=>{ exporter ? . stop (); receiver . closeAllConnections (); await new Promise ( resolve => receiver . close ( resolve )); store . db . close (); rmSync ( directory ,{ recursive : true , force : true });});
store . event ( 'device' , 'download.scan' ,{}); exporter = startSIEM ( store , directory ,{ SIEM_HTTP_URL : `https://127.0.0.1: ${ receiver . address (). port } ` , SIEM_CA_FILE : new URL ( './fixtures/icap/cert.pem' , import . meta . url )});
await arrival ; const active = exporter . flush (); exporter . setEnabled ({ enabled : false , expectedRevision : 1 }, 'admin' ); await active ; await closure ;
assert . equal ( exporter . status (). cursor , 0 ); assert . equal ( exporter . status (). failures , 0 ); assert . equal ( exporter . status (). pending , 2 );
});
test ( 'SIEM management API enforces authentication, administrator role, origin, strict settings and CAS' , async t =>{
const { createApp } = await import ( '../apps/server/app.mjs' ); const { digest } = await import ( '../apps/server/store.mjs' );
const directory = mkdtempSync ( join ( tmpdir (), 'safetynet-siem-api-' )), store = openStore ( directory ), exporter = startSIEM ( store , directory ,{ SIEM_ENABLED : 'false' });
const adminToken = 'synthetic-admin-credential-012345678901234567890' ; const config = { publicUrl : 'http://127.0.0.1:4310' , adminToken , secure : false , siemControl : exporter };
const server = createApp ( store , config ). listen ( 0 , '127.0.0.1' ); await new Promise ( resolve => server . on ( 'listening' , resolve ));
t . after ( async ()=>{ exporter . stop (); await new Promise ( resolve => server . close ( resolve )); store . db . close (); rmSync ( directory ,{ recursive : true , force : true });});
const base = `http://127.0.0.1: ${ server . address (). port } ` ;
async function api ( method , body , headers = { authorization : `Bearer ${ adminToken } ` }){ const r = await fetch ( base + '/api/admin/siem' ,{ method , headers : {... headers ,...( body ? { 'content-type' : 'application/json' } : {})}, body : body ? JSON . stringify ( body ) : undefined }); return { status : r . status , body : await r . json ()};}
assert . equal (( await api ( 'GET' , null ,{})). status , 401 ); const initial = await api ( 'GET' ); assert . equal ( initial . status , 200 ); assert . equal ( initial . body . enabled , false );
for ( const role of [ 'operator' , 'auditor' ]){ const session = `synthetic- ${ role } -session-012345678901234567890` ; store . db . prepare ( 'INSERT INTO sessions VALUES(?,?,?)' ). run ( digest ( session ), Date . now () + 60000 , JSON . stringify ({ sub : role , role })); const headers = { cookie : `sn_session= ${ session } ` , origin : config . publicUrl }; assert . equal (( await api ( 'GET' , null , headers )). status , 200 ); assert . equal (( await api ( 'PUT' ,{ enabled : true , expectedRevision : 1 }, headers )). status , 403 );}
assert . equal (( await api ( 'PUT' ,{ enabled : true , expectedRevision : 1 },{ authorization : `Bearer ${ adminToken } ` , origin : 'https://untrusted.example' })). status , 403 );
assert . equal (( await api ( 'PUT' ,{ enabled : true , expectedRevision : 1 , endpoint : 'https://untrusted.example' })). status , 400 );
const enabled = await api ( 'PUT' ,{ enabled : true , expectedRevision : 1 }); assert . equal ( enabled . status , 200 ); assert . equal ( enabled . body . revision , 2 );
assert . equal (( await api ( 'PUT' ,{ enabled : false , expectedRevision : 1 })). status , 409 ); assert . equal (( await api ( 'GET' )). body . enabled , true );
assert . equal ( store . db . prepare ( "SELECT count(*) AS n FROM events WHERE kind='siem.forwarding-changed'" ). get (). n , 1 );
config . siemControl = null ; assert . equal (( await api ( 'GET' )). status , 503 ); assert . equal (( await api ( 'PUT' ,{ enabled : false , expectedRevision : 2 })). status , 503 );
});