Files
safetynet/tests/audit-api.test.mjs
T

29 lines
3.2 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import {mkdtempSync,rmSync,readFileSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {openStore} from '../apps/server/store.mjs';
import {createApp} from '../apps/server/app.mjs';
import {startSIEM} from '../apps/server/siem.mjs';
import {startTelemetry} from '../apps/server/telemetry.mjs';
import {adminRoutePermission} from '../apps/server/admin-access.mjs';
test('audit API verifies real transactional events and exposes bounded metadata-only proof',{timeout:20000},async t=>{
const directory=mkdtempSync(join(tmpdir(),'safetynet-audit-api-')),store=openStore(directory),credential='test-only-audit-administrator-123456789';
const sdk=startTelemetry();const server=createApp(store,{publicUrl:'http://127.0.0.1',adminToken:credential,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));await sdk?.shutdown();store.db.close();assert.ok(directory.startsWith(join(tmpdir(),'safetynet-audit-api-')));rmSync(directory,{recursive:true,force:true});});
const url=`http://127.0.0.1:${server.address().port}`,headers={authorization:`Bearer ${credential}`};
store.event(null,'dlp.block',{ruleId:'test',content:'SYNTHETIC_CONTENT_NOT_IN_PROOF'});
assert.equal((await fetch(url+'/api/admin/audit/integrity')).status,401);
assert.equal(adminRoutePermission('GET','/api/admin/audit/integrity'),'audit.read');
assert.equal(adminRoutePermission('GET','/api/admin/audit/entries'),'audit.read');
let response=await fetch(url+'/api/admin/audit/integrity',{headers});assert.equal(response.status,200);let result=await response.json();assert.equal(result.valid,true);assert.equal(result.authenticated,false);
response=await fetch(url+'/api/admin/audit/entries?limit=1',{headers});assert.equal(response.status,200);assert.equal((await response.text()).includes('SYNTHETIC_CONTENT_NOT_IN_PROOF'),false);
assert.equal((await fetch(url+'/api/admin/audit/entries?limit=1001',{headers})).status,400);
const exporter=startSIEM(store,directory,{});try{await exporter.flush();const event=JSON.parse(readFileSync(join(directory,'security.ndjson'),'utf8').trim());assert.equal(event.safetynet.audit.hash,store.auditIntegrity.linkage(1).hash);assert.equal(JSON.stringify(event).includes('SYNTHETIC_CONTENT_NOT_IN_PROOF'),false);}finally{exporter.stop();}
store.db.prepare("UPDATE events SET kind='changed' WHERE id=1").run();response=await fetch(url+'/api/admin/audit/integrity',{headers});result=await response.json();assert.equal(result.valid,false);assert.ok(result.errors.includes('event-digest'));
});
test('failed audit append rolls back event and Sigma alert atomically',t=>{
const directory=mkdtempSync(join(tmpdir(),'safetynet-audit-atomic-')),s=openStore(directory);t.after(()=>{s.db.close();assert.ok(directory.startsWith(join(tmpdir(),'safetynet-audit-atomic-')));rmSync(directory,{recursive:true,force:true});});
s.db.exec("CREATE TRIGGER reject_audit BEFORE INSERT ON audit_integrity BEGIN SELECT RAISE(ABORT,'audit fixture'); END");assert.throws(()=>s.event(null,'dlp.block',{}),/audit fixture/);assert.equal(s.db.prepare('SELECT count(*) n FROM events').get().n,0);
});