Files
safetynet/tests/header-inspection.test.mjs

54 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));
});