Implement managed browser security, individual agents and observability
This commit is contained in:
1 parent
ff482ebe23
commit
ea448de2d4
172 files changed
+5404
-347
No files matched your search
@@ -1,12 +1,14 @@
|
||||
'use strict';
|
||||
const {safeFilename:filename,inspectFileTransfer}=require('../../packages/core/file-transfer.cjs');
|
||||
const {createHash}=require('node:crypto');
|
||||
const {buildDownloadEvidence}=require('./evidence-capture.cjs');
|
||||
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};};
|
||||
const context=getContext(webContentsId);let bytes,facts,mime;
|
||||
const auditFacts=()=>facts?{bytes:facts.size,detectedType:facts.detectedType,sha256:facts.sha256}:undefined;
|
||||
const evidence=(decision,ruleId)=>context?.ready&&isCurrent(context)?buildDownloadEvidence({url,name,mime,facts,decision,ruleId},context.policy):null;
|
||||
const deny=async reason=>{await audit('download.block',reason,new URL(url).hostname,auditFacts(),evidence('block',reason));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');
|
||||
@@ -27,7 +29,7 @@ function createDownloadGate({getContext,isCurrent,fetchResponse,scan,choosePath,
|
||||
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);
|
||||
mime=response.headers.get('content-type');facts=inspectFileTransfer(bytes,context.policy,'download',{filename:name,mime});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');
|
||||
@@ -37,7 +39,7 @@ function createDownloadGate({getContext,isCurrent,fetchResponse,scan,choosePath,
|
||||
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};
|
||||
await audit('download.released',scanLabel,new URL(url).hostname,auditFacts(),evidence('allow',scanLabel));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;}
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user