'use strict'; const fs=require('node:fs/promises'); const path=require('node:path'); const {spawn}=require('node:child_process'); const {TextDecoder}=require('node:util'); const {StateSchema}=require('./service-acceptance.cjs'); const MAX=2097152; const SID=/^(?:S-1-5-21|S-1-12-1)-(?:(?:0|[1-9][0-9]{0,9})-){3}(?:0|[1-9][0-9]{0,9})$/; const exact=(v,keys)=>v&&typeof v==='object'&&!Array.isArray(v)&&Object.keys(v).sort().join(',')===keys.sort().join(','); const fail=()=>Error('broker-bootstrap-invalid'); // These paths derive exclusively from the reviewed installed module location. // Native helper verifies its service SID and retained signed Node parent. A JSON // SID alone is never evidence of peer authentication. const installationRoot=path.resolve(__dirname,'../../../..'); async function readEnvelope(root,name,io=fs){ if(!['release-envelope.json','deployment-envelope.json'].includes(name))throw fail(); const cap=Math.ceil((name==='release-envelope.json'?2097152:262144)*4/3)+256; const target=path.join(root,name);let handle; try{ let current=path.parse(target).root; for(const component of target.slice(current.length).split(path.sep)){current=path.join(current,component);const info=await io.lstat(current);if(info.isSymbolicLink())throw fail();} handle=await io.open(target,'r');const info=await handle.stat(); if(!info.isFile()||info.size<2||info.size>cap)throw fail(); const bytes=Buffer.alloc(info.size+1);let count=0; try{ while(count30000)throw fail(); return new Promise((resolve,reject)=>{ let child,timer,failed=false,closed=false,total=0;const limit=mode==='--commit-acceptance'?65536:MAX,output=Buffer.alloc(limit+4); const stop=()=>{failed=true;try{child?.kill();}catch{}}; try{ const env={SystemRoot:process.env.SystemRoot,WINDIR:process.env.WINDIR},sid=process.env.SAFETYNET_SERVICE_CLIENT_SID; if(sid!==undefined){if(!SID.test(sid)||sid.split('-').slice(4).some(n=>Number(n)>4294967295))throw fail();env.SAFETYNET_SERVICE_CLIENT_SID=sid;} child=launch(path.join(root,'safetynet-service-host.exe'),[mode],{cwd:root,windowsHide:true,shell:false,stdio:['pipe','pipe','ignore'],env}); timer=setTimeout(stop,deadlineMs); child.once('error',stop);child.stdout.on('error',stop);child.stdin.on('error',stop); child.stdout.on('data',chunk=>{if(failed)return;if(!Buffer.isBuffer(chunk)||total+chunk.length>limit+4){stop();return;}chunk.copy(output,total);total+=chunk.length;if(total>=4){const size=output.readUInt32LE(0);if(size<2||size>limit||total>size+4)stop();}}); child.once('close',(code,signal)=>{ closed=true;clearTimeout(timer); try{if(failed||code!==0||signal||total<6||output.readUInt32LE(0)!==total-4)throw fail();const value=JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(output.subarray(4,total)));resolve(value);}catch{reject(fail());}finally{output.fill(0);} }); if(input===undefined)child.stdin.end();else{const payload=Buffer.from(JSON.stringify(input));if(payload.length<2||payload.length>MAX)throw fail();const frame=Buffer.alloc(payload.length+4);frame.writeUInt32LE(payload.length);payload.copy(frame,4);payload.fill(0);child.stdin.end(frame,()=>frame.fill(0));} }catch{stop();if(!child||closed){clearTimeout(timer);output.fill(0);reject(fail());}} // Timeout cannot accept output or invent process termination. A killed child // must emit close; an unconfirmed terminal state remains a failed pending job. }); } function createServiceStoreClient({launch=spawn,root=installationRoot,deadlineMs=10000}={}){ const invoke=(mode,input)=>invokeHelper(mode,input,{launch,root,deadlineMs}); const safe=fn=>async(...args)=>{try{return await fn(...args);}catch{throw fail();}}; return Object.freeze({snapshot:safe(async()=>{const value=await invoke('--trust-snapshot');if(!exact(value,['version','authenticatedSid','releaseAnchor','acceptanceState'])||value.version!==1||typeof value.authenticatedSid!=='string'||typeof value.releaseAnchor!=='string'||value.releaseAnchor.length>16384)throw fail();StateSchema.parse(value.acceptanceState);return value;}),commit:safe(async request=>{if(!exact(request,['expectedPreviousSHA','candidateState'])||!/^[a-f0-9]{64}$/.test(request.expectedPreviousSHA))throw fail();StateSchema.parse(request.candidateState);const value=await invoke('--commit-acceptance',request);if(!exact(value,['persisted','stateSha256'])||value.persisted!==true||!/^[a-f0-9]{64}$/.test(value.stateSha256))throw fail();return value;}),envelopes:safe(async()=>({releaseEnvelope:await readEnvelope(root,'release-envelope.json'),deploymentEnvelope:await readEnvelope(root,'deployment-envelope.json')}))}); } module.exports={createServiceStoreClient,invokeHelper,installationRoot,readEnvelope};