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

+34 -14
View File
@@ -1,20 +1,40 @@
const {app}=require('electron');
const {spawn}=require('node:child_process');
const path=require('node:path');
function capabilityCrypto(operation,input){
const root=path.join(__dirname,'../..');
const python=app.isPackaged?path.join(process.resourcesPath,'python/python.exe'):path.join(root,'.runtime/windows/python/python.exe');
const script=app.isPackaged?path.join(process.resourcesPath,'inspector/dpapi.py'):path.join(root,'apps/inspector/dpapi.py');
if(!['protect','unprotect'].includes(operation)||Buffer.byteLength(input)>32768)return Promise.reject(Error('Capability rejected'));
function createCapabilityCrypto({packaged=false,resourcesPath,root=path.join(__dirname,'../..'),launch=spawn,environment=process.env}={}){
const python=packaged?path.join(resourcesPath,'python/python.exe'):path.join(root,'.runtime/windows/python/python.exe');
const script=packaged?path.join(resourcesPath,'inspector/dpapi.py'):path.join(root,'apps/inspector/dpapi.py');
const active=new Set();
return function convert(operation,input){
const inputLimit=operation==='protect'?32768:65536;
if(!['protect','unprotect'].includes(operation)||!(typeof input==='string'||Buffer.isBuffer(input))||!Buffer.byteLength(input)||Buffer.byteLength(input)>inputLimit)return Promise.reject(Error('Capability rejected'));
if(active.size>=8)return Promise.reject(Error('Windows capability protection failed'));
return new Promise((resolve,reject)=>{
const environment={...process.env};for(const key of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','INSPECTOR_TOKEN','SIEM_HTTP_TOKEN','SAFETYNET_PROXY_PASSWORD'])delete environment[key];
const child=spawn(python,[script,operation],{windowsHide:true,stdio:['pipe','pipe','pipe'],env:environment});let output='',settled=false;
const timer=setTimeout(()=>{child.kill();fail();},5000);
function fail(){if(!settled){settled=true;clearTimeout(timer);reject(Error('Windows capability protection failed'));}}
child.on('error',fail);child.stdin.on('error',fail);child.stderr.on('data',()=>{});
child.stdout.on('data',data=>{output+=data;if(output.length>65536){child.kill();fail();}});
child.on('close',code=>{if(code!==0||!output)return fail();if(!settled){settled=true;clearTimeout(timer);resolve(output);}});
child.stdin.end(input);
// Python startup hooks and inherited secrets must not enter a capability
// helper. Isolated mode also disables user-site and Python environment paths.
const env={};for(const key of ['SystemRoot','WINDIR','TEMP','TMP','USERPROFILE','LOCALAPPDATA','APPDATA'])if(environment[key])env[key]=environment[key];
let child,timer,settled=false,terminal=false,bytes=0;
const output=Buffer.alloc(operation==='protect'?65536:32768),slot={};active.add(slot);
function fail(){
if(settled)return;settled=true;clearTimeout(timer);output.fill(0);
// A rejected operation does not free the slot until actual child close.
if(child&&!terminal){try{child.kill();}catch{}child.stdin?.destroy?.();child.stdout?.destroy?.();child.stderr?.destroy?.();}
reject(Error('Windows capability protection failed'));
}
try{
child=launch(python,['-I','-B',script,operation],{windowsHide:true,shell:false,stdio:['pipe','pipe','pipe'],env});
child.once('close',code=>{
terminal=true;active.delete(slot);
if(settled)return;if(code!==0||!bytes){fail();return;}
settled=true;clearTimeout(timer);const result=output.subarray(0,bytes).toString('utf8');output.fill(0);resolve(result);
});
child.on('error',fail);child.stdin.on('error',fail);child.stdout.on('error',fail);child.stderr.on('error',fail);child.stderr.on('data',()=>{});
child.stdout.on('data',data=>{if(settled)return;if(!Buffer.isBuffer(data)||bytes+data.length>output.length){fail();return;}data.copy(output,bytes);bytes+=data.length;});
timer=setTimeout(fail,5000);child.stdin.end(input);
}catch{if(!child)active.delete(slot);fail();}
});
};
}
module.exports={capabilityCrypto};
let sharedConverter;
function capabilityCrypto(operation,input){sharedConverter??=createCapabilityCrypto({packaged:app.isPackaged,resourcesPath:process.resourcesPath});return sharedConverter(operation,input);}
module.exports={capabilityCrypto,createCapabilityCrypto};