// Standalone payload preparation only. No browser wiring, installer or service activation. import {readFile,writeFile,lstat,mkdir,mkdtemp,rename} from 'node:fs/promises'; import {createReadStream} from 'node:fs'; import {createHash} from 'node:crypto'; import {builtinModules} from 'node:module'; import {gunzipSync} from 'node:zlib'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; import {BROKER_RUNTIME} from './provision-broker-runtime.mjs'; import {buildBrokerPackageManifest,verifyBrokerPackage} from './broker-package-contract.mjs'; export const BROKER_PAYLOAD=Object.freeze({version:'0.1.0',protocol:1,entrypoint:'node.exe'}); const BROKER_FILES=Object.freeze(['audit-gate.cjs','bounded-audit.cjs','connect-inspection-proxy.cjs','diagnostics-adapter.cjs','dns-policy-route.cjs','download-response-gate.cjs','http-request-gate.cjs','http-response-gate.cjs','https-forwarder.cjs','https-inspection-server.cjs','lifecycle-audit.cjs','managed-proxy.cjs','management-identity-operation.cjs','multipart.cjs','policy-authority.cjs','process-entry.cjs','production-entry.cjs','production-factory.cjs','proxy-forwarder.cjs','service-acceptance.cjs','service-bootstrap.cjs','service-commit-gate.cjs','service-entry.cjs','service-store-client.cjs','framed-channel.cjs','tls-authority.cjs','tls-certificates.cjs','windows-wfp-plan.cjs','wfp-topology.cjs','wfp-revalidation.cjs','work-admission.cjs']); const BROWSER_HELPERS=new Set(['policy-assignment.cjs','enterprise-inspection.cjs','icap-client.cjs','inspect-text-representations.cjs','header-inspection.cjs','inspection.cjs','loopback-http.cjs']); const builtins=new Set(builtinModules.flatMap(name=>[name,name.replace(/^node:/,''),'node:'+name.replace(/^node:/,'')])); function fail(code){throw Object.assign(new Error(code),{code});} async function safe(file,directory=false){ for(let current=path.resolve(file);;current=path.dirname(current)){ const stat=await lstat(current);if(stat.isSymbolicLink())fail('broker_stage_path_unsafe'); if(current===path.resolve(file)&&((directory&&!stat.isDirectory())||(!directory&&!stat.isFile())))fail('broker_stage_path_unsafe'); if(path.dirname(current)===current)break; } } async function digest(file){await safe(file);const before=await lstat(file);if(before.size>100000000)fail('broker_stage_file_limit');const hash=createHash('sha256');for await(const chunk of createReadStream(file))hash.update(chunk);const after=await lstat(file);if(before.size!==after.size||before.mtimeMs!==after.mtimeMs||before.ctimeMs!==after.ctimeMs)fail('broker_stage_source_changed');return {bytes:after.size,sha256:hash.digest('hex')};} export async function snapshotSources(root,names){const records=[];for(const name of names){if(!/^[a-zA-Z0-9_./-]+$/.test(name)||name.split('/').some(p=>p==='..'||p==='.'||!p))fail('broker_stage_path_unsafe');records.push({path:name,...await digest(path.join(root,...name.split('/')))});}return records;} export async function verifySourceSnapshot(root,records){for(const record of records){const actual=await digest(path.join(root,...record.path.split('/')));if(actual.bytes!==record.bytes||actual.sha256!==record.sha256)fail('broker_stage_source_changed');}} function allowedSource(name){return /^packages\/core\/[a-z0-9-]+\.(?:cjs|mjs)$/.test(name)||(name.startsWith('apps/network-broker/')&&BROKER_FILES.includes(name.slice(20)))||(name.startsWith('apps/browser/')&&BROWSER_HELPERS.has(name.slice(13)));} export async function reviewedSourceClosure(root){ const queue=BROKER_FILES.map(name=>'apps/network-broker/'+name);queue.push('packages/core/policy.mjs');const seen=new Set(),external=new Set(); while(queue.length){const name=queue.shift();if(seen.has(name))continue;if(!allowedSource(name))fail('broker_stage_unreviewed_source');await safe(path.join(root,...name.split('/')));const source=await readFile(path.join(root,...name.split('/')),'utf8');if(Buffer.byteLength(source)>1048576)fail('broker_stage_source_limit');seen.add(name); for(const call of source.matchAll(/\b(?:require|import)\s*\(([^)]*)\)/g))if(!/^\s*['"][^'"]+['"]\s*$/.test(call[1]))fail('broker_stage_dynamic_dependency'); const imports=[...source.matchAll(/(?:require\s*\(|import\s*\()\s*['"]([^'"]+)['"]/g),...source.matchAll(/\bfrom\s*['"]([^'"]+)['"]/g),...source.matchAll(/\bimport\s*['"]([^'"]+)['"]/g)].map(match=>match[1]); for(const specifier of imports){if(builtins.has(specifier))continue;if(specifier.startsWith('.')){const next=path.posix.normalize(path.posix.join(path.posix.dirname(name),specifier));if(!allowedSource(next))fail('broker_stage_unreviewed_source');queue.push(next);}else external.add(specifier);} } if([...external].some(name=>name!=='zod'))fail('broker_stage_unreviewed_dependency');return {files:[...seen].sort(),dependencies:[...external].sort()}; } export function extractVerifiedZod(bytes,locked){ if(!Buffer.isBuffer(bytes)||bytes.length>10485760||locked?.version!=='4.6.7'||locked.dev||locked.resolved!==`https://registry.npmjs.org/zod/-/zod-${locked.version}.tgz`||!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(locked.integrity||'')||'sha512-'+createHash('sha512').update(bytes).digest('base64')!==locked.integrity)fail('broker_stage_dependency_integrity'); let tar;try{tar=gunzipSync(bytes,{maxOutputLength:67108864});}catch{fail('broker_stage_dependency_archive');} const files=[];const names=new Set();for(let offset=0;offset+512<=tar.length;){const header=tar.subarray(offset,offset+512);if(header.every(byte=>byte===0))break;const text=buffer=>buffer.toString('utf8').replace(/\0.*$/s,'');const name=text(header.subarray(0,100)),prefix=text(header.subarray(345,500));const full=prefix?prefix+'/'+name:name;const size=parseInt(text(header.subarray(124,136)).trim(),8),type=header[156];const checksum=parseInt(text(header.subarray(148,156)).trim(),8);let sum=0;for(let i=0;i<512;i++)sum+=i>=148&&i<156?32:header[i];if(sum!==checksum||!Number.isSafeInteger(size)||size<0||offset+512+size>tar.length||![0,48,53].includes(type)||!full.startsWith('package/')||full.includes('\\')||full.split('/').filter(Boolean).some(part=>! /^[a-zA-Z0-9_.-]+$/.test(part)||part==='..'||part==='.'||/[. ]$/.test(part)||/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part))||names.has(full.toLowerCase()))fail('broker_stage_dependency_archive');names.add(full.toLowerCase());if(names.size>4096)fail('broker_stage_dependency_archive'); const relative=full.slice(8);if(type!==53&&relative&&!relative.startsWith('src/')&&!/(^|\/)(tests?|fixtures?|benchmarks?)(\/|$)/i.test(relative)&&(relative==='package.json'||/^license(?:\.[a-z]+)?$/i.test(relative)||/\.(?:js|cjs|mjs)$/.test(relative)))files.push({path:'node_modules/zod/'+relative,bytes:Buffer.from(tar.subarray(offset+512,offset+512+size))});offset+=512+Math.ceil(size/512)*512; } if(files.length>2048||!files.some(file=>file.path==='node_modules/zod/package.json')||!files.some(file=>/\/license(?:\.[a-z]+)?$/i.test(file.path)))fail('broker_stage_dependency_archive');let metadata;try{metadata=JSON.parse(files.find(file=>file.path==='node_modules/zod/package.json').bytes.toString());}catch{fail('broker_stage_dependency_archive');}if(metadata.name!=='zod'||metadata.version!==locked.version)fail('broker_stage_dependency_integrity');return files; } async function archive(locked){const response=await fetch(locked.resolved,{redirect:'error',signal:AbortSignal.timeout(30000)});if(!response.ok)fail('broker_stage_dependency_download');const chunks=[];let count=0;for await(const chunk of response.body){count+=chunk.length;if(count>10485760)fail('broker_stage_dependency_limit');chunks.push(Buffer.from(chunk));}return Buffer.concat(chunks);} export async function stageBrokerPayload({root=fileURLToPath(new URL('../',import.meta.url)),destination,dependencyArchive}={}){ root=path.resolve(root);const parent=path.join(root,'.runtime','windows');destination=path.resolve(destination||path.join(parent,'broker-payload'));if(path.dirname(destination)!==parent||!/^broker-payload(?:-[a-zA-Z0-9_-]+)?$/.test(path.basename(destination)))fail('broker_stage_destination_unsafe');await safe(root,true);await safe(parent,true);try{await lstat(destination);fail('broker_stage_destination_exists');}catch(error){if(error.code!=='ENOENT')throw error;} const closure=await reviewedSourceClosure(root);const runtimeNames=['node.exe','LICENSE'];const sourceNames=[...closure.files,'package-lock.json',...runtimeNames.map(name=>'.runtime/windows/broker/'+name)];const sources=await snapshotSources(root,sourceNames);for(const [name,expected] of [['node.exe',BROKER_RUNTIME.executableSha256],['LICENSE',BROKER_RUNTIME.licenseSha256]])if(sources.find(item=>item.path==='.runtime/windows/broker/'+name).sha256!==expected)fail('broker_stage_runtime_integrity'); const lock=JSON.parse(await readFile(path.join(root,'package-lock.json'),'utf8'));const locked=lock.packages?.['node_modules/zod'];if(!locked)fail('broker_stage_dependency_missing');const dependencies=extractVerifiedZod(dependencyArchive||await archive(locked),locked); const temporary=await mkdtemp(path.join(parent,'broker-payload-stage-'));async function put(name,bytes){const target=path.join(temporary,...name.split('/'));await mkdir(path.dirname(target),{recursive:true});await writeFile(target,bytes,{flag:'wx'});} for(const name of runtimeNames)await put(name,await readFile(path.join(root,'.runtime/windows/broker',name)));for(const name of closure.files)await put(name,await readFile(path.join(root,...name.split('/'))));for(const file of dependencies)await put(file.path,file.bytes); await verifySourceSnapshot(root,sources);const manifest=await buildBrokerPackageManifest(temporary,BROKER_PAYLOAD);for(const source of sources.filter(item=>item.path!=='package-lock.json')){const target=source.path.startsWith('.runtime/windows/broker/')?source.path.slice('.runtime/windows/broker/'.length):source.path;const copied=manifest.files.find(item=>item.path===target);if(!copied||copied.bytes!==source.bytes||copied.sha256!==source.sha256)fail('broker_stage_source_changed');}await verifyBrokerPackage(temporary,manifest,BROKER_PAYLOAD);await verifySourceSnapshot(root,sources);await rename(temporary,destination); // Adjacent metadata is evidence; only an embedded/signed installer copy is trusted. await writeFile(destination+'.manifest.json',JSON.stringify(manifest,null,2)+'\n',{flag:'wx'});return {root:destination,manifest,sources,sourceFiles:closure.files.length,dependencies:closure.dependencies,productionReady:false}; } if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){try{const result=await stageBrokerPayload();console.log(JSON.stringify({files:result.manifest.files.length,dependencies:result.dependencies,productionReady:result.productionReady}));}catch(error){console.error(error.code?.startsWith('broker_')?error.code:'broker_stage_failed');process.exitCode=1;}}