Implement managed browser security, individual agents and observability

This commit is contained in:
Karl committed 2026-10-13 16:58:15 +02:00
1 parent ff482ebe23
commit ea448de2d4
172 files changed
+5404 -347

No files matched your search

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