first commit
This commit is contained in:
commit
ff482ebe23
251 files changed
+19126
No files matched your search
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
const {safeFilename:filename,inspectFileTransfer}=require('../../packages/core/file-transfer.cjs');
|
||||
const {createHash}=require('node:crypto');
|
||||
function createDownloadGate({getContext,isCurrent,fetchResponse,scan,choosePath,writeFile,audit,status}){
|
||||
let busy=false;
|
||||
return async function download({url,name,method,webContentsId}){
|
||||
const context=getContext(webContentsId);let bytes,facts;
|
||||
const auditFacts=()=>facts?{bytes:facts.size,detectedType:facts.detectedType,sha256:facts.sha256}:{};
|
||||
const deny=async reason=>{await audit('download.block',reason,new URL(url).hostname,auditFacts());status?.(`Download blocked: ${reason}`);return {released:false,reason};};
|
||||
if(busy)return deny('download-busy');
|
||||
if(!context||context.webContentsId!==webContentsId)return deny('unmanaged-download');
|
||||
if(!context.ready||!context.policy?.browser.allowDownloads)return deny('downloads-disabled');
|
||||
const yara=context.policy.security?.yara,icap=context.policy.enterprise?.icap;
|
||||
const hasYara=!!yara?.enabled&&!!yara.rules.length,hasIcap=!!icap?.enabled&&icap.scanDownloads!==false;
|
||||
if(yara?.enabled&&!hasYara)return deny('yara-required');
|
||||
if(!hasYara&&!hasIcap&&!context.policy.fileTransfers?.download?.allowWithoutMalwareScanner)return deny('malware-scanner-required');
|
||||
if(method!=='GET'||!/^https?:\/\//.test(url))return deny('unsupported-download');
|
||||
if(!isCurrent(context))return deny('policy-changed');
|
||||
busy=true;const controller=new AbortController(),timeout=setTimeout(()=>controller.abort(),45000);
|
||||
try{
|
||||
const response=await fetchResponse(url,{signal:controller.signal,cache:'no-store',credentials:'include',redirect:'error'});
|
||||
if(!response.ok||!response.body)return deny('download-fetch-failed');
|
||||
const limit=Math.min(hasYara?yara.maxScanBytes:20*1024*1024,hasIcap?icap.maxBytes:20*1024*1024,context.policy.fileTransfers?.download?.maxBytes??20*1024*1024);
|
||||
const length=response.headers.get('content-length');if(length&&Number(length)>limit)return deny('download-size-limit');
|
||||
const reader=response.body.getReader(),chunks=[];let size=0;
|
||||
try{for(;;){const {value,done}=await reader.read();if(done)break;size+=value.length;if(size>limit){await reader.cancel();return deny('download-size-limit');}chunks.push(Buffer.from(value));}}
|
||||
finally{reader.releaseLock();}
|
||||
bytes=Buffer.concat(chunks);const digest=createHash('sha256').update(bytes).digest('hex');
|
||||
if(!isCurrent(context)||controller.signal.aborted)return deny('policy-changed');
|
||||
facts=inspectFileTransfer(bytes,context.policy,'download',{filename:name,mime:response.headers.get('content-type')});if(facts.decision!=='allow')return deny(facts.ruleId);
|
||||
const result=await scan(bytes,context.policy);
|
||||
if(result.decision!=='allow'||result.coverage?.complete!==true||result.sha256!==digest)return deny(result.ruleId||'yara-scan-incomplete');
|
||||
if(!isCurrent(context)||controller.signal.aborted)return deny('policy-changed');
|
||||
const scanLabel=hasYara&&hasIcap?'yara-icap-allow':hasYara?'yara-allow':hasIcap?'icap-allow':'explicit-no-antivirus';
|
||||
await audit('download.scan',scanLabel,new URL(url).hostname,auditFacts());
|
||||
const selected=await choosePath(filename(name));if(!selected)return {released:false,reason:'user-cancelled'};
|
||||
if(!isCurrent(context)||controller.signal.aborted)return deny('policy-changed');
|
||||
// The only file-writing operation occurs after verified scanning of these exact bytes.
|
||||
await writeFile(selected,bytes,()=>isCurrent(context)&&!controller.signal.aborted);
|
||||
await audit('download.released',scanLabel,new URL(url).hostname,auditFacts());status?.('Download scanned and saved.');return {released:true,sha256:digest,facts};
|
||||
}catch{return deny('download-inspection-failed');}
|
||||
finally{clearTimeout(timeout);bytes?.fill(0);busy=false;}
|
||||
};
|
||||
}
|
||||
module.exports={createDownloadGate,filename};
|
||||
Reference in new issue
Block a user