Files
safetynet/scripts/semantic-browser-smoke.mjs
T

51 lines
7.4 KiB
JavaScript
Raw Normal View History

2026-10-13 10:33:31 +02:00
// Independent localhost control plane and real Windows browser. No cloud/model downloads.
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {createServer} from 'node:http';
import {mkdtempSync,mkdirSync,existsSync,writeFileSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join,resolve} from 'node:path';
import {spawn} from 'node:child_process';
import {randomBytes} from 'node:crypto';
import {createApp} from '../apps/server/app.mjs';
import {openStore,digest} from '../apps/server/store.mjs';
import {inspect,verifyPolicy} from '../packages/core/policy.mjs';
const require=createRequire(import.meta.url),electron=require('electron');
const temporary=mkdtempSync(join(tmpdir(),'safetynet-semantic-browser-'));
const runtime=resolve('.runtime/windows/semantic'),python=resolve('.runtime/windows/python/python.exe');
assert.ok(existsSync(join(runtime,'models/model-manifest.json')),'Stage verified local model artifacts before running');
const fixtures={reference:'Confidential corporate restructuring will close the Lisbon office next autumn and eliminate two hundred positions before the public announcement. Leadership is negotiating a six-million-euro acquisition of a rival. These undisclosed staffing reductions and acquisition plans are restricted internal company information.',paraphrase:'Private workforce planning calls for shutting the Portugal branch after summer and cutting roughly two hundred jobs. Executives are discussing a six-million-euro purchase of a competitor; the timetable and personnel reductions remain undisclosed.',publicText:'A simple tomato pasta recipe combines ripe tomatoes, olive oil, garlic and fresh basil. Boil the pasta in salted water and serve it warm.'};
let inspector,management,sink,store,child;
const report={model:'google/embeddinggemma-2',revision:'bfcd298762cc34d0357ece5ebdd31791a3a374d8',dimensions:768,profile:'document-v1',artifactFormat:'gguf',quantization:'Q8_0',runtimeRevision:'b11461',local:true};
try{
const inspectorToken=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url');
inspector=spawn(python,['apps/inspector/server.py'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,EMBEDDING_MODEL_DIR:join(runtime,'models'),EMBEDDING_WORKER_PATH:join(runtime,'searchi-gemma-worker.exe'),INSPECTOR_TOKEN:inspectorToken,INSPECTOR_PORT:'0',INSPECTOR_PARENT_PID:String(process.pid)}});
inspector.stderr.on('data',()=>{});
const inspectorPort=await new Promise((accept,reject)=>{let output='';const timeout=setTimeout(()=>{inspector.kill();reject(Error('Inspector startup timed out'));},40000);inspector.once('error',reject);inspector.once('exit',()=>{clearTimeout(timeout);reject(Error('Inspector exited before startup'));});inspector.stdout.on('data',chunk=>{output+=chunk;if(output.includes('\n')){clearTimeout(timeout);try{accept(JSON.parse(output.split('\n')[0]).port);}catch{reject(Error('Invalid inspector startup'));}}});});
sink=createServer((_req,res)=>{res.setHeader('Content-Type','text/html');res.end('<!doctype html><title>Local semantic enforcement fixture</title><h1>Public browser fixture</h1><pre id="source"></pre>');}).listen(0,'127.0.0.1');await new Promise(resolve=>sink.once('listening',resolve));
store=openStore(join(temporary,'management.sqlite'));
const initial=store.get('policy');initial.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;initial.browser.allowedHosts=['127.0.0.1'];initial.dlp.rules=[];store.put('policy',initial);
store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-semantic-user');
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,inspectorUrl:`http://127.0.0.1:${inspectorPort}`,inspectorToken};
management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>management.once('listening',resolve));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
async function admin(route,body,method='POST'){const result=await fetch(config.publicUrl+route,{method,headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify(body),signal:AbortSignal.timeout(90000)});assert.equal(result.status,200,`Central route ${route} failed`);return result.json();}
const teaching=await admin('/api/admin/teaching',{label:'Synthetic confidential workforce plan',filename:'synthetic-reference.txt',data:Buffer.from(fixtures.reference).toString('base64')});
await admin(`/api/admin/teaching/${teaching.id}/publish`,{expectedVersion:initial.version,approve:true});
const reference=await admin('/api/admin/semantic/reference',{description:fixtures.reference});
for(const key of ['model','revision','dimensions','profile','artifactFormat','quantization','runtimeRevision','local'])assert.equal(reference[key],report[key],`Unexpected local model ${key}`);
const policy=store.get('policy');policy.version++;policy.dlp.semantic={enabled:true,model:reference.model,revision:reference.revision,dimensions:reference.dimensions,profile:reference.profile,artifactFormat:reference.artifactFormat,quantization:reference.quantization,runtimeRevision:reference.runtimeRevision,categories:[{id:'synthetic-restructuring',label:'Synthetic restricted workforce plan',threshold:0.65,prototype:reference.vector,prototypes:reference.vectors}]};
assert.equal(inspect(Buffer.from(fixtures.paraphrase),policy).decision,'allow','Paraphrase accidentally matches a deterministic rule');
const secret=await admin('/api/admin/policy/test',{text:fixtures.paraphrase,policy}),ordinary=await admin('/api/admin/policy/test',{text:fixtures.publicText,policy});
assert.equal(secret.ruleId,'synthetic-restructuring','Semantic paraphrase did not match calibrated category');assert.equal(ordinary.decision,'allow','Unrelated public text failed semantic calibration');
await admin('/api/admin/policy',policy,'PUT');const signed=verifyPolicy(store.envelope(),store.publicKey());assert.equal(signed.dlp.semantic.revision,report.revision);assert.equal(signed.dlp.semantic.profile,report.profile);
const env={...process.env,SAFETYNET_SEMANTIC_PROFILE:join(temporary,'browser'),SAFETYNET_SEMANTIC_SERVER:config.publicUrl,SAFETYNET_SEMANTIC_CODE:code,SAFETYNET_SEMANTIC_ADMIN:adminToken,SAFETYNET_SEMANTIC_FIXTURES:JSON.stringify(fixtures)};
// Prove the browser's automatic bundled-path wiring rather than the fixture inspector environment.
delete env.EMBEDDING_MODEL_DIR;delete env.EMBEDDING_WORKER_PATH;delete env.ELECTRON_RUN_AS_NODE;
child=spawn(electron,['scripts/semantic-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),100000);
const status=await new Promise(resolve=>{child.once('exit',code=>resolve(code??1));child.once('error',()=>resolve(1));});clearTimeout(timer);assert.equal(status,0,'Native semantic browser fixture failed');
Object.assign(report,{centralTeaching:true,signedPolicyVerified:true,deterministicParaphraseAllow:true,semanticParaphraseBlock:true,publicTextAllow:true,paraphraseSimilarity:secret.similarity?.[0]?.score,publicSimilarity:ordinary.similarity?.[0]?.score,nativeProofPassed:true});
mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/semantic-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
}finally{
child?.kill();inspector?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}store?.db.close();
}