50 lines
2.8 KiB
JavaScript
50 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};
|