'use strict'; const {fork}=require('node:child_process'); const path=require('node:path'); const {randomUUID,X509Certificate}=require('node:crypto'); const UUID=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/; const ENTRY='apps/network-broker/production-entry.cjs'; const STARTUP_CODES=new Set(['broker-bootstrap-invalid','broker-route-unsupported','broker-startup-inspector','broker-startup-certificate','broker-startup-transport','broker-startup-managed','broker-startup-failed']); // Parent-only: resourceRoot and verifyPackage come from the trusted embedded // installer contract. Never accept executable/module paths from renderer IPC. function createBrokerSupervisor({resourceRoot,verifyPackage,persistAudit,acceptCertificate,onQuarantine,spawn=fork,deadlineMs=15000,shutdownMs=5000,traceOperation=(_name,_attributes,run)=>run()}={}){ if(!path.isAbsolute(resourceRoot||'')||![verifyPackage,persistAudit,acceptCertificate,onQuarantine,spawn].every(f=>typeof f==='function')||!Number.isInteger(deadlineMs)||deadlineMs<1||deadlineMs>60000)throw Error('broker-supervisor-invalid'); if(!Number.isInteger(shutdownMs)||shutdownMs<1||shutdownMs>30000)throw Error('broker-supervisor-invalid'); if(typeof traceOperation!=='function')throw Error('broker-supervisor-invalid'); // Instrumentation may neither skip a protected operation nor replace its // result, retry it, or invoke it twice. Only fixed stage names are exported. async function stage(name,operation){ let original,called=false; const once=()=>{if(called)throw Error('broker-diagnostics-invalid');called=true;try{original=Promise.resolve(operation());}catch(error){original=Promise.reject(error);}original.catch(()=>{});return original;}; try{await traceOperation(name,{component:'network'},once);}catch(error){if(called)throw error;return once();} if(!called)once();return original; } let child,current,failed=false,closed=false,starting=false,timer,readyResolve,readyReject,lastSequence=0,stopping,pendingCertificates=0,startupCode; const stopChild=()=>{ clearTimeout(timer);if(stopping)return stopping; const previous=child;if(!previous)return Promise.resolve(); previous.removeAllListeners('message'); stopping=stage('browser.broker.shutdown',()=>new Promise((resolve,reject)=>{ let done=false; const finish=()=>{if(done)return;done=true;clearTimeout(limit);child=undefined;resolve();}; const limit=setTimeout(()=>{if(done)return;done=true;failed=true;try{onQuarantine('broker-shutdown-unconfirmed');}catch{}reject(Error('broker-shutdown-unconfirmed'));},shutdownMs); previous.once('exit',finish); // ChildProcess retains ownership state; no numeric PID/taskkill operation // is performed after exit, avoiding a reused-PID tree kill. Native job-based // descendant containment remains an installer/launcher prerequisite. if(previous.exitCode!==null&&previous.exitCode!==undefined||previous.signalCode)finish(); else try{previous.kill('SIGKILL');}catch{} })); // Fault callers do not await shutdown, but close() receives its real outcome. stopping.catch(()=>{});return stopping; }; const fault=()=>{if(failed||closed)return;failed=true;stopChild();try{onQuarantine('broker-supervisor-unavailable');}catch{}readyReject?.(Error('broker-supervisor-unavailable'));}; const send=value=>{if(!child||failed||closed)throw Error('broker-supervisor-unavailable');child.send(value,error=>{if(error)fault();});}; const context=()=>({version:1,generation:current.generation,policyVersion:current.policyVersion}); const exact=(m,keys)=>Object.keys(m).sort().join(',')===keys.sort().join(','); async function message(m,owner,generation){ if(owner!==child||generation!==current.generation)return; try{ const bytes=Buffer.byteLength(JSON.stringify(m)); if(bytes>65536||!m||typeof m!=='object'||Array.isArray(m)||m.version!==1||m.generation!==current.generation||m.policyVersion!==current.policyVersion)throw Error(); const common=['type','version','generation','policyVersion']; if(m.type==='startup-fault'){ if(current.ready||!exact(m,[...common,'code'])||!STARTUP_CODES.has(m.code))throw Error(); // Bounded local pre-enrollment state only; no raw errors or unauthorised // telemetry exporter. This never grants readiness or permits a retry. startupCode=m.code;fault();return; } if(m.type==='ready'){ if(m.sequence!==0||m.authMode!=='basic'||current.ready||!exact(m,[...common,'port','credentials','authMode','sequence'])||!Number.isInteger(m.port)||m.port!==current.port||!m.credentials||!exact(m.credentials,['username','password'])||!/^safetynet-[a-f0-9]{32}$/.test(m.credentials.username)||!/^[A-Za-z0-9_-]{43}$/.test(m.credentials.password))throw Error(); current.ready=true;clearTimeout(timer);readyResolve(Object.freeze({generation:current.generation,policyVersion:current.policyVersion,port:m.port,credentials:Object.freeze({...m.credentials})}));return; } if(!UUID.test(m.requestId)||!Number.isSafeInteger(m.sequence)||m.sequence!==lastSequence+1)throw Error();lastSequence=m.sequence; if(m.type==='audit'&&exact(m,[...common,'requestId','sequence','event'])){ const receipt=persistAudit(m.event); if(!receipt||receipt.persisted!==true||receipt.eventId!==m.event.eventId||receipt.then)throw Error(); if(owner!==child||generation!==current.generation)throw Error(); send({type:'audit-receipt',...context(),requestId:m.requestId,sequence:m.sequence,receipt});return; } if(m.type==='certificate'&&exact(m,[...common,'requestId','sequence','authority','cert'])){ if(pendingCertificates>=16)throw Error(); if(typeof m.authority!=='string'||m.authority.length>300||typeof m.cert!=='string'||m.cert.length>16384||!/^-----BEGIN CERTIFICATE-----/.test(m.cert))throw Error(); const certificate=new X509Certificate(m.cert);if(certificate.ca)throw Error(); pendingCertificates++; const certificateDeadline=setTimeout(fault,shutdownMs); let accepted; // Admission remains occupied until the ORIGINAL callback settles even if // the deadline quarantines. A timed-out operation cannot free capacity. try{accepted=await acceptCertificate(Object.freeze({...context(),authority:m.authority,cert:m.cert,fingerprint256:certificate.fingerprint256}));} finally{clearTimeout(certificateDeadline);pendingCertificates--;} if(accepted!==true||owner!==child||generation!==current.generation)throw Error(); send({type:'certificate-receipt',...context(),requestId:m.requestId,sequence:m.sequence,accepted:true});return; } throw Error(); }catch{fault();} } return Object.freeze({ async start({policyVersion,port,config}={}){ if(failed||closed||child||starting||!Number.isSafeInteger(policyVersion)||policyVersion<1||!Number.isInteger(port)||port<1||port>65535)throw Error('broker-supervisor-invalid'); starting=true; try{ if(await stage('browser.broker.verify',()=>verifyPackage(resourceRoot))!==true)throw Error(); if(failed||closed)throw Error(); current={generation:randomUUID(),policyVersion,port,ready:false};lastSequence=0; const envelope={type:'start',...context(),config};if(Buffer.byteLength(JSON.stringify(envelope))>2097152)throw Error(); // Allowlisted Windows runtime essentials only; inherited Node injection, // proxy credentials and developer environment never reach the child. const env={};for(const key of ['SystemRoot','WINDIR','TEMP','TMP','USERPROFILE','LOCALAPPDATA','APPDATA'])if(process.env[key])env[key]=process.env[key]; const pending=new Promise((resolve,reject)=>{readyResolve=resolve;readyReject=reject;}); const owner=spawn(path.join(resourceRoot,ENTRY),[],{execPath:path.join(resourceRoot,'node.exe'),cwd:resourceRoot,env,execArgv:[],windowsHide:true,stdio:['ignore','ignore','ignore','ipc'],serialization:'json'}); child=owner;const generation=current.generation; owner.on('message',m=>{void message(m,owner,generation);}); for(const event of ['error','exit','disconnect'])owner.once(event,()=>{if(owner===child)fault();}); timer=setTimeout(fault,deadlineMs);send(envelope);return await stage('browser.broker.start',()=>pending); }catch{fault();throw Error('broker-supervisor-unavailable');} }, async close(){closed=true;readyReject?.(Error('broker-supervisor-closed'));await stopChild();}, // Replacement is deliberately stop-and-new-supervisor: no automatic retry // and no stale ready state or credentials survive a policy generation. status(){return Object.freeze({ready:!!current?.ready&&!failed&&!closed,failed,closed,generation:current?.generation,startupCode});} }); } module.exports={createBrokerSupervisor};