53 lines
4.8 KiB
JavaScript
53 lines
4.8 KiB
JavaScript
import test from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import {createRequire} from 'node:module';
|
||
|
|
import {createHash} from 'node:crypto';
|
||
|
|
import {defaultPolicy,inspect} from '../packages/core/policy.mjs';
|
||
|
|
const {snapshotHeaders,inspectHeaders}=createRequire(import.meta.url)('../apps/browser/header-inspection.cjs');
|
||
|
|
function policy(){const p=defaultPolicy();p.dlp.rules=[{id:'roadmap',label:'Roadmap',kind:'literal',value:'Secret Roadmap',action:'block'}];return p;}
|
||
|
|
async function check(headers,p=policy(),overrides={}){return inspectHeaders(snapshotHeaders(headers,p),{policy:p,inspect,isCurrent:()=>true,...overrides});}
|
||
|
|
test('custom, credential and browser metadata header values obey the same central DLP',async()=>{
|
||
|
|
for(const name of ['X-Data','Cookie','Authorization','Proxy-Authorization','Referer','User-Agent'])assert.equal((await check({[name]:'Secret Roadmap'})).ruleId,'roadmap');
|
||
|
|
assert.equal((await check({'X-Data':'Secret%20Roadmap'})).ruleId,'roadmap');
|
||
|
|
assert.equal((await check({'X-First':'Secret ','X-Second':'Roadmap'})).ruleId,'roadmap');
|
||
|
|
assert.equal((await check({'Secret-Roadmap':'public'})).decision,'allow');
|
||
|
|
assert.equal((await check({Authorization:'Bearer sk-123456789012345678901234'})).ruleId,'api-secret');
|
||
|
|
assert.equal((await check({Authorization:'Bearer eyJhbGciOiJIUzI1NiJ9.opaque.signature',Cookie:'session=ordinaryOpaqueValue'})).decision,'allow');
|
||
|
|
});
|
||
|
|
test('per-header exact hashes and registered content phrases remain effective',async()=>{
|
||
|
|
const p=policy();p.dlp.rules=[{id:'reference',label:'Reference',kind:'sha256',value:createHash('sha256').update('known exact reference').digest('hex'),action:'block'}];
|
||
|
|
assert.equal((await check({'X-Data':'known exact reference'},p)).ruleId,'reference');
|
||
|
|
p.security.content.blockedPhrases=['Internal planning'];
|
||
|
|
assert.equal((await check({Cookie:'x=Internal+planning'},p)).ruleId,'content-blocklist');
|
||
|
|
});
|
||
|
|
test('snapshot rejects malformed, inherited, accessor and oversized input without executing getters',()=>{
|
||
|
|
const p=policy();let getterCalled=false;const input={};Object.defineProperty(input,'X',{enumerable:true,get(){getterCalled=true;return 'public';}});
|
||
|
|
assert.throws(()=>snapshotHeaders(input,p),/uninspectable/);assert.equal(getterCalled,false);
|
||
|
|
for(const malformed of [null,[],Object.create({X:'secret'}),{'Bad Header':'public'},{X:['public']},{X:'bad\r\nheader'},{X:'bad\0header'}])assert.throws(()=>snapshotHeaders(malformed,p),/uninspectable/);
|
||
|
|
assert.throws(()=>snapshotHeaders(Object.fromEntries(Array.from({length:129},(_,i)=>[`X-${i}`,'ok'])),p),/size-limit/);
|
||
|
|
assert.throws(()=>snapshotHeaders({X:'a'.repeat(65536)},p),/size-limit/);
|
||
|
|
p.dlp.maxBytes=1024;assert.throws(()=>snapshotHeaders({X:'a'.repeat(1024)},p),/size-limit/);
|
||
|
|
});
|
||
|
|
test('approved snapshot cannot be changed by input mutations or scanners; buffers wiped',async()=>{
|
||
|
|
const p=policy(),input={Cookie:'ordinary=session',X:'public'},snapshot=snapshotHeaders(input,p);input.X='Secret Roadmap';
|
||
|
|
let held;const result=await inspectHeaders(snapshot,{policy:p,inspect,isCurrent:()=>true,inspectEnterprise:async bytes=>{held=bytes;return {decision:'allow',coverage:{complete:true}};}});
|
||
|
|
assert.equal(result.decision,'allow');assert.equal(snapshot.headers.X,'public');assert.throws(()=>{snapshot.headers.X='Secret Roadmap';},TypeError);assert.ok(held.every(byte=>byte===0));
|
||
|
|
assert.deepEqual(Object.keys(result).sort(),['decision','ruleId']);
|
||
|
|
});
|
||
|
|
test('local and enterprise inspection require complete allow and current policy',async()=>{
|
||
|
|
const p=policy();p.dlp.semantic.enabled=true;
|
||
|
|
assert.equal((await check({X:'public'},p)).ruleId,'inspector-unavailable');
|
||
|
|
assert.equal((await check({X:'public'},p,{inspectLocal:async()=>({decision:'allow'})})).ruleId,'inspection-incomplete');
|
||
|
|
let enterpriseCalls=0;
|
||
|
|
assert.equal((await check({X:'public'},p,{inspectLocal:async()=>({decision:'block',ruleId:'semantic-secret'}),inspectEnterprise:async()=>{enterpriseCalls++;}})).ruleId,'semantic-secret');assert.equal(enterpriseCalls,0);
|
||
|
|
let current=true;
|
||
|
|
assert.equal((await check({X:'public'},p,{isCurrent:()=>current,inspectLocal:async()=>{current=false;return {decision:'allow',coverage:{complete:true}};}})).ruleId,'policy-changed');
|
||
|
|
p.dlp.semantic.enabled=false;
|
||
|
|
assert.equal((await check({X:'public'},p,{inspectEnterprise:async()=>({decision:'allow'})})).ruleId,'inspection-incomplete');
|
||
|
|
assert.equal((await check({X:'public'},p,{inspectEnterprise:async()=>({decision:'block',ruleId:'icap-malware'})})).ruleId,'icap-malware');
|
||
|
|
});
|
||
|
|
test('exceptions wipe full-header scan buffers and do not turn failure into allow',async()=>{
|
||
|
|
let held;
|
||
|
|
await assert.rejects(check({Cookie:'ordinary=session'},policy(),{inspectEnterprise:async bytes=>{held=bytes;throw Error('fixture scanner unavailable');}}),/unavailable/);
|
||
|
|
assert.ok(held.every(byte=>byte===0));
|
||
|
|
});
|