Files
safetynet/scripts/verify-package.mjs
T

56 lines
6.4 KiB
JavaScript
Raw Normal View History

2026-10-13 10:33:31 +02:00
import {createRequire} from 'node:module';
import {spawn} from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';
import assert from 'node:assert/strict';
const require=createRequire(import.meta.url);
const {startAgentControl}=require('../apps/browser/agent-control.cjs');
const {startInspector}=require('../apps/browser/inspection.cjs');
const root=path.resolve('dist/win-unpacked');
if(process.platform!=='win32')throw Error('Windows package verification requires Windows');
const {extractFile}=require('@electron/asar');
for(const file of ['apps/browser/policy-assignment.cjs','packages/core/policy-groups.mjs','apps/browser/tab-registry.cjs','apps/browser/agent-adapters.cjs','apps/browser/agent-control.cjs','apps/browser/password-fill.cjs','apps/browser/diagnostics.cjs','packages/core/diagnostics.mjs','packages/core/security.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged device/session policy source is stale: ${file}`);
for(const file of ['apps/browser/main.cjs','apps/browser/preload.cjs','apps/browser/site-preload.cjs','apps/browser/clipboard-deny.cjs','apps/browser/clipboard-guard.cjs','apps/browser/clipboard-dom.cjs','apps/browser/page-clipboard-guard.cjs','apps/browser/startup-security.cjs','apps/browser/visual-capture.cjs','apps/browser/renderer.js','apps/browser/index.html','apps/browser/style.css','packages/core/clipboard.mjs']){
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged source is stale: ${file}`);
}
for(const file of ['apps/browser/inspection.cjs','apps/browser/managed-settings.cjs','apps/browser/management-channel.cjs','apps/browser/enterprise-inspection.cjs','apps/browser/icap-client.cjs','apps/browser/network-posture.cjs','apps/browser/download-gate.cjs','packages/core/enterprise.mjs','packages/core/semantic.mjs','packages/core/managed-settings.mjs','packages/core/policy-channel.cjs','packages/core/policy.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged source is stale: ${file}`);
for(const file of ['semantic.py','engine.py','server.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py'])assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector',file)),await fs.readFile(path.join('apps/inspector',file)),`Packaged inspector is stale: ${file}`);
const temporary=await fs.mkdtemp(path.join(os.tmpdir(),'safetynet-package-'));
for(const file of ['apps/browser/bookmarks.cjs','packages/core/bookmarks.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged bookmark source is stale: ${file}`);
function run(executable,args,input,timeoutMs=15000){return new Promise((resolve,reject)=>{
const child=spawn(executable,args,{stdio:['pipe','pipe','pipe'],windowsHide:true,env:{...process.env,PYTHONDONTWRITEBYTECODE:'1'}});let output='',errors='';
const timer=setTimeout(()=>{child.kill();reject(Error('Package verification timed out'));},timeoutMs);
child.on('error',reject);child.stdout.on('data',data=>output+=data);child.stderr.on('data',data=>errors+=data);
child.on('close',code=>{clearTimeout(timer);resolve({code,output,errors});});child.stdin.end(input);
});}
let service;
try{
const inspectorBytecode=path.join(root,'resources/inspector/__pycache__');
const bytecodeExists=()=>fs.stat(inspectorBytecode).then(()=>true,error=>{if(error.code==='ENOENT')return false;throw error;});
assert.equal(await bytecodeExists(),false,'Package must start with no inspector bytecode cache');
const packagedInspector=startInspector(path.join(root,'resources'),true);
assert.ok(packagedInspector,'Packaged inspector runtime is missing');
try{
const verdict=await packagedInspector.inspect(Buffer.from('Public package verification text'),{dlp:{maxBytes:1048576,rules:[],semantic:{enabled:false}}});
assert.equal(verdict.decision,'allow','Packaged contained inspector failed');
assert.equal(verdict.coverage.complete,true);
}finally{packagedInspector.stop();}
const semanticPath=path.join(root,'resources/semantic');
const actual=await run(path.join(root,'resources/python/python.exe'),['-c','import sys; sys.path.insert(0,sys.argv[1]); from semantic import NativeSemanticModel; m=NativeSemanticModel(sys.argv[2],sys.argv[3]); vectors=m.text_vectors("Public information about weather forecasting."); assert len(vectors)>=1 and len(vectors[0])==768; m.close(); print("Offline packaged semantic CPU inference verified.")',path.join(root,'resources/inspector'),path.join(semanticPath,'models'),path.join(semanticPath,'searchi-gemma-worker.exe')],undefined,45000);
assert.equal(actual.code,0,'Packaged offline semantic inference failed');assert.ok(actual.output.includes('Offline packaged semantic CPU inference verified.'));
service=await startAgentControl({authorize:async()=>({allowed:true,role:'agent',scopes:['sessions:list']}),audit:async()=>{},actions:{sessions:async()=>[{id:'managed',url:'https://example.test/'}]}});
const encrypted=await run(path.join(root,'resources/python/python.exe'),[path.join(root,'resources/inspector/dpapi.py'),'protect'],JSON.stringify({address:service.address,token:service.token}));
assert.equal(encrypted.code,0,'Packaged DPAPI failed');
const connection=path.join(temporary,'connection.json'),request=path.join(temporary,'request.json');
await fs.writeFile(connection,JSON.stringify({format:'windows-dpapi-v1',protectedConnection:encrypted.output}));
await fs.writeFile(request,JSON.stringify({action:'sessions'}));
const result=await run(path.join(root,'SafetyNet.exe'),[`--agent-connection=${connection}`,`--agent-action=${request}`]);
assert.equal(result.code,0,'Packaged agent helper failed');assert.equal(JSON.parse(result.output).result[0].id,'managed');assert.equal(result.output.includes(service.token),false);
assert.equal(await bytecodeExists(),false,'Cold packaged startup must not create inspector bytecode files');
console.log('Packaged security sources, Windows-contained inspector, offline EmbeddingGemma 2 CPU inference, Windows agent helper and bundled DPAPI runtime passed.');
}finally{
await service?.close();
if(!temporary.startsWith(path.join(os.tmpdir(),'safetynet-package-')))throw Error('Unexpected temporary directory');
await fs.rm(temporary,{recursive:true,force:true});
}