Add packaged DLP broker and protected Windows service foundation

This commit is contained in:
Karl committed 2026-10-14 14:19:22 +02:00
1 parent 95d23f4e0b
commit 7b5057901a
140 files changed
+11215 -60

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
'use strict';
const {createHash}=require('node:crypto');
const {detectType}=require('./file-transfer.cjs');
// Typed binary carriers supplement text normalization. No whole decoded file
// may be released on the strength of an allow verdict for its outer JSON text.
function extractEncodedDocuments(bytes,{maxDecodedBytes=16777216,maxParts=16,maxNodes=20000,maxDepth=32,jsonRequired=false}={}){
if(!Buffer.isBuffer(bytes)||bytes.length>16777216||![maxDecodedBytes,maxParts,maxNodes,maxDepth].every(Number.isSafeInteger)||maxDecodedBytes<1||maxDecodedBytes>16777216||maxParts<1||maxParts>64||maxNodes<1||maxNodes>20000||maxDepth<1||maxDepth>32)throw Error('encoded-document-configuration-invalid');
if(typeof jsonRequired!=='boolean')throw Error('encoded-document-configuration-invalid');
const parts=[],seen=new Set(),encodings=new Set();let total=0,nodes=0,complete=true;
const result=()=>Object.freeze({parts:Object.freeze(parts),complete,decodedBytes:total,visitedNodes:nodes,dispose});
const dispose=()=>{for(const part of parts)part.bytes.fill(0);};
function candidate(value){
if(!complete||typeof value!=='string')return;
let encoded=value.trim(),explicit=false,mime;
if(/^data:/i.test(encoded)){
const match=/^data:([a-z0-9.+/-]+)?(?:;[a-z0-9-]+=[a-z0-9._-]+)*;base64,([\s\S]*)$/i.exec(encoded);
if(!match){complete=false;return;}mime=(match[1]||'application/octet-stream').toLowerCase();encoded=match[2];explicit=true;
if(encoded.includes('%')){try{encoded=decodeURIComponent(encoded);}catch{complete=false;return;}}
}
if(!explicit&&encoded.length<16)return;
if(!/^[A-Za-z0-9+/_-]*={0,2}$/.test(encoded)||encoded.length%4===1){if(explicit)complete=false;return;}
const normalized=encoded.replace(/-/g,'+').replace(/_/g,'/').replace(/=+$/,'');
const encodingKey=createHash('sha256').update(normalized).update('\0'+(mime||'')).digest('hex');
if(encodings.has(encodingKey))return;
const projected=Math.floor(normalized.length*3/4);
if(total+projected>maxDecodedBytes){complete=false;return;}
const decoded=Buffer.from(normalized,'base64');
total+=decoded.length;encodings.add(encodingKey);
const type=detectType(decoded);
if(decoded.toString('base64').replace(/=+$/,'')!==normalized){decoded.fill(0);if(explicit||type!=='unknown')complete=false;return;}
if(!explicit&&type==='unknown'){decoded.fill(0);return;}
const digest=createHash('sha256').update(decoded).digest('hex');
const identity=digest+'\0'+(mime||'');
if(seen.has(identity)){decoded.fill(0);return;}
if(parts.length>=maxParts){decoded.fill(0);complete=false;return;}
seen.add(identity);parts.push(Object.freeze({bytes:decoded,...(mime?{mime}:{})}));
}
function scanText(value,percentDepth=0){
candidate(value);
const carriers=[];
for(const match of value.matchAll(/data:[^\s,"'<>]+,[A-Za-z0-9+/_=%-]+/gi)){if(++nodes>maxNodes){complete=false;break;}carriers.push([match.index,match.index+match[0].length]);candidate(match[0]);}
let carrier=0;
for(const match of value.matchAll(/[A-Za-z0-9+/_-]{16,}={0,2}/g)){
while(carrier<carriers.length&&carriers[carrier][1]<=match.index)carrier++;
if(carrier<carriers.length&&match.index>=carriers[carrier][0]&&match.index<carriers[carrier][1])continue;
if(++nodes>maxNodes){complete=false;break;}candidate(match[0]);
}
if(complete&&value.includes('%')){
if(percentDepth>=4){complete=false;return;}
try{const decoded=decodeURIComponent(value);if(decoded!==value)scanText(decoded,percentDepth+1);}catch{if(/data%3a/i.test(value))complete=false;}
}
}
try{
const text=new TextDecoder('utf-8',{fatal:true}).decode(bytes),trimmed=text.trim();
if(jsonRequired||trimmed.startsWith('{')||trimmed.startsWith('[')){
let root;try{root=JSON.parse(trimmed);}catch{if(jsonRequired)complete=false;else scanText(trimmed);return result();}
const stack=[{value:root,depth:0}];
while(stack.length&&complete){const {value,depth}=stack.pop();if(++nodes>maxNodes||depth>maxDepth){complete=false;break;}
if(typeof value==='string')scanText(value);
else if(Array.isArray(value)){
if(value.length>maxNodes-nodes||stack.length+value.length>maxNodes){complete=false;break;}
for(const child of value)stack.push({value:child,depth:depth+1});
}else if(value&&typeof value==='object'){
// Avoid Object.entries allocating an unbounded array of key/value pairs
// before the traversal budget is checked on a hostile JSON collection.
for(const key in value){if(!Object.hasOwn(value,key))continue;scanText(key);stack.push({value:value[key],depth:depth+1});if(stack.length>maxNodes||!complete){complete=false;break;}}
}
}
}else{
scanText(trimmed);
}
}catch{complete=false;}
return result();
}
module.exports={extractEncodedDocuments};