Add packaged DLP broker and protected Windows service foundation
This commit is contained in:
1 parent
95d23f4e0b
commit
7b5057901a
140 files changed
+11215
-60
No files matched your search
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
const {createHash}=require('node:crypto');
|
||||
const {GateError}=require('./http-request-gate.cjs');
|
||||
const {validateResponse}=require('./http-response-gate.cjs');
|
||||
const {detectType,inspectFileTransfer}=require('../../packages/core/file-transfer.cjs');
|
||||
const {scanDownloadEnterprise}=require('../browser/enterprise-inspection.cjs');
|
||||
const digest=bytes=>createHash('sha256').update(bytes).digest('hex');
|
||||
function filename(value,url){
|
||||
if(value===undefined){try{return decodeURIComponent(new URL(url).pathname.split('/').pop());}catch{throw new GateError('invalid-upstream-response');}}
|
||||
if(typeof value!=='string'||value.length>8192||!/^\s*(attachment|inline)(?:\s*;|\s*$)/i.test(value))throw new GateError('invalid-upstream-response');
|
||||
const matches=[...value.matchAll(/;\s*filename(\*)?\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;\s]+))/gi)];
|
||||
if(matches.length>1)throw new GateError('invalid-upstream-response');if(!matches.length)return '';
|
||||
const match=matches[0],text=match[2]!==undefined?match[2].replace(/\\(.)/g,'$1'):match[3];
|
||||
if(match[1]){if(!/^UTF-8''/i.test(text))throw new GateError('invalid-upstream-response');try{return decodeURIComponent(text.slice(7));}catch{throw new GateError('invalid-upstream-response');}}return text;
|
||||
}
|
||||
/** A full buffered response is held until document/download analysis succeeds.
|
||||
* Incoming HTML/API text is not classified as outgoing company data. Partial
|
||||
* and compressed representations are unsupported, never counted as covered.
|
||||
*/
|
||||
function createDownloadResponseGate({forward,policy,current,inspector,enterpriseScanner,maxBytes=16777216,auditRequired=true}={}){
|
||||
if(typeof auditRequired!=='boolean'||typeof forward!=='function'||typeof current!=='function'||!policy||!Number.isSafeInteger(maxBytes)||maxBytes<0||maxBytes>16777216)throw new GateError('invalid-configuration');
|
||||
const check=signal=>{if(signal?.aborted)throw new GateError('aborted');if(current()!==true)throw new GateError('authority-expired');};
|
||||
return async input=>{
|
||||
check(input.signal);if(auditRequired&&typeof input.auditContext?.response!=='function')throw new GateError('audit-unavailable');const upstream=await forward(input);check(input.signal);
|
||||
const response=validateResponse(upstream,maxBytes);let scanCopy;
|
||||
const known=new Set([...(policy.dlp?.rules||[]).map(rule=>rule.id),...(policy.dlp?.semantic?.categories||[]).map(rule=>rule.id),...(policy.security?.yara?.rules||[]).map(rule=>rule.id)]);
|
||||
const record=async(decision,reason,ruleId)=>{if(input.auditContext?.response)await input.auditContext.response(decision,{bytes:response.body.length,detectedType:detectType(response.body),sha256:digest(response.body),host:new URL(input.request.url).hostname.replace(/^\[|\]$/g,''),reason,...(known.has(ruleId)?{matchedRuleId:ruleId}:{})});else if(auditRequired)throw new GateError('audit-unavailable');check(input.signal);};
|
||||
try{
|
||||
if(response.statusCode===206)throw new GateError('invalid-upstream-response');
|
||||
const disposition=response.headers['content-disposition'],type=detectType(response.body),mime=response.headers['content-type'];
|
||||
const claimed=filename(disposition,input.request.url);
|
||||
const document=type!=='text'||disposition!==undefined||/^(?:application\/(?:pdf|zip|octet-stream|msword|vnd\.)|image\/)/i.test(mime||'');
|
||||
if(!document)return response;
|
||||
if(/^image\/svg\+xml(?:;|$)/i.test(mime||'')){await record('block','broker-download-inspection-block');throw new GateError('download-inspection-blocked');}
|
||||
if(/^\s*attachment(?:\s*;|\s*$)/i.test(disposition||'')&&policy.browser.allowDownloads!==true){await record('block','broker-download-policy-block');throw new GateError('download-policy-blocked');}
|
||||
const verdict=inspectFileTransfer(response.body,policy,'download',{filename:claimed,mime});if(verdict.decision!=='allow'){await record('block','broker-download-policy-block',verdict.ruleId);throw new GateError('download-policy-blocked');}
|
||||
input.markStage?.('scan');scanCopy=Buffer.from(response.body);const originalDigest=digest(scanCopy);
|
||||
const result=await scanDownloadEnterprise(scanCopy,policy,inspector,enterpriseScanner);check(input.signal);
|
||||
if(digest(scanCopy)!==originalDigest||result?.decision!=='allow'||result.coverage?.complete!==true||result.sha256!==originalDigest||result.bytes!==scanCopy.length){await record('block','broker-download-inspection-block',digest(scanCopy)===originalDigest?result?.ruleId:undefined);throw new GateError('download-inspection-blocked');}
|
||||
input.markStage?.('forward');await record('allow','broker-download-approved');
|
||||
return response;
|
||||
}catch(error){response.body.fill(0);if(error instanceof GateError)throw error;throw new GateError('download-inspection-blocked');}
|
||||
finally{scanCopy?.fill(0);}
|
||||
};
|
||||
}
|
||||
module.exports={createDownloadResponseGate};
|
||||
Reference in new issue
Block a user