Implement managed browser security, individual agents and observability

This commit is contained in:
Karl committed 2026-10-13 16:58:15 +02:00
1 parent ff482ebe23
commit ea448de2d4
172 files changed
+5404 -347

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
const {deliverAuditRecord,stripPendingEvidence,collectionAllowed,validatedEvidence}=createRequire(import.meta.url)('../apps/browser/evidence-delivery.cjs');
function fixture(){
const payload={sourceUrl:'https://example.com/report',name:'report.pdf',sha256:'a'.repeat(64),bytes:12,mime:'application/pdf',decision:'allow',ruleId:'scanner-allow'};
const record={eventId:'b7a17c1d-680e-4c90-9a3c-3d18f50a4e3f',kind:'download.released',file:{sha256:payload.sha256,bytes:payload.bytes},protectedEvidence:{deviceId:'device-a',payload}};
const context={ready:true,deviceId:'device-a',policy:{expiresAt:new Date(Date.now()+60000).toISOString(),evidence:{enabled:true,captureDownloads:true}}};
return {payload,record,context};
}
test('event-first retries preserve original UUID and atomic tuple without leaking evidence into events',async()=>{
const f=fixture(),calls=[],snapshot=structuredClone(f.record);let failure=true;
const send=async(route,body)=>{calls.push({route,body});if(route.endsWith('/evidence')){if(failure){const error=Error('temporarily unavailable');error.status=503;throw error;}return {id:'00000000-0000-4000-8000-000000000001',sourceEventId:7};}return {};};
await assert.rejects(deliverAuditRecord({record:f.record,send,getContext:()=>f.context}),/temporarily unavailable/);assert.deepEqual(f.record,snapshot);
failure=false;assert.equal((await deliverAuditRecord({record:f.record,send,getContext:()=>f.context})).evidenceDelivered,true);
assert.deepEqual(calls.map(c=>c.route),['/api/device/events','/api/device/evidence','/api/device/events','/api/device/evidence']);
for(const call of calls){assert.equal(call.body.eventId,f.record.eventId);if(call.route.endsWith('/events'))assert.equal('protectedEvidence' in call.body,false);else assert.deepEqual(call.body,{eventId:f.record.eventId,type:'download',payload:f.payload});}
});
test('403 discards evidence after source event; unavailable evidence service keeps record retryable',async()=>{
for(const status of [403,503]){const f=fixture(),routes=[];const task=deliverAuditRecord({record:f.record,getContext:()=>f.context,send:async route=>{routes.push(route);if(route.endsWith('/evidence'))throw Object.assign(Error('rejected'),{status});return {};}});
if(status===403)assert.deepEqual(await task,{eventDelivered:true,evidenceDiscarded:true});else await assert.rejects(task,/rejected/);assert.deepEqual(routes,['/api/device/events','/api/device/evidence']);assert.ok(f.record.protectedEvidence);}
});
test('device change, central disable and expired policy discard only evidence while preserving event',async()=>{
for(const change of [f=>f.context.deviceId='device-b',f=>f.context.policy.evidence.enabled=false,f=>f.context.policy.evidence.captureDownloads=false,f=>f.context.policy.expiresAt='2000-01-01T00:00:00Z']){const f=fixture(),routes=[];change(f);assert.deepEqual(await deliverAuditRecord({record:f.record,getContext:()=>f.context,send:async route=>{routes.push(route);return {};}}),{eventDelivered:true,evidenceDiscarded:true});assert.deepEqual(routes,['/api/device/events']);}
const f=fixture(),stripped=stripPendingEvidence([f.record]);assert.equal(stripped[0].eventId,f.record.eventId);assert.deepEqual(stripped[0].file,f.record.file);assert.equal('protectedEvidence' in stripped[0],false);assert.ok(f.record.protectedEvidence);
});
test('not-ready managed context pauses evidence after source event without losing tuple',async()=>{
const f=fixture(),routes=[];f.context.ready=false;await assert.rejects(deliverAuditRecord({record:f.record,getContext:()=>f.context,send:async route=>{routes.push(route);return {};}}),/paused/);assert.deepEqual(routes,['/api/device/events']);assert.ok(f.record.protectedEvidence);
});
test('invalid evidence acknowledgement and invalid payload never mark evidence delivered',async()=>{
for(const acknowledgement of [undefined,{}, {id:7,sourceEventId:1},{id:'id',sourceEventId:1.5},{id:'00000000-0000-4000-8000-000000000001',sourceEventId:0},{id:'',sourceEventId:1}]){const f=fixture();await assert.rejects(deliverAuditRecord({record:f.record,getContext:()=>f.context,send:async route=>route.endsWith('/evidence')?acknowledgement:{}}),/acknowledgement invalid/);}
const f=fixture(),routes=[];f.record.protectedEvidence.payload.secret='forbidden';await assert.rejects(deliverAuditRecord({record:f.record,getContext:()=>f.context,send:async route=>{routes.push(route);return {};}}),/Invalid protected/);assert.deepEqual(routes,['/api/device/events']);
});
test('collection requires enabled current ready context and validation returns independent clone',()=>{
const f=fixture();assert.equal(collectionAllowed(f.context),true);assert.equal(collectionAllowed({...f.context,ready:false}),false);assert.equal(collectionAllowed({...f.context,policy:{...f.context.policy,expiresAt:'invalid'}}),false);const clone=validatedEvidence(f.payload);clone.name='changed';assert.equal(f.payload.name,'report.pdf');
});