2026-10-13 16:58:15 +02:00
import test from 'node:test' ;
import assert from 'node:assert/strict' ;
import { randomUUID } from 'node:crypto' ;
import audit from '../packages/core/agent-audit.cjs' ;
import { toECS , toCEF } from '../packages/core/siem.mjs' ;
2026-10-13 23:19:22 +02:00
test ( 'worker audit retains immutable principal generation without inventing a foreground session' ,()=>{
const facts = audit . agentAuditFacts ({ action : 'network:background' , outcome : 'completed' , principalId : 'worker-agent' , credentialGeneration : 3 });
assert . equal ( facts . scope , 'network:background' ); assert . equal ( facts . sessionId , undefined );
assert . deepEqual ( audit . agentFactsForEvent ( 'agent.action' , facts ), facts );
assert . equal ( audit . agentFactsForEvent ( 'agent.denied' , facts ), null );
assert . throws (()=> audit . agentAuditFacts ({... facts , credentialGeneration : undefined }));
for ( const key of [ 'body' , 'cookies' , 'authorization' , 'initiator' , 'url' ]) assert . throws (()=> audit . agentAuditFacts ({... facts ,[ key ] : 'PRIVATE_WORKER_DATA' }));
});
2026-10-13 16:58:15 +02:00
test ( 'agent audit binds every fixed action to its scope and rejects secret-bearing extras' ,()=>{
for ( const [ action , scope ] of Object . entries ( audit . AGENT_SCOPES )){
const outcome = action === 'agent-request' ? 'denied' : 'completed' ;
assert . deepEqual ( audit . agentAuditFacts ({ action , outcome }),{ action , scope , outcome });
assert . throws (()=> audit . AgentAuditSchema . parse ({ action , scope : 'wrong' , outcome }));
}
for ( const extra of [ 'args' , 'prompt' , 'url' , 'principalId' , 'token' ]) assert . throws (()=> audit . agentAuditFacts ({ action : 'read' , outcome : 'completed' ,[ extra ] : 'synthetic-secret' }));
assert . throws (()=> audit . agentAuditFacts ({ action : 'agent-request' , outcome : 'authorized' }));
assert . throws (()=> audit . agentAuditFacts ({ action : 'read' , outcome : 'completed' , sessionId : 'secret-path' }));
});
test ( 'agent lifecycle exporters preserve trace and chain fields and exclude invalid metadata' ,()=>{
const sessionId = randomUUID (), metadata = { agent : audit . agentAuditFacts ({ action : 'type' , outcome : 'completed' , sessionId }), ruleId : 'type:completed' , policyVersion : 2 , traceId : 'a' . repeat ( 32 ), spanId : 'b' . repeat ( 16 )};
const event = { id : 7 , time : new Date (). toISOString (), kind : 'agent.action' , device_id : 'device' , metadata , audit : { hash : 'hash' , sequence : 7 }};
const ecs = toECS ( event ), cef = toCEF ( event );
assert . deepEqual ( ecs . safetynet . agent ,{ action : 'type' , scope : 'browser:interact' , outcome : 'completed' , session_id : sessionId });
for ( const value of [ 'cs1Label=RuleId' , 'cs2Label=AgentAction' , 'cs3Label=AgentScope' , 'cs4Label=TraceId' , 'cs5Label=SpanId' , 'cs6Label=AgentOutcome' , 'flexString1Label=AuditRecordHash' , `requestContext= ${ sessionId } ` ]) assert . ok ( cef . includes ( value ), value );
event . kind = 'download.released' ; assert . equal ( toECS ( event ). safetynet . agent , undefined ); assert . equal ( toCEF ( event ). includes ( 'AgentAction' ), false );
event . kind = 'agent.denied' ; assert . equal ( toECS ( event ). safetynet . agent , undefined );
metadata . agent = {... metadata . agent , outcome : 'denied' , prompt : 'synthetic-secret' }; assert . equal ( toECS ( event ). safetynet . agent , undefined ); assert . equal ( toCEF ( event ). includes ( 'synthetic-secret' ), false );
delete metadata . agent . prompt ; assert . equal ( toECS ( event ). safetynet . agent . outcome , 'denied' );
metadata . agent . outcome = 'failed' ; assert . equal ( toECS ( event ). event . outcome , 'failure' ); assert . deepEqual ( toECS ( event ). event . type ,[ 'error' ]);
metadata . request = { bytes : 123 , detectedType : 'text' }; assert . ok ( toCEF ( event ). includes ( 'cs6=failed' )); assert . equal ( toCEF ( event ). includes ( 'DetectedRequestType' ), false );
});
test ( 'central agent facts ingestion validates kind pairing and rejects unstructured content' , async t =>{
const { mkdtempSync , rmSync } = await import ( 'node:fs' ),{ tmpdir } = await import ( 'node:os' ),{ join , resolve } = await import ( 'node:path' );
const { openStore , digest } = await import ( '../apps/server/store.mjs' ),{ createApp } = await import ( '../apps/server/app.mjs' );
const prefix = join ( tmpdir (), 'safetynet-agent-audit-' ), dir = mkdtempSync ( prefix ), store = openStore ( dir ), credential = 'synthetic-device-agent-audit-token' ;
store . db . prepare ( 'INSERT INTO devices(id,name,token_hash) VALUES(?,?,?)' ). run ( 'device' , 'Synthetic' , digest ( credential ));
const server = createApp ( store ,{ publicUrl : 'http://127.0.0.1' , secure : false }). listen ( 0 , '127.0.0.1' ); await new Promise ( r => server . once ( 'listening' , r ));
t . after ( async ()=>{ server . closeAllConnections (); await new Promise ( r => server . close ( r )); store . db . close (); assert . ok ( resolve ( dir ). startsWith ( resolve ( prefix ))); rmSync ( dir ,{ recursive : true , force : true });});
const send = body => fetch ( `http://127.0.0.1: ${ server . address (). port } /api/device/events` ,{ method : 'POST' , headers : { authorization : `Bearer ${ credential } ` , 'content-type' : 'application/json' }, body : JSON . stringify ({ eventId : randomUUID (), policyVersion : 1 ,... body })});
const agent = audit . agentAuditFacts ({ action : 'read' , outcome : 'authorized' , sessionId : randomUUID ()});
assert . equal (( await send ({ kind : 'agent.action' , agent })). status , 200 );
const attributed = {... agent , principalId : 'agent-' + randomUUID (), credentialGeneration : 1 };
assert . equal (( await send ({ kind : 'agent.action' , agent : attributed })). status , 200 );
for ( const partial of [{... agent , principalId : attributed . principalId },{... agent , credentialGeneration : 1 }]) assert . equal (( await send ({ kind : 'agent.action' , agent : partial })). status , 400 );
for ( const body of [{ kind : 'agent.denied' , agent },{ kind : 'browser.started' , agent },{ kind : 'agent.action' , agent : {... agent , scope : 'browser:interact' }},{ kind : 'agent.action' , agent : {... agent , prompt : 'synthetic-secret' }}]) assert . equal (( await send ( body )). status , 400 );
assert . equal (( await send ({ kind : 'agent.denied' , agent : {... agent , outcome : 'denied' }})). status , 200 );
assert . equal (( await send ({ kind : 'agent.action' , ruleId : 'read:completed' })). status , 200 ); // Older enrolled client compatibility.
const rows = store . db . prepare ( 'SELECT * FROM events' ). all (); assert . equal ( rows . length , 4 ); assert . equal ( rows . some ( row => row . metadata . includes ( 'synthetic-secret' )), false ); assert . equal ( store . auditIntegrity . verify (). valid , true );
});
test ( 'agent principal attribution is bounded, paired and never invented for legacy events' ,()=>{
const base = { action : 'read' , outcome : 'completed' }, principalId = 'agent:' + randomUUID (), credentialGeneration = 2 ;
const parsed = audit . agentAuditFacts ({... base , principalId , credentialGeneration });
assert . equal ( parsed . principalId , principalId ); assert . equal ( parsed . credentialGeneration , 2 );
for ( const extra of [{ principalId },{ credentialGeneration },{ principalId : 'a' . repeat ( 81 ), credentialGeneration },{ principalId : 'bad principal' , credentialGeneration },{ principalId : 'https://secret' , credentialGeneration },{ principalId , credentialGeneration : 0 },{ principalId , credentialGeneration : 1.5 },{ principalId , credentialGeneration : Number . MAX_SAFE_INTEGER + 1 },{ principalId : null , credentialGeneration }]) assert . throws (()=> audit . agentAuditFacts ({... base ,... extra }));
assert . equal ( audit . agentAuditFacts ( base ). principalId , undefined );
const event = { id : 9 , time : new Date (). toISOString (), kind : 'agent.action' , metadata : { actor : 'human-actor' , agent : parsed , ruleId : 'read:completed' , traceId : 'a' . repeat ( 32 ), spanId : 'b' . repeat ( 16 )}, audit : { hash : 'hash' , sequence : 9 }};
const ecs = toECS ( event ), cef = toCEF ( event );
assert . equal ( ecs . safetynet . agent . principal_id , principalId ); assert . equal ( ecs . safetynet . agent . credential_generation , 2 ); assert . equal ( ecs . user . id , 'human-actor' );
for ( const field of [ `suid= ${ principalId } ` , 'flexNumber1Label=AgentCredentialGeneration' , 'flexNumber1=2' , 'suser=human-actor' , 'cs1Label=RuleId' , 'cs2Label=AgentAction' , 'cs3Label=AgentScope' , 'cs4Label=TraceId' , 'cs5Label=SpanId' , 'cs6Label=AgentOutcome' , 'flexString1Label=AuditRecordHash' ]) assert . ok ( cef . includes ( field ), field );
event . metadata . agent = audit . agentAuditFacts ( base ); assert . equal ( toECS ( event ). safetynet . agent . principal_id , undefined ); assert . equal ( toCEF ( event ). includes ( 'suid=' ), false ); assert . equal ( toCEF ( event ). includes ( 'AgentCredentialGeneration' ), false );
event . metadata . agent = {... parsed , credentialGeneration : undefined }; assert . equal ( toECS ( event ). safetynet . agent , undefined ); assert . equal ( toCEF ( event ). includes ( 'suid=' ), false );
});