first commit

This commit is contained in:
Karl committed 2026-10-13 10:34:48 +02:00
commit ff482ebe23
251 files changed
+19126

No files matched your search

+45
View File
@@ -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};