'use strict'; const fs=require('node:fs'); const {createHash}=require('node:crypto'); const {detectType,inspectFileTransfer}=require('../../packages/core/file-transfer.cjs'); const CHANNELS={upload:'scanUploads',download:'scanDownloads',clipboard:'scanClipboard',ai:'scanAI'}; function icapRequired(policy,channel){if(!Object.hasOwn(CHANNELS,channel))throw Error('Unsupported enterprise inspection channel');const settings=policy.enterprise?.icap;return !!settings?.enabled&&settings[CHANNELS[channel]]!==false;} function deploymentTLS(){ const options={};for(const [key,name] of [['ca','SAFETYNET_ICAP_CA_FILE'],['cert','SAFETYNET_ICAP_CERT_FILE'],['key','SAFETYNET_ICAP_KEY_FILE']]){if(process.env[name]){const stat=fs.statSync(process.env[name]);if(!stat.isFile()||stat.size>131072)throw Error('Enterprise scanner certificate configuration unavailable');options[key]=fs.readFileSync(process.env[name]);}} if(!!options.cert!==!!options.key)throw Error('Enterprise scanner client identity incomplete');return options; } async function scanEnterprise(bytes,policy,channel,scanner){ const digest=createHash('sha256').update(bytes).digest('hex'); const base={engine:'icap',sha256:digest,bytes:bytes.length,coverage:{complete:false},decision:'block',ruleId:'icap-unavailable'}; if(!icapRequired(policy,channel))return {...base,engine:'not-required',decision:'allow',ruleId:null,coverage:{complete:true}}; const snapshot=Buffer.from(bytes); try{ const result=await (scanner||require('./icap-client.cjs').scanIcap)(snapshot,policy.enterprise.icap,deploymentTLS()); if(result.decision==='allow'&&result.coverage?.complete===true&&result.sha256===digest&&result.bytes===snapshot.length&&createHash('sha256').update(snapshot).digest('hex')===digest)return {...base,...result}; return {...base,ruleId:typeof result.ruleId==='string'&&/^icap-[a-z0-9-]{1,80}$/.test(result.ruleId)?result.ruleId:'icap-inspection-incomplete'}; }catch{return base;} finally{snapshot.fill(0);} } async function scanDownloadEnterprise(bytes,policy,localInspector,scanner){ const digest=createHash('sha256').update(bytes).digest('hex'); const blocked=ruleId=>({decision:'block',ruleId,engine:'enterprise',sha256:digest,bytes:bytes.length,coverage:{complete:false}}); const fileVerdict=inspectFileTransfer(bytes,policy,'download');if(fileVerdict.decision!=='allow')return blocked(fileVerdict.ruleId); const noAv=!policy.security?.yara.enabled&&!icapRequired(policy,'download'); if(noAv&&!policy.fileTransfers?.download?.allowWithoutMalwareScanner)return blocked('malware-scanner-required'); // Antivirus clean does not classify company secrets. Supported documents always // require the contained DLP parser; explicit no-antivirus mode still requires DLP. if(noAv||(policy.fileTransfers?.download?.requireDocumentDlp!==false&&(policy.dlp?.semantic?.enabled||['pdf','zip','ole','png','jpeg','gif','webp','text'].includes(detectType(bytes))))){ if(!localInspector?.inspect)return blocked('download-dlp-unavailable'); let dlp;try{dlp=await localInspector.inspect(bytes,policy);}catch{return blocked('download-dlp-unavailable');} if(dlp.decision!=='allow')return blocked(typeof dlp.ruleId==='string'?dlp.ruleId:'download-dlp-blocked'); if(dlp.coverage?.complete!==true)return blocked('download-dlp-incomplete'); } if(noAv)return {decision:'allow',ruleId:null,engine:'explicit-no-antivirus',sha256:digest,bytes:bytes.length,coverage:{complete:true}}; if(policy.security?.yara.enabled){ if(!localInspector)return blocked('yara-unavailable'); let local;try{local=await localInspector.scanDownload(bytes,policy);}catch{return blocked('yara-unavailable');} if(local.decision!=='allow')return local; if(local.coverage?.complete!==true||local.sha256!==digest||local.bytes!==bytes.length)return blocked('yara-inspection-incomplete'); if(!icapRequired(policy,'download'))return local; } return scanEnterprise(bytes,policy,'download',scanner); } module.exports={icapRequired,scanEnterprise,scanDownloadEnterprise,deploymentTLS};