Add packaged DLP broker and protected Windows service foundation
This commit is contained in:
1 parent
95d23f4e0b
commit
7b5057901a
140 files changed
+11215
-60
No files matched your search
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
const {app,session}=require('electron');
|
||||
const {createBrokerCertificatePins}=require('../../apps/browser/broker-certificate-pins.cjs');
|
||||
app.disableHardwareAcceleration();
|
||||
let initialized=false;
|
||||
process.once('disconnect',()=>app.exit(0));
|
||||
process.on('message',async input=>{
|
||||
if(input?.type==='stop'){app.exit(0);return;}
|
||||
if(initialized||input?.type!=='start')return;initialized=true;
|
||||
try{
|
||||
app.setPath('userData',input.profile);await app.whenReady();
|
||||
const partition=session.fromPartition('certificate-pin-native',{cache:false});
|
||||
const pins=createBrokerCertificatePins({generation:'synthetic-generation',policyVersion:4,current:()=>true,authorizedAuthority:authority=>authority===input.leaf.authority});
|
||||
pins.accept({...input.leaf,generation:'synthetic-generation',policyVersion:4});
|
||||
partition.setCertificateVerifyProc((request,callback)=>pins.verify(request,callback));
|
||||
await partition.setProxy({mode:'direct'});
|
||||
const allowed=await partition.fetch(input.publicURL,{signal:AbortSignal.timeout(10000)});
|
||||
const publicBody=await allowed.text();let denied=false;
|
||||
try{await partition.fetch(input.untrustedURL,{signal:AbortSignal.timeout(10000)});}catch{denied=true;}
|
||||
await partition.closeAllConnections();pins.revoke();
|
||||
process.send({type:'proof',publicStatus:allowed.status,publicBody,untrustedRejected:denied,electron:process.versions.electron,chromium:process.versions.chrome});
|
||||
}catch{process.send({type:'fault',reason:'certificate-native-proof-failed'});}
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
// Test-only, actual fsync of synthetic metadata. Production uses the protected
|
||||
// encrypted audit store; this fixture never stores request contents or tokens.
|
||||
const fs=require('node:fs');
|
||||
const path=require('node:path');
|
||||
const {randomUUID}=require('node:crypto');
|
||||
const {BrokerAuditEventSchema}=require('../../apps/network-broker/audit-gate.cjs');
|
||||
function createSyntheticAudit(test){
|
||||
const root=path.resolve(__dirname,'../../artifacts/broker-audit-fixtures');fs.mkdirSync(root,{recursive:true});
|
||||
const filename=path.join(root,`${randomUUID()}.jsonl`),fd=fs.openSync(filename,'wx');
|
||||
test.after(()=>fs.closeSync(fd));
|
||||
return Object.freeze({
|
||||
persist:event=>{BrokerAuditEventSchema.parse(event);fs.writeFileSync(fd,JSON.stringify(event)+'\n');fs.fsyncSync(fd);return {persisted:true,eventId:event.eventId};},
|
||||
events:()=>fs.readFileSync(filename,'utf8').trim().split('\n').filter(Boolean).map(line=>JSON.parse(line))
|
||||
});
|
||||
}
|
||||
module.exports={createSyntheticAudit};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
// Test-only stock Electron client. No production browser APIs or policy edits.
|
||||
const {app,BrowserWindow,session}=require('electron');
|
||||
const {X509Certificate}=require('node:crypto');
|
||||
app.disableHardwareAcceleration();
|
||||
let window,config,loginCount=0,initialized=false;
|
||||
const send=value=>{if(process.connected)process.send(value);};
|
||||
process.on('disconnect',()=>app.exit(0));
|
||||
app.on('login',(event,contents,_details,info,callback)=>{
|
||||
event.preventDefault();
|
||||
if(config&&contents===window?.webContents&&info.isProxy&&info.host==='127.0.0.1'&&info.port===config.proxyPort&&info.scheme.toLowerCase()==='basic'&&info.realm==='SafetyNet private broker'){
|
||||
loginCount++;callback(config.credentials.username,config.credentials.password);
|
||||
}else callback();
|
||||
});
|
||||
process.on('message',async message=>{
|
||||
try {
|
||||
if(message?.type==='initialize'&&!initialized){
|
||||
initialized=true;config=message.config;
|
||||
app.setPath('userData',config.userData);
|
||||
await app.whenReady();
|
||||
const partition=session.fromPartition('broker-interop',{cache:false});
|
||||
partition.setCertificateVerifyProc((request,callback)=>{
|
||||
try {
|
||||
const certificate=new X509Certificate(request.certificate.data);
|
||||
const now=Date.now();
|
||||
if(request.hostname==='localhost'&&certificate.checkHost('localhost')==='localhost'&&certificate.fingerprint256===config.fingerprint&&Date.parse(certificate.validFrom)<=now&&now<Date.parse(certificate.validTo))return callback(0);
|
||||
}catch{}
|
||||
callback(-3);
|
||||
});
|
||||
await partition.setProxy({mode:'fixed_servers',proxyRules:`http=127.0.0.1:${config.proxyPort};https=127.0.0.1:${config.proxyPort}`,proxyBypassRules:'<-loopback>'});
|
||||
window=new BrowserWindow({show:false,webPreferences:{session:partition,nodeIntegration:false,contextIsolation:true,sandbox:true,webSecurity:true}});
|
||||
window.webContents.on('render-process-gone',()=>send({type:'error',code:'renderer-gone'}));
|
||||
await window.loadURL(config.origin+'/');
|
||||
send({type:'ready',loginCount,electron:process.versions.electron,chromium:process.versions.chrome});return;
|
||||
}
|
||||
if(message?.type==='stop'){window?.destroy();app.exit(0);return;}
|
||||
const bodies={public:'public browser content',secret:'PROJECT SECRET',revoked:'public revoked content'};
|
||||
if(!window||!Object.hasOwn(bodies,message?.type))throw Error('invalid-message');
|
||||
const result=await window.webContents.executeJavaScript(`(async()=>{try{const response=await fetch('/submit',{method:'POST',body:${JSON.stringify(bodies[message.type])}});return {status:response.status,body:await response.text()};}catch{return {blocked:true};}})()`);
|
||||
send({type:'result',operation:message.type,result,loginCount});
|
||||
} catch {send({type:'error',code:'interop-failed'});}
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
// Diagnostic fixture only: compare explicitly allowlisted environment profiles
|
||||
// against a real packaged contained inspector. No private content or values.
|
||||
const path=require('node:path');
|
||||
const {startInspector}=require('../../apps/browser/inspection.cjs');
|
||||
const {execFileSync}=require('node:child_process');
|
||||
function children(){const text=execFileSync('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe',['-NoProfile','-NonInteractive','-Command',`@(Get-CimInstance Win32_Process -Filter "ParentProcessId = ${process.pid}" | Where-Object Name -eq 'python.exe' | Select-Object -ExpandProperty ProcessId) | ConvertTo-Json -Compress`],{encoding:'utf8',windowsHide:true,timeout:10000}).trim();return text?[].concat(JSON.parse(text)):[];}
|
||||
async function main(){
|
||||
const {defaultPolicy}=await import('../../packages/core/policy.mjs');const baseline={...process.env},root=path.resolve(__dirname,'../../.runtime/windows');
|
||||
for(const variant of ['stripped','profile']){
|
||||
const allowed=['SystemRoot','WINDIR','TEMP','TMP',...(variant==='profile'?['USERPROFILE','LOCALAPPDATA','APPDATA']:[])];for(const key of Object.keys(process.env))delete process.env[key];for(const key of allowed)if(baseline[key])process.env[key]=baseline[key];
|
||||
let inspector,stage='startup',result;
|
||||
try{inspector=startInspector(root,true);if(!inspector)throw Error('fixed-path-fault');await inspector.waitReady();stage='scan';const answer=await inspector.inspect(Buffer.from('public synthetic text'),defaultPolicy());result={variant,stage,decision:answer.decision,complete:answer.coverage.complete};}
|
||||
catch{result={variant,stage,failed:true};}
|
||||
finally{inspector?.stop();for(let count=0;count<20&&children().length;count++)await new Promise(resolve=>setTimeout(resolve,100));result.ownedParents=children().length;}
|
||||
process.stdout.write(JSON.stringify(result)+'\n');if(result.ownedParents)throw Error('fixed-cleanup-fault');
|
||||
}
|
||||
}
|
||||
main().catch(()=>{process.stderr.write('fixed-environment-proof-fault\n');process.exitCode=1;});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
// Explicitly authorized synthetic fixture only. Private bounded in-memory OTLP
|
||||
// receiver; no real management policy, content, token or production exporter.
|
||||
const http=require('node:http');
|
||||
const {createDiagnostics}=require('../../apps/browser/diagnostics.cjs');
|
||||
async function createFixtureDiagnostics({enabled,policy,audit}){
|
||||
if(!enabled)return {persist:audit.persist,run:(_attributes,fn)=>fn(),stage:(_name,_attributes,fn)=>fn(),finish:async()=>({enabled:false})};
|
||||
const batches=[];let storedBytes=0,invalid=0;const spans=()=>batches.flatMap(batch=>(batch.resourceSpans||[]).flatMap(resource=>(resource.scopeSpans||[]).flatMap(scope=>scope.spans||[])));
|
||||
const server=http.createServer({maxHeaderSize:8192},async(request,response)=>{
|
||||
if(request.method==='GET'&&/^\/api\/trace\/[a-f0-9]{32}$/.test(request.url)){const id=request.url.split('/').pop();response.setHeader('content-type','application/json');response.end(JSON.stringify({spans:spans().filter(span=>span.traceId===id)}));return;}
|
||||
if(request.method!=='POST'||!['/v1/traces','/v1/logs'].includes(request.url)){response.writeHead(404);response.end();return;}
|
||||
const chunks=[];let size=0;
|
||||
try{for await(const chunk of request){size+=chunk.length;if(size>1048576||storedBytes+size>4194304)throw Error('fixed-budget');chunks.push(chunk);}const text=Buffer.concat(chunks).toString();if(text.includes('SYNTHETIC_PRIVATE')||text.includes('proxy-password')||text.includes('%PDF-'))throw Error('fixed-content');const value=JSON.parse(text);if(request.url==='/v1/traces')batches.push(value);storedBytes+=size;response.setHeader('content-type','application/json');response.end('{}');}catch{invalid++;response.writeHead(400);response.end('{}');}
|
||||
});server.maxConnections=8;server.listen(0,'127.0.0.1');await new Promise(resolve=>server.once('listening',resolve));
|
||||
const endpoint=`http://127.0.0.1:${server.address().port}`,diagnostics=createDiagnostics();let active;
|
||||
policy.diagnostics={...policy.diagnostics,enabled:true,mode:'debug',endpoint,sampleRate:1,debugSampleRate:1,maxRecordAgeMs:120000};
|
||||
if(!diagnostics.configure(policy.diagnostics,{policyVersion:policy.version,deviceId:'native-production-fixture'}))throw Error('fixture-diagnostics-unavailable');
|
||||
return {
|
||||
stage:(name,attributes,fn)=>diagnostics.span(name,attributes,fn),
|
||||
async trace(traceId){if(!/^[a-f0-9]{32}$/.test(traceId))throw Error('fixture-trace-invalid');await diagnostics.flush();const response=await fetch(endpoint+'/api/trace/'+traceId);if(!response.ok)throw Error('fixture-trace-unavailable');return response.json();},
|
||||
persist:event=>diagnostics.withCorrelation(active,()=>diagnostics.spanSync('browser.management',{operation:'audit.deliver',component:'management'},()=>audit.persist({...event,...diagnostics.correlation()}))),
|
||||
run:(attributes,fn)=>diagnostics.span('browser.request',{operation:'broker.request',component:'network',...attributes},async()=>{active=diagnostics.correlation();try{return await fn();}finally{active=undefined;}}),
|
||||
async finish(){
|
||||
try{await diagnostics.flush();const status=diagnostics.status();if(status.failedExports||status.lifetimeFailedExports||invalid)throw Error('fixture-diagnostics-export-failed');const event=audit.events().find(value=>value.traceId);if(!event)throw Error('fixture-diagnostics-correlation-missing');const response=await fetch(endpoint+'/api/trace/'+event.traceId),trace=await response.json();const child=trace.spans.find(value=>value.spanId===event.spanId),parent=trace.spans.find(value=>value.spanId===child?.parentSpanId);if(!child||!parent||parent.name!=='browser.request'||BigInt(parent.endTimeUnixNano)<=BigInt(parent.startTimeUnixNano))throw Error('fixture-diagnostics-parent-missing');const stages=trace.spans.filter(value=>value.name.startsWith('browser.broker.')).map(value=>({name:value.name,parentSpanId:value.parentSpanId,durationMs:Number(BigInt(value.endTimeUnixNano)-BigInt(value.startTimeUnixNano))/1e6}));return {enabled:true,traceId:event.traceId,auditSpanId:event.spanId,parentSpanId:parent.spanId,spanCount:spans().length,storedBytes,stages,invalid,failedExports:status.failedExports,lifetimeFailedExports:status.lifetimeFailedExports};}
|
||||
finally{await diagnostics.shutdown();server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}
|
||||
}
|
||||
};
|
||||
}
|
||||
module.exports={createFixtureDiagnostics};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
// Synthetic classification belongs only in this explicitly test-only factory.
|
||||
const fs=require('node:fs');
|
||||
const path=require('node:path');
|
||||
const https=require('node:https');
|
||||
const {runBrokerProcess}=require('../../apps/network-broker/process-entry.cjs');
|
||||
const {createConnectProxy}=require('../../apps/network-broker/connect-inspection-proxy.cjs');
|
||||
const cert=fs.readFileSync(path.join(__dirname,'icap/cert.pem'));
|
||||
const key=fs.readFileSync(path.join(__dirname,'icap/key.pem'));
|
||||
runBrokerProcess(config=>{
|
||||
if(Object.keys(config).join(',')!=='destination'||typeof config.destination!=='string'||!/^localhost:[1-9][0-9]{0,4}$/.test(config.destination))throw Error('invalid-test-config');
|
||||
const port=Number(config.destination.split(':')[1]);if(port>65535)throw Error('invalid-test-config');
|
||||
return createConnectProxy({authority:'synthetic-test-policy',current:()=>true,resolveCertificate:binding=>binding.authority===config.destination?{authority:config.destination,key,cert}:undefined,inspect:({body})=>({decision:body.toString().includes('SECRET')?'block':'allow'}),forward:({request,body,signal,maxResponseBytes})=>new Promise((resolve,reject)=>{
|
||||
const outgoing=https.request({host:'127.0.0.1',port,path:new URL(request.url).pathname,method:request.method,servername:'localhost',ca:cert,rejectUnauthorized:true,signal,headers:{host:config.destination,'content-length':body.length}},res=>{
|
||||
const chunks=[];let bytes=0;
|
||||
res.on('data',chunk=>{bytes+=chunk.length;if(bytes>maxResponseBytes){outgoing.destroy();reject(Error('response-bound'));return;}chunks.push(Buffer.from(chunk));});res.on('end',()=>resolve({statusCode:res.statusCode,headers:res.headers,body:Buffer.concat(chunks)}));res.on('error',reject);
|
||||
});outgoing.on('error',reject);outgoing.end(body);
|
||||
}),deadlineMs:1000,authMode:'bearer'});
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
const {createFramedChannel}=require('../../apps/network-broker/framed-channel.cjs');
|
||||
const channel=createFramedChannel({readable:process.stdin,writable:process.stdout,inboundFirstLimit:2097152});
|
||||
channel.once('disconnect',()=>process.exit(0));
|
||||
let context;
|
||||
channel.on('message',message=>{
|
||||
if(message.type==='start'){
|
||||
context={version:1,generation:message.generation,policyVersion:message.policyVersion};
|
||||
if(message.config.malformed){process.stdout.write(Buffer.from([1,0,0,0,0xff]));return;}
|
||||
channel.send({type:'ready',...context,authMode:'basic',sequence:0,port:message.config.port,credentials:{username:'safetynet-'+'a'.repeat(32),password:'b'.repeat(43)}},error=>{
|
||||
if(error)process.exit(2);
|
||||
if(message.config.audit){const id='12345678-1234-4234-8234-123456789abc';channel.send({type:'audit',...context,sequence:1,requestId:id,event:{eventId:id,kind:'upload.authorized',ruleId:'broker-approved',policyVersion:context.policyVersion,request:{bytes:0,detectedType:'text'}}});}
|
||||
});
|
||||
}else if(message.type==='audit-receipt'){
|
||||
if(message.receipt?.persisted!==true||message.receipt.eventId!==message.requestId)process.exit(3);
|
||||
}else process.exit(4);
|
||||
});
|
||||
Reference in new issue
Block a user