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); }); 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); });