Files
safetynet/scripts/windows-semantic-dlp-smoke.cjs
T

13 lines
10 KiB
JavaScript
Raw Normal View History

'use strict';
const {app,BrowserWindow,session,clipboard,ClipboardItem}=require('electron'),fs=require('node:fs'),path=require('node:path'),https=require('node:https'),assert=require('node:assert/strict'),{X509Certificate}=require('node:crypto');
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();const fixtures=JSON.parse(process.env.SAFETYNET_SEMANTIC_FIXTURES),origin=process.env.SAFETYNET_SMOKE_ORIGIN,base=process.env.SAFETYNET_SMOKE_SERVER,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN},localWitnesses=[];
let actualDiagnostics;const diagnosticsModule=require('../apps/browser/diagnostics.cjs'),createDiagnostics=diagnosticsModule.createDiagnostics;diagnosticsModule.createDiagnostics=(...args)=>{const value=createDiagnostics(...args);actualDiagnostics=value;return value;};const inspection=require('../apps/browser/inspection.cjs'),startInspector=inspection.startInspector;inspection.startInspector=(...args)=>{const value=startInspector(...args),inspect=value.inspect.bind(value);value.inspect=async(bytes,policy)=>{const result=await inspect(bytes,policy);localWitnesses.push({model:result.model,decision:result.decision,semanticRule:result.ruleId==='semantic-workforce-fixture',coverageComplete:result.coverage?.complete===true,score:result.similarity?.find(s=>s.categoryId==='semantic-workforce-fixture')?.score});return result;};return value;};require('../apps/browser/main.cjs');
const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn){for(let i=0;i<400;i++){const value=await fn();if(value)return value;await wait(100);}throw Error('Native semantic fixture timeout');}
app.whenReady().then(async()=>{let page,original=[],stage='startup',getFacts;const checks=[];try{for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);original.push(new ClipboardItem(data));}const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});const fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256;session.fromPartition('persist:safetynet-managed').setCertificateVerifyProc((req,callback)=>{let valid=false;try{valid=req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint;}catch{}callback(valid?0:-2);});getFacts=()=>new Promise((resolve,reject)=>{const request=https.get(origin+'/facts',{ca:fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem')),checkServerIdentity:(_host,cert)=>new X509Certificate(cert.raw).fingerprint256===fingerprint?undefined:Error('Synthetic certificate mismatch')},response=>{let body='';response.on('data',chunk=>body+=chunk);response.on('end',()=>resolve(JSON.parse(body)));});request.on('error',reject);});const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);assert.equal(result.ok,true);return result.data;};stage='enrollment under reviewed semantic policy';const enrolled=await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});assert.equal(enrolled.policyVersion,Number(process.env.SAFETYNET_SEMANTIC_POLICY));page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()&&page.getURL()===origin+'/workspace');
const key=async keyCode=>{win.focus();page.focus();page.sendInputEvent({type:'keyDown',keyCode,modifiers:['control']});page.sendInputEvent({type:'keyUp',keyCode,modifiers:['control']});await wait(500);await until(async()=>!(await ui('state')).clipboardPending);};const editor=async text=>page.executeJavaScript(`(()=>{const e=document.getElementById('editor');e.innerHTML='<p></p>';e.firstChild.textContent=${JSON.stringify(text)};e.focus();const r=document.createRange();r.selectNodeContents(e.firstChild);r.collapse(false);const s=getSelection();s.removeAllRanges();s.addRange(r);})()`),value=()=>page.executeJavaScript("document.getElementById('editor').innerText.trimEnd()");
stage='native public AI editor paste';await editor('');const point=await page.executeJavaScript("(()=>{const r=document.getElementById('editor').getBoundingClientRect();return {x:Math.round(r.left+8),y:Math.round(r.top+10)}})()");page.sendInputEvent({type:'mouseDown',button:'left',clickCount:1,...point});page.sendInputEvent({type:'mouseUp',button:'left',clickCount:1,...point});await clipboard.writeText(fixtures.publicText);await key('V');assert.equal(await value(),fixtures.publicText);assert.equal(await page.executeJavaScript('window.submitPrompt()'),true);assert.equal((await getFacts()).publicPosts,1);checks.push('real-gemma-public-paste-submit');
stage='native nonliteral paraphrase paste denied';await editor(fixtures.publicText);await clipboard.writeText(fixtures.paraphrase);await key('V');assert.equal(await value(),fixtures.publicText);assert.equal(await page.executeJavaScript('window.submitPrompt()'),true);assert.equal((await getFacts()).paraphrasePosts,0);assert.ok(localWitnesses.some(w=>w.model==='google/embeddinggemma-2'&&w.semanticRule&&w.decision==='block'&&w.score>=0.65));checks.push('real-gemma-paraphrase-paste-denied');
stage='native confidential typed paraphrase denied';const before=(await getFacts()).posts;assert.equal(await page.executeJavaScript(`fetch('/receiver/prompt',{method:'POST',headers:{'content-type':'text/plain'},body:${JSON.stringify(fixtures.paraphrase)}}).then(r=>r.ok).catch(()=>false)`),false);assert.equal((await getFacts()).posts,before);checks.push('real-gemma-paraphrase-body-denied');
stage='native public NTFS document allowed';const doc=await page.debugger.sendCommand('DOM.getDocument',{}),node=await page.debugger.sendCommand('DOM.querySelector',{nodeId:doc.root.nodeId,selector:'#file'});const upload=async file=>{await page.debugger.sendCommand('DOM.setFileInputFiles',{nodeId:node.nodeId,files:[file]});return page.executeJavaScript("(()=>{const body=new FormData();body.append('file',document.getElementById('file').files[0]);return fetch('/receiver/file',{method:'POST',body}).then(r=>r.ok).catch(()=>false)})()");};assert.equal(await upload(process.env.SAFETYNET_SMOKE_PUBLIC_FILE),true);assert.equal((await getFacts()).exactPublicFiles,1);stage='native paraphrase NTFS document denied';assert.equal(await upload(process.env.SAFETYNET_SMOKE_SECRET_FILE),false);let facts=await getFacts();assert.equal(facts.filePosts,1);assert.equal(facts.paraphrasePosts,0);assert.equal(facts.posts,3);checks.push('real-gemma-public-paraphrase-ntfs-document');
stage='actual semantic OTEL and durable audit';await actualDiagnostics.flush();await wait(2000);const events=[];let cursor=0;for(let batch=0;batch<20;batch++){const response=await fetch(base+`/api/admin/events/export?after=${cursor}&limit=1000&format=ecs`,{headers});assert.equal(response.status,200);const rows=(await response.text()).trim().split('\n').filter(Boolean).map(JSON.parse);for(const row of rows){assert.ok(!JSON.stringify(row).includes(fixtures.reference));assert.ok(!JSON.stringify(row).includes(fixtures.paraphrase));events.push(row);}if(rows.length<1000)break;const next=Number(response.headers.get('x-safetynet-next-cursor'));assert.ok(next>cursor);cursor=next;}assert.ok(events.some(e=>e.event.action==='teaching.published'));assert.ok(events.some(e=>e.event.action==='policy.published'));const traces=[];for(const action of ['clipboard.paste','clipboard.block','upload.authorized','upload.block']){const candidates=events.filter(e=>e.event.action===action&&e.trace?.id&&(action.endsWith('block')?e.rule?.id==='semantic-workforce-fixture':true));assert.ok(candidates.length,action+' semantic audit missing');let found;for(const event of candidates.reverse()){const trace=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.trace.id)).json();if(action.startsWith('upload.')&&!trace.spans.some(s=>s.name==='browser.upload.lease'))continue;assert.equal(event.safetynet.policy_version,enrolled.policyVersion);assert.equal(event.host.id,enrolled.deviceId);assert.ok(!JSON.stringify(trace).includes(fixtures.reference));assert.ok(!JSON.stringify(trace).includes(fixtures.paraphrase));const semantic=trace.spans.find(s=>s.name==='inspector.semantic');assert.ok(semantic,'Real imported Gemma semantic stage absent');assert.ok(trace.spans.some(s=>s.name==='inspector.semantic.load'));const byId=new Map(trace.spans.map(s=>[s.spanId,s])),expected=action.startsWith('clipboard.')?'browser.clipboard':'browser.request';let current=semantic,linked=false;const seen=new Set();while(current&&!seen.has(current.spanId)){if(current.name===expected){linked=true;break;}seen.add(current.spanId);current=byId.get(current.parentSpanId);}assert.ok(linked,'Semantic inference parent correlation missing');found={action,traceId:event.trace.id,stages:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs}))};break;}assert.ok(found);traces.push(found);}const diagnostics=actualDiagnostics.status();assert.equal(diagnostics.enabled,true);assert.equal(diagnostics.failedExports,0);assert.equal(diagnostics.lifetimeFailedExports,0);const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0);facts=await getFacts();fs.writeFileSync(path.resolve('artifacts/windows-semantic-dlp-native-proof.json'),JSON.stringify({actualWindows:process.platform==='win32',actualElectron:true,localModelAutomaticBundledPaths:true,exactPinnedHTTPS:true,nonliteralParaphrase:true,policyVersion:enrolled.policyVersion,deviceId:enrolled.deviceId,checks,receiver:facts,localWitnesses,traces,diagnostics,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}},null,2));console.log('Native local Gemma semantic paste/upload proof passed');if(original.length)await clipboard.write(original);else await clipboard.clear();require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0);
}catch(error){let facts;try{facts=await getFacts?.();await actualDiagnostics?.flush();}catch{}fs.writeFileSync(path.resolve('artifacts/windows-semantic-dlp-native-error.json'),JSON.stringify({stage,name:error.name,checks,facts,localWitnesses,diagnostics:actualDiagnostics?.status()},null,2));console.error('Native semantic MVP failed at '+stage+': '+error.name);try{if(original.length)await clipboard.write(original);else await clipboard.clear();}catch{}if(page&&!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});