Files
safetynet/apps/browser/header-inspection.cjs
T

51 lines
2.8 KiB
JavaScript

'use strict';
// Final native request headers are scanned in privileged code before release.
// No values, names or credential fingerprints are returned as diagnostic facts.
const MAX_HEADERS=128,MAX_BYTES=65536;
function snapshotHeaders(input,policy){
if(!input||typeof input!=='object'||Array.isArray(input)||![Object.prototype,null].includes(Object.getPrototypeOf(input)))throw Error('headers-uninspectable');
const entries=Object.entries(Object.getOwnPropertyDescriptors(input));
if(entries.length>MAX_HEADERS)throw Error('headers-size-limit');
const headers=Object.create(null),values=[],lines=[];let bytes=0;
const limit=Math.min(MAX_BYTES,policy.dlp.maxBytes);
for(const [name,descriptor]of entries){
if(!('value'in descriptor)||!/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,256}$/.test(name)||typeof descriptor.value!=='string')throw Error('headers-uninspectable');
const value=descriptor.value;
if(value.length>limit)throw Error('headers-size-limit');
if(/[\x00-\x08\x0A-\x1F\x7F]|[\uD800-\uDFFF]/u.test(value))throw Error('headers-uninspectable');
bytes+=Buffer.byteLength(name)+Buffer.byteLength(value)+4;
if(bytes>limit)throw Error('headers-size-limit');
headers[name]=value;values.push(value);lines.push(`${name}: ${value}`);
}
return {headers:Object.freeze(headers),values,lines,bytes,count:entries.length};
}
async function inspectHeaders(snapshot,{policy,inspect,inspectLocal,inspectEnterprise,isCurrent}){
const blocked=ruleId=>({decision:'block',ruleId});
if(!isCurrent())return blocked('policy-changed');
// Individual values preserve exact registered fingerprints. The joined value
// view also catches a literal split across multiple distinct header fields.
for(const text of [...snapshot.values,snapshot.values.join(''),snapshot.lines.join('\r\n')]){
const bytes=Buffer.from(text);
try{const verdict=inspect(bytes,policy);if(verdict.decision!=='allow')return verdict;}
finally{bytes.fill(0);}
}
const bytes=Buffer.from(snapshot.lines.join('\r\n'));
try{
if(policy.dlp.semantic.enabled||policy.security.yara.enabled){
if(!inspectLocal)return blocked('inspector-unavailable');
const verdict=await inspectLocal(bytes,policy);
if(!isCurrent())return blocked('policy-changed');
if(verdict.decision!=='allow')return verdict;
if(verdict.coverage?.complete!==true)return blocked('inspection-incomplete');
}
if(inspectEnterprise){
const verdict=await inspectEnterprise(bytes,policy);
if(!isCurrent())return blocked('policy-changed');
if(verdict.decision!=='allow')return verdict;
if(verdict.coverage?.complete!==true)return blocked('inspection-incomplete');
}
return isCurrent()?{decision:'allow',ruleId:null}:blocked('policy-changed');
}finally{bytes.fill(0);}
}
module.exports={snapshotHeaders,inspectHeaders,MAX_HEADERS,MAX_BYTES};