68 lines
9.2 KiB
JavaScript
68 lines
9.2 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {createRequire} from 'node:module';
|
|
import {defaultPolicy} from '../packages/core/policy.mjs';
|
|
const require=createRequire(import.meta.url),{createDownloadResponseGate}=require('../apps/network-broker/download-response-gate.cjs');
|
|
const input={request:{url:'https://intranet.example/document.pdf'},body:Buffer.alloc(0)};
|
|
const response=(body,headers={},statusCode=200)=>({statusCode,headers,body:Buffer.from(body)});
|
|
test('incoming confidential HTML/API is not outbound DLP; magic document requires actual scanner',async()=>{
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;let calls=0;
|
|
const page=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>response('company internal text',{'content-type':'text/html'}),inspector:{inspect:async()=>{calls++;assert.fail('incoming page not outgoingDLP');}}});
|
|
assert.equal((await page(input)).body.toString(),'company internal text');assert.equal(calls,0);
|
|
const document=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>response('%PDF-1.4 public',{'content-type':'application/pdf'})});await assert.rejects(document(input),{code:'download-inspection-blocked'});
|
|
});
|
|
test('signed explicit no-malware-scanner still requires complete document parsing and preserves disposition',async()=>{
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;policy.fileTransfers.download.allowWithoutMalwareScanner=true;
|
|
let calls=0;const gate=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>response('%PDF-1.4 public',{'content-type':'application/pdf','content-disposition':'inline; filename="public.pdf"'}),inspector:{inspect:async()=>{calls++;return {decision:'allow',coverage:{complete:true}};}}});
|
|
const value=await gate(input);assert.equal(calls,1);assert.equal(value.headers['content-disposition'],'inline; filename="public.pdf"');assert.equal(value.body.toString(),'%PDF-1.4 public');
|
|
});
|
|
test('malware/DLP block, incomplete coverage, scanner mutation and revocation release no response',async()=>{
|
|
for(const inspect of [async()=>({decision:'block',ruleId:'registered'}),async()=>({decision:'allow',coverage:{complete:false}}),async bytes=>{bytes.fill(0);return {decision:'allow',coverage:{complete:true}};}]){
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;policy.fileTransfers.download.allowWithoutMalwareScanner=true;
|
|
const gate=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>response('%PDF-1.4 public'),inspector:{inspect}});await assert.rejects(gate(input),{code:'download-inspection-blocked'});
|
|
}
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;policy.fileTransfers.download.allowWithoutMalwareScanner=true;let current=true;
|
|
const gate=createDownloadResponseGate({auditRequired:false,policy,current:()=>current,forward:async()=>response('%PDF-1.4 public'),inspector:{inspect:async()=>{current=false;return {decision:'allow',coverage:{complete:true}};}}});await assert.rejects(gate(input),{code:'authority-expired'});
|
|
});
|
|
test('magic mismatch, partial206 and compression cannot bypass full download inspection',async()=>{
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;policy.fileTransfers.download.blockMimeMismatch=true;
|
|
for(const record of [response('%PDF-1.4 public',{'content-type':'text/plain'}),response('%PDF-1.4 public',{},206),response('%PDF-1.4 public',{'content-encoding':'gzip'})]){
|
|
const gate=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>record,inspector:{inspect:async()=>assert.fail('invalidrepresentation must not scan')}});await assert.rejects(gate(input));
|
|
}
|
|
});
|
|
import {randomUUID,createHash} from 'node:crypto';
|
|
import {createSyntheticAudit} from './fixtures/broker-durable-audit.cjs';
|
|
const {createBrokerAudit}=require('../apps/network-broker/audit-gate.cjs'),{createBrokerLifecycle}=require('../apps/network-broker/lifecycle-audit.cjs');
|
|
test('download verdict commits typed immutable real-file facts and registered detector before release',async t=>{
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;policy.fileTransfers.download.allowWithoutMalwareScanner=true;policy.dlp.rules=[{id:'registered-secret',label:'Synthetic',kind:'literal',value:'synthetic',action:'block'}];
|
|
const journal=createSyntheticAudit(t),audit=createBrokerAudit({persist:journal.persist,current:()=>true,policyVersion:1,allowedRuleIds:['registered-secret']}),context=createBrokerLifecycle(audit).begin();await context.decision('allow',{bytes:0,detectedType:'text'});
|
|
const body=Buffer.from('%PDF-1.4 encodedsynthetic'),gate=createDownloadResponseGate({policy,current:()=>true,forward:async()=>response(body),inspector:{inspect:async()=>({decision:'block',ruleId:'registered-secret',coverage:{complete:true}})}});
|
|
await assert.rejects(gate({...input,auditContext:context}),{code:'download-inspection-blocked'});await context.reject('upstream-failed',{bytes:0,detectedType:'text'});
|
|
const events=journal.events();assert.equal(events.length,2);assert.equal(events[1].kind,'download.block');assert.equal(events[1].ruleId,'registered-secret');assert.equal(events[1].broker.phase,'response.block');assert.equal(events[1].broker.authorizedEventId,events[0].eventId);assert.equal(events[1].file.sha256,createHash('sha256').update(body).digest('hex'));assert.equal(events[1].request,undefined);
|
|
});
|
|
test('pending or failed mandatory download commit cannot return document bytes',async()=>{
|
|
const policy=defaultPolicy();policy.browser.allowDownloads=true;policy.fileTransfers.download.allowWithoutMalwareScanner=true;let release,entered;const hold=new Promise(resolve=>release=resolve),started=new Promise(resolve=>entered=resolve);let completed=false;
|
|
const audit=createBrokerAudit({current:()=>true,policyVersion:1,persist:async event=>{if(event.kind==='download.scan'){entered();await hold;}return {persisted:true,eventId:event.eventId};}}),context=createBrokerLifecycle(audit).begin();await context.decision('allow',{bytes:0,detectedType:'text'});
|
|
const gate=createDownloadResponseGate({policy,current:()=>true,forward:async()=>response('%PDF-1.4 public'),inspector:{inspect:async()=>({decision:'allow',coverage:{complete:true}})}});
|
|
const pending=gate({...input,auditContext:context}).then(()=>completed=true);await started;assert.equal(completed,false);release();await pending;assert.equal(completed,true);
|
|
const fail=createBrokerAudit({current:()=>true,policyVersion:1,persist:async event=>{if(event.kind==='download.scan')throw Error('private journal detail');return {persisted:true,eventId:event.eventId};}}),denied=createBrokerLifecycle(fail).begin();await denied.decision('allow',{bytes:0,detectedType:'text'});await assert.rejects(gate({...input,auditContext:denied}),{code:'audit-persistence-failed'});
|
|
const missing=createDownloadResponseGate({policy,current:()=>true,forward:async()=>assert.fail('no unaudited dial')});await assert.rejects(missing(input),{code:'audit-unavailable'});
|
|
});
|
|
test('download causation refuses missing authorization and unknown matched detector',async()=>{
|
|
const audit=createBrokerAudit({current:()=>true,policyVersion:1,persist:async()=>assert.fail('invalid facts never persist')});
|
|
for(const broker of [{requestId:randomUUID(),phase:'response.block',reason:'broker-download-inspection-block'},{requestId:randomUUID(),phase:'response.scanned',reason:'broker-download-approved',authorizedEventId:randomUUID()}])await assert.rejects(audit.recordDownload('block',{bytes:3,detectedType:'pdf',sha256:'a'.repeat(64),broker}),{code:'audit-facts-invalid'});
|
|
});
|
|
|
|
test('OS save prohibition does not bypass or deny complete inline asset scans',async()=>{
|
|
const policy=defaultPolicy();policy.fileTransfers.download.allowWithoutMalwareScanner=true;const bytes=Buffer.from('89504e470d0a1a0a','hex');let scans=0;
|
|
const gate=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>response(bytes,{'content-type':'image/png'}),inspector:{inspect:async()=>{scans++;return {decision:'allow',coverage:{complete:true}};}}});
|
|
assert.deepEqual((await gate({...input,request:{url:'https://intranet.example/icon.png'}})).body,bytes);assert.equal(scans,1);
|
|
const attachment=createDownloadResponseGate({auditRequired:false,policy,current:()=>true,forward:async()=>response(bytes,{'content-type':'image/png','content-disposition':'attachment; filename=icon.png'}),inspector:{inspect:async()=>assert.fail('policy denies before scan')}});await assert.rejects(attachment(input),{code:'download-policy-blocked'});
|
|
});
|
|
|
|
test('late original classifier cannot commit another decision after a terminal scan timeout',async t=>{
|
|
const journal=createSyntheticAudit(t),audit=createBrokerAudit({persist:journal.persist,current:()=>true,policyVersion:1}),context=createBrokerLifecycle(audit).begin();
|
|
await context.reject('inspection-deadline',{bytes:6,detectedType:'text'});assert.throws(()=>context.decision('allow',{bytes:6,detectedType:'text'}),{code:'aborted'});assert.throws(()=>context.response('allow',{bytes:6,detectedType:'pdf',sha256:'a'.repeat(64)}),{code:'aborted'});
|
|
const events=journal.events();assert.equal(events.length,1);assert.equal(events[0].broker.reason,'broker-scan-timeout');
|
|
});
|