Harden Windows DLP enforcement and Electron runtime packaging
This commit is contained in:
1 parent
e7d778aeac
commit
8a253eda89
120 files changed
+3569
-135
No files matched your search
@@ -8,12 +8,12 @@ async function capture(file,section){await evaluate(`document.getElementById('${
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
const proofTraceIds=[],requestsById=new Map();session.defaultSession.webRequest.onCompleted({urls:[process.env.SAFETYNET_ADMIN_SMOKE_URL+'/api/*']},details=>{const row=requestsById.get(details.id);if(row){row.outcome='completed';row.statusCode=details.statusCode;}});session.defaultSession.webRequest.onErrorOccurred({urls:[process.env.SAFETYNET_ADMIN_SMOKE_URL+'/api/*']},details=>{const row=requestsById.get(details.id);if(row)row.outcome='failed';});session.defaultSession.webRequest.onBeforeSendHeaders({urls:[process.env.SAFETYNET_ADMIN_SMOKE_URL+'/api/*']},(details,callback)=>{const crypto=require('node:crypto'),traceId=crypto.randomBytes(16).toString('hex'),spanId=crypto.randomBytes(8).toString('hex');const path=new URL(details.url).pathname,route=path==='/api/admin/policy'?'/api/admin/policy':path==='/api/admin/policy/restore'?'/api/admin/policy/restore':/^\/api\/admin\/policy-reviews\/[^/]+\/approve$/.test(path)?'/api/admin/policy-reviews/:id/approve':/^\/api\/admin\/policy-history\/[^/]+\/restore$/.test(path)?'/api/admin/policy-history/:id/restore':'other-api';const row={traceId,parentSpanId:spanId,outcome:'pending',method:details.method,route};proofTraceIds.push(row);requestsById.set(details.id,row);callback({requestHeaders:{...details.requestHeaders,traceparent:'00-'+traceId+'-'+spanId+'-01'}});});
|
||||
await identity('grant-author');assert.equal(await evaluate('typeof require'),'undefined');for(const scope of ['history:list','history:go','window:state','window:control','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.deepEqual(await evaluate(`(()=>{const field=document.getElementById('agent-scope-${scope}');return {present:!!field,checked:field.checked,disabled:field.disabled};})()`),{present:true,checked:false,disabled:false});assert.equal(await evaluate(`document.getElementById('vault-controlled-submit').checked`),false);await evaluate(`document.getElementById('agent-enabled').checked=true;document.getElementById('vault-controlled-submit').checked=true;document.getElementById('vault-submit-forms').value=JSON.stringify([{id:'company-login',actionUrl:'https://login.company.example/signin',usernameField:'username',passwordField:'password'}]);document.getElementById('agent-scope-sessions').checked=true;document.getElementById('agent-scope-read').checked=true;for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])document.getElementById('agent-scope-'+scope).checked=true;document.getElementById('agent-hosts').value='*.company.example';document.getElementById('agent-identity-mode').value='individual';document.getElementById('agent-identity-mode').dispatchEvent(new Event('change'));document.getElementById('agent-grant-add').click();`);
|
||||
await identity('grant-author');assert.equal(await evaluate('typeof require'),'undefined');for(const scope of ['history:list','history:go','window:state','window:control','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.deepEqual(await evaluate(`(()=>{const field=document.getElementById('agent-scope-${scope}');return {present:!!field,checked:field.checked,disabled:field.disabled};})()`),{present:true,checked:false,disabled:false});assert.equal(await evaluate(`document.getElementById('vault-controlled-submit').checked`),false);assert.equal(await evaluate(`document.getElementById('agent-background-workers').checked||document.getElementById('agent-scope-network:background').checked`),false);await evaluate(`document.getElementById('agent-enabled').checked=true;document.getElementById('vault-controlled-submit').checked=true;document.getElementById('vault-rule-add').click();for(const [key,value]of Object.entries({id:'company-login',actionUrl:'https://login.company.example/signin',usernameField:'username',passwordField:'password'})){const field=document.querySelector('#vault-rule-editor [data-credential-rule-field='+key+']');field.value=value;field.dispatchEvent(new Event('input'));}document.getElementById('agent-scope-sessions').checked=true;document.getElementById('agent-scope-read').checked=true;for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])document.getElementById('agent-scope-'+scope).checked=true;document.getElementById('agent-hosts').value='*.company.example';document.getElementById('agent-identity-mode').value='individual';document.getElementById('agent-identity-mode').dispatchEvent(new Event('change'));document.getElementById('agent-grant-add').click();`);
|
||||
assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor .checks input')).every(x=>!x.checked)`),true);assert.equal(await evaluate(`document.getElementById('agent-vault-help').textContent.includes('A person must unlock')&&document.getElementById('agent-vault-help').textContent.includes('cannot unlock it or export passwords')`),true);assert.equal(await evaluate(`document.getElementById('agent-grants-editor').textContent.includes('human unlock')`),true);assert.equal(await evaluate(`document.getElementById('agent-sessions-help').textContent.includes('no passwords, form values or page state')&&document.getElementById('agent-sessions-help').textContent.includes('never restores revoked agent authority')`),true);
|
||||
await evaluate(`const box=document.querySelector('#agent-grants-editor>fieldset'),fields=box.querySelectorAll('.grid input,.grid textarea');fields[0].value='research-agent';fields[0].dispatchEvent(new Event('input'));fields[1].value='Research Agent';fields[1].dispatchEvent(new Event('input'));fields[2].checked=true;fields[2].dispatchEvent(new Event('change'));fields[3].value='2099-12-31T23:59:59Z';fields[3].dispatchEvent(new Event('input'));fields[4].value='*.company.example';fields[4].dispatchEvent(new Event('input'));for(const label of box.querySelectorAll('.checks label'))if(['sessions','read','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'].includes(label.textContent)){const input=label.querySelector('input');input.checked=true;input.dispatchEvent(new Event('change'));}document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');await selectReview();assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);
|
||||
assert.equal(await evaluate(`fetch('/api/admin/policy-reviews/'+selectedIndependentReview.id+'/approve',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({payloadHash:selectedIndependentReview.payloadHash,expectedVersion:selectedIndependentReview.expectedVersion,approve:true})}).then(r=>r.status)`),403);
|
||||
await identity('grant-reviewer');await selectReview();const diff=await evaluate(`document.getElementById('independent-review-diff').textContent`);for(const text of ['/agents/grants','research-agent','2099','company.example','sessions','read','generation','controlledSubmission','company-login','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.ok(diff.includes(text),text);await capture('artifacts/agent-grants-review-admin.png','independent-review-detail');await approve(2);
|
||||
await capture('artifacts/credential-submission-admin-controls.png','password-vault');await identity('grant-auditor');for(const id of ['vault-controlled-submit','vault-submit-forms'])assert.equal(await evaluate(`document.getElementById('${id}').disabled`),true);for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.equal(await evaluate(`document.getElementById('agent-scope-${scope}').disabled`),true);assert.equal(await evaluate(`document.getElementById('agent-grant-add').disabled&&document.getElementById('agent-identity-mode').disabled`),true);assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor input, #agent-grants-editor textarea, #agent-grants-editor button')).every(x=>x.disabled)`),true);
|
||||
await capture('artifacts/credential-submission-admin-controls.png','password-vault');await identity('grant-auditor');assert.equal(await evaluate(`Array.from(document.querySelectorAll('#vault-rule-editor input,#vault-rule-editor button')).every(x=>x.disabled)`),true);for(const id of ['vault-controlled-submit','vault-submit-forms','vault-rule-add','agent-background-workers','agent-scope-network:background'])assert.equal(await evaluate(`document.getElementById('${id}').disabled`),true);for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.equal(await evaluate(`document.getElementById('agent-scope-${scope}').disabled`),true);assert.equal(await evaluate(`document.getElementById('agent-grant-add').disabled&&document.getElementById('agent-identity-mode').disabled`),true);assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor input, #agent-grants-editor textarea, #agent-grants-editor button')).every(x=>x.disabled)`),true);
|
||||
await identity('grant-author');assert.equal(await evaluate(`document.querySelector('#agent-grants-editor .grid input').readOnly`),true);await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor button')).find(x=>x.textContent==='Invalidate previous credentials').click();document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');await identity('grant-reviewer');await selectReview();assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('"generation":2'));await approve(3);
|
||||
await identity('grant-author');await evaluate(`document.getElementById('brand-name').value='Unpublished preserved draft';document.getElementById('history-load').click()`);await waitFor(`document.querySelectorAll('#history-rows tr').length===3`);await evaluate(`document.querySelectorAll('#history-rows button')[1].click()`);await waitFor(`!document.getElementById('history-review').hidden`);assert.equal(await evaluate(`document.getElementById('brand-name').value`),'Unpublished preserved draft');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v3');assert.equal(await evaluate(`document.getElementById('history-restore').disabled`),true);const history=await evaluate(`document.getElementById('history-diff-list').textContent`);assert.ok(history.includes('/agents/grants'));assert.ok(history.includes('generation'));assert.ok((await evaluate(`document.getElementById('history-review-summary').textContent`)).includes('does not restore invalidated credentials'));await capture('artifacts/agent-grants-history-admin.png','history-review');await evaluate(`document.getElementById('history-approve').checked=true;document.getElementById('history-approve').dispatchEvent(new Event('change'));document.getElementById('history-restore').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v3');await selectReview();assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);await identity('grant-reviewer');await selectReview();assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('research-agent'));await approve(4);
|
||||
await identity('grant-author');assert.equal(await evaluate(`document.getElementById('agent-scope-screenshot').checked`),true);assert.equal(await evaluate(`document.getElementById('agent-visual-export-help').textContent.includes('does not authorize desktop')`),true);await evaluate(`document.getElementById('agent-scope-screenshot').checked=false;document.getElementById('agent-scope-screenshot').dispatchEvent(new Event('input'));for(const label of document.querySelectorAll('#agent-grants-editor .checks label'))if(label.textContent==='screenshot'){label.querySelector('input').checked=false;label.querySelector('input').dispatchEvent(new Event('change'));}document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v4');await identity('grant-reviewer');await selectReview();const revokeDiff=await evaluate(`document.getElementById('independent-review-diff').textContent`);assert.ok(revokeDiff.includes('screenshot'));await approve(5);assert.equal(await evaluate(`document.getElementById('agent-scope-screenshot').checked`),false);assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor .checks label')).filter(l=>l.textContent==='screenshot').every(l=>!l.querySelector('input').checked)`),true);
|
||||
|
||||
@@ -19,5 +19,6 @@ try {
|
||||
child=spawn(require('electron'),['scripts/agent-grants-history-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',d=>{if(String(d).includes('GRANT_ADMIN_OK'))console.log('Grant admin actual UI passed');});child.stderr.on('data',d=>{if(String(d).includes('GRANT_ADMIN_FAILED'))console.error(String(d).trim());});const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0);
|
||||
const p=verifyPolicy(store.envelope(),store.publicKey());assert.equal(p.version,5);assert.equal(p.agents.grants[0].generation,1);assert.equal(p.agents.identityMode,'individual');assert.equal(p.agents.scopes.includes('screenshot'),false);assert.equal(p.agents.grants[0].scopes.includes('screenshot'),false);for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit']){assert.ok(p.agents.scopes.includes(scope));assert.ok(p.agents.grants[0].scopes.includes(scope));}
|
||||
assert.equal(p.passwordManager.controlledSubmission.enabled,true);assert.equal(p.passwordManager.controlledSubmission.forms[0].actionUrl,'https://login.company.example/signin');
|
||||
const traces=JSON.parse(readFileSync('artifacts/agent-grants-admin-request-trace-ids.json','utf8'));await sdk.shutdown();await new Promise(r=>setTimeout(r,100));const exported=[],failedAttempts=traces.filter(item=>item.outcome==='failed');assert.ok(traces.some(item=>item.outcome==='completed'),'Completed API requests required');assert.equal(traces.some(item=>item.outcome!=='completed'&&['POST','PUT','PATCH','DELETE'].includes(item.method)),false,'Every policy mutation must complete and export a correlated span');for(const item of traces.filter(item=>item.outcome==='completed')){const r=await fetch(`http://127.0.0.1:4318/api/trace/${item.traceId}`);assert.equal(r.ok,true,'Completed API request trace must be available');if(r.ok){const trace=await r.json();assert.ok(trace.spans.some(s=>s.name==='server.request'&&s.parentSpanId===item.parentSpanId&&s.durationMs>=0));exported.push({traceId:item.traceId,parentSpanId:item.parentSpanId,trace});}}assert.ok(exported.length,'Actual request traces required');mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/agent-grants-history-admin-traces.json',JSON.stringify(exported,null,2));writeFileSync('artifacts/agent-grants-history-admin-proof.json',JSON.stringify({actualUi:true,vaultScopesDefaultOffAndReviewed:true,sessionScopesDefaultOffAndReviewed:true,controlledCredentialPolicyReviewed:true,prepareSubmitScopeDefaultOffAndReviewed:true,screenshotScopeDefaultOffAndReviewed:true,screenshotBaselineAndGrantRevocationReviewed:true,separateAuthorReviewer:true,selfApprovalDenied:true,auditorReadonly:true,exactGrantDiff:true,rotationReviewed:true,historyDraftPreserved:true,restoreReviewed:true,finalVersion:p.version,completedRequests:traces.filter(item=>item.outcome==='completed').length,failedAttempts:failedAttempts.map(item=>({traceId:item.traceId,outcome:item.outcome,method:item.method,route:item.route})),traceIds:exported.map(x=>x.traceId)},null,2));console.log('Grant publication/history and exported traces verified');
|
||||
const traces=JSON.parse(readFileSync('artifacts/agent-grants-admin-request-trace-ids.json','utf8'));await sdk.shutdown();await new Promise(r=>setTimeout(r,100));const exported=[],failedAttempts=traces.filter(item=>item.outcome==='failed');assert.ok(traces.some(item=>item.outcome==='completed'),'Completed API requests required');assert.equal(traces.some(item=>item.outcome!=='completed'&&['POST','PUT','PATCH','DELETE'].includes(item.method)),false,'Every policy mutation must complete and export a correlated span');for(const item of traces.filter(item=>item.outcome==='completed')){const r=await fetch(`http://127.0.0.1:4318/api/trace/${item.traceId}`);assert.equal(r.ok,true,'Completed API request trace must be available');if(r.ok){const trace=await r.json();assert.ok(trace.spans.some(s=>s.name==='server.request'&&s.parentSpanId===item.parentSpanId&&s.durationMs>=0));exported.push({traceId:item.traceId,parentSpanId:item.parentSpanId,trace});}}assert.ok(exported.length,'Actual request traces required');mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/agent-grants-history-admin-traces.json',JSON.stringify(exported,null,2));writeFileSync('artifacts/agent-grants-history-admin-proof.json',JSON.stringify({actualUi:true,vaultScopesDefaultOffAndReviewed:true,sessionScopesDefaultOffAndReviewed:true,controlledCredentialPolicyReviewed:true,typedCredentialRulesReviewed:true,typedCredentialRulesAuditorReadonly:true,backgroundWorkerControlsDefaultOff:true,prepareSubmitScopeDefaultOffAndReviewed:true,screenshotScopeDefaultOffAndReviewed:true,screenshotBaselineAndGrantRevocationReviewed:true,separateAuthorReviewer:true,selfApprovalDenied:true,auditorReadonly:true,exactGrantDiff:true,rotationReviewed:true,historyDraftPreserved:true,restoreReviewed:true,finalVersion:p.version,completedRequests:traces.filter(item=>item.outcome==='completed').length,failedAttempts:failedAttempts.map(item=>({traceId:item.traceId,outcome:item.outcome,method:item.method,route:item.route})),traceIds:exported.map(x=>x.traceId)},null,2));console.log('Grant publication/history and exported traces verified');
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
} finally {child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-agent-grant-admin-'))throw Error('Unsafe cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
||||
@@ -14,7 +14,7 @@ process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-agent-visual-'));mkdirSync('artifacts',{recursive:true});
|
||||
if(!resolve(temporary).startsWith(resolve(tmpdir())+sep)||!temporary.split(/[\\/]/).at(-1).startsWith('safetynet-agent-visual-'))throw Error('Unsafe temporary fixture path');
|
||||
let blockPNG=false;const avFacts={scans:0,pngScans:0,pngBlocks:0},avSockets=new Set(),pngSignature=Buffer.from([137,80,78,71,13,10,26,10]);const av=net.createServer(socket=>{avSockets.add(socket);socket.on('close',()=>avSockets.delete(socket));socket.on('error',()=>{});let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(input.length>4194304){socket.destroy();return;}if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options){input.fill(0);socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nAllow: 204\r\nISTag: "visual-fixture"\r\nEncapsulated: null-body=0\r\n\r\n');return;}avFacts.scans++;const png=input.indexOf(pngSignature)>=0;if(png)avFacts.pngScans++;const blocked=png&&blockPNG;if(blocked)avFacts.pngBlocks++;input.fill(0);socket.end(blocked?'ICAP/1.0 204 No Content\r\nISTag: "visual-fixture"\r\nX-Infection-Found: Type=0; Threat=synthetic-visual;\r\n\r\n':'ICAP/1.0 204 No Content\r\nISTag: "visual-fixture"\r\n\r\n');});}).listen(0,'127.0.0.1');await new Promise(r=>av.once('listening',r));
|
||||
let requests=0;const sink=createServer({cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')},(req,res)=>{requests++;if(req.url==='/av-stats'){res.setHeader('content-type','application/json');res.end(JSON.stringify(avFacts));return;}if(req.url==='/av-block')blockPNG=true;else if(['/public','/canvas','/sensitive','/closed-shadow','/open-shadow','/frames'].includes(req.url))blockPNG=false;res.setHeader('content-type','text/html');const base='<!doctype html><meta name="viewport" content="width=device-width"><style>body{margin:0;background:white;color:#123;font:28px Arial}h1{margin:70px}canvas{display:block}</style><title>Public visual fixture</title>';if(req.url==='/canvas')res.end(base+'<canvas width="1200" height="360"></canvas><script>const c=document.querySelector("canvas").getContext("2d");c.fillStyle="white";c.fillRect(0,0,1200,360);c.fillStyle="black";c.font="bold 48px Arial";c.fillText("CLASSIFIED ORCHID DOCUMENT",40,170);</script>');else if(req.url==='/sensitive')res.end(base+'<h1>Public form</h1><input type="password" value="synthetic-hidden-field" style="position:absolute;left:-9999px"><script>document.querySelectorAll=()=>[]</script>');else if(req.url==='/closed-shadow')res.end(base+`<div id=host></div><script>document.getElementById("host").attachShadow({mode:"closed"}).innerHTML='<input type="password" value="synthetic-closed-shadow-password">';document.querySelectorAll=()=>[]</script>`);else if(req.url==='/open-shadow')res.end(base+`<div id=host></div><script>document.getElementById("host").attachShadow({mode:"open"}).innerHTML='<p style="margin:70px;font:28px Arial">Public shadow content</p>'</script>`);else if(req.url==='/frames')res.end(base+'<h1>Public parent</h1><iframe src="/public"></iframe>');else res.end(base+'<h1>Public approved visual page</h1><p style="margin:70px">Research and documentation workspace.</p>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
const sink=createServer({cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')},(req,res)=>{if(req.url==='/av-stats'){res.setHeader('content-type','application/json');res.end(JSON.stringify(avFacts));return;}if(req.url==='/av-block')blockPNG=true;else if(['/public','/canvas','/sensitive','/closed-shadow','/open-shadow','/frames'].includes(req.url))blockPNG=false;res.setHeader('content-type','text/html');const base='<!doctype html><meta name="viewport" content="width=device-width"><style>body{margin:0;background:white;color:#123;font:28px Arial}h1{margin:70px}canvas{display:block}</style><title>Public visual fixture</title>';if(req.url==='/canvas')res.end(base+'<canvas width="1200" height="360"></canvas><script>const c=document.querySelector("canvas").getContext("2d");c.fillStyle="white";c.fillRect(0,0,1200,360);c.fillStyle="black";c.font="bold 48px Arial";c.fillText("CLASSIFIED ORCHID DOCUMENT",40,170);</script>');else if(req.url==='/sensitive')res.end(base+'<h1>Public form</h1><input type="password" value="synthetic-hidden-field" style="position:absolute;left:-9999px"><script>document.querySelectorAll=()=>[]</script>');else if(req.url==='/closed-shadow')res.end(base+`<div id=host></div><script>document.getElementById("host").attachShadow({mode:"closed"}).innerHTML='<input type="password" value="synthetic-closed-shadow-password">';document.querySelectorAll=()=>[]</script>`);else if(req.url==='/open-shadow')res.end(base+`<div id=host></div><script>document.getElementById("host").attachShadow({mode:"open"}).innerHTML='<p style="margin:70px;font:28px Arial">Public shadow content</p>'</script>`);else if(req.url==='/frames')res.end(base+'<h1>Public parent</h1><iframe src="/public"></iframe>');else res.end(base+'<h1>Public approved visual page</h1><p style="margin:70px">Research and documentation workspace.</p>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
const origin=`https://127.0.0.1:${sink.address().port}`,store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');
|
||||
const p=store.get('policy');p.browser.homeUrl=origin+'/public';p.inspection.documentAnalysisEnabled=false;p.dlp.rules.push({id:'visual-confidential-fixture',label:'Synthetic visual confidential rule',kind:'literal',value:'CLASSIFIED ORCHID DOCUMENT',action:'block'});
|
||||
p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${av.address().port}/scan`,scanUploads:true,scanDownloads:false,scanClipboard:false,scanAI:false};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
// All wrappers observe the real native guards; they never replace enforcement.
|
||||
const {app,BrowserWindow,session}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),https=require('node:https'),{X509Certificate}=require('node:crypto');
|
||||
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();
|
||||
const origin=process.env.SAFETYNET_SMOKE_ORIGIN,forbiddenOrigin=process.env.SAFETYNET_SMOKE_FORBIDDEN_ORIGIN,fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256;
|
||||
const nativeSessions=new WeakMap(),records=[],requests=[],fromPartition=session.fromPartition.bind(session);
|
||||
session.fromPartition=(...args)=>{const s=fromPartition(...args);if(nativeSessions.has(s))return s;const record={index:records.length,partitionOwned:typeof args[0]==='string'&&args[0].startsWith('safetynet-agent-'),hooks:[],firstRequestHooks:null};records.push(record);nativeSessions.set(s,record);s.setCertificateVerifyProc((req,cb)=>{let valid=false;try{valid=['127.0.0.1','127.0.0.2'].includes(req.hostname)&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint;}catch{}cb(valid?0:-2);});
|
||||
for(const name of ['onBeforeRequest','onBeforeSendHeaders','onBeforeRedirect','onCompleted','onErrorOccurred']){const original=s.webRequest[name].bind(s.webRequest);s.webRequest[name]=(...values)=>{const index=values.length-1,listener=values[index];if(typeof listener==='function'){record.hooks.push(name);values[index]=(...eventArgs)=>{const d=eventArgs[0];if(name==='onBeforeRequest'&&d.url?.startsWith(origin)){record.firstRequestHooks??=[...record.hooks];if(d.url.includes('/worker/'))requests.push({nativeSession:record.index,requestId:d.id,hasWebContents:Number.isInteger(d.webContentsId)&&d.webContentsId>0,hasFrame:!!d.frame,method:d.method,bytes:(d.uploadData||[]).reduce((n,v)=>n+(v.bytes?.length||0),0),sameInitiator:d.initiator===origin});}return listener(...eventArgs);};}return original(...values);};}
|
||||
for(const name of ['setPermissionRequestHandler','setPermissionCheckHandler','registerPreloadScript'])if(typeof s[name]==='function'){const original=s[name].bind(s);s[name]=(...values)=>{record.hooks.push(name);return original(...values);};}
|
||||
const proxy=s.setProxy.bind(s);s.setProxy=async(...values)=>{const result=await proxy(...values);record.hooks.push('proxyReady');return result;};return s;};
|
||||
const localWitnesses=[],inspection=require('../apps/browser/inspection.cjs'),startInspector=inspection.startInspector;inspection.startInspector=(...args)=>{const client=startInspector(...args),inspect=client.inspect.bind(client);client.inspect=async(bytes,policy)=>{const result=await inspect(bytes,policy);localWitnesses.push({decision:result.decision,ruleId:result.ruleId==='worker-yara-fixture'?'worker-yara-fixture':null,complete:result.coverage?.complete===true});return result;};return client;};
|
||||
require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms)),base=process.env.SAFETYNET_SMOKE_SERVER,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN,'content-type':'application/json'};
|
||||
async function until(fn){for(let i=0;i<250;i++){const value=await fn();if(value)return value;await wait(100);}throw Error('Native worker fixture timeout');}
|
||||
const 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);});
|
||||
app.whenReady().then(async()=>{const pages=[];let stage='startup';try{const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);assert.equal(result.ok,true,method);return result.data;},overview=async()=>(await fetch(base+'/api/admin/overview',{headers})).json();await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});const human=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;pages.push(human);await until(()=>!human.isLoading()&&human.getURL()===origin+'/app');
|
||||
const connections={a:await ui('agentConnection','worker-a'),b:await ui('agentConnection','worker-b')};async function call(actor,action,args={}){const requestFile=path.join(app.getPath('userData'),'worker-request-'+actor+'.json');fs.writeFileSync(requestFile,JSON.stringify({action,args}));const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connections[actor].credentialPath,'--request',requestFile],{windowsHide:true,stdio:['ignore','pipe','pipe']});let output='';child.stdout.on('data',v=>output+=v);child.stderr.on('data',()=>{});const exit=await new Promise(r=>{child.once('exit',r);child.once('error',()=>r(1));});return {exit,result:exit===0?JSON.parse(output).result:undefined};}
|
||||
const change=async modify=>{const p=(await overview()).policy;modify(p);p.version++;const proposed=await fetch(base+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(p)});assert.equal(proposed.status,202);const review=await proposed.json();const approved=await fetch(base+`/api/admin/policy-reviews/${review.reviewId}/approve`,{method:'POST',headers:{cookie:'sn_session='+process.env.SAFETYNET_SMOKE_REVIEWER,origin:base,'content-type':'application/json'},body:JSON.stringify({payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true})});assert.equal(approved.status,200);await until(async()=>{const state=await ui('state');return state.ready&&state.policyVersion===p.version;});};
|
||||
stage='owned native tab creation';const owned={};for(const actor of ['a','b']){const response=await call(actor,'tabs:create',{url:origin+'/seed-'+actor});assert.equal(response.exit,0,'Owned native creation refused');assert.ok(response.result.sessionId);const page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;pages.push(page);await until(()=>!page.isLoading()&&page.getURL()===origin+'/seed-'+actor);owned[actor]={id:response.result.sessionId,page,session:page.session,record:nativeSessions.get(page.session)};assert.ok(owned[actor].record?.partitionOwned,'Actor uses immutable owned partition');assert.equal((await call(actor,'navigate',{sessionId:owned[actor].id,url:origin+'/app'})).exit,0);await until(()=>!page.isLoading()&&page.getURL()===origin+'/app');}
|
||||
assert.notEqual(owned.a.session,owned.b.session);assert.notEqual(owned.a.session,human.session);for(const actor of ['a','b']){const cookies=await owned[actor].session.cookies.get({url:origin,name:'worker-session'});assert.equal(cookies.length,1);assert.equal(cookies[0].value,actor.toUpperCase());const installed=owned[actor].record.firstRequestHooks;for(const hook of ['onBeforeRequest','onBeforeSendHeaders','onBeforeRedirect','onCompleted','onErrorOccurred','setPermissionRequestHandler','setPermissionCheckHandler','registerPreloadScript','proxyReady'])assert.ok(installed.includes(hook),'Missing guard before first native request: '+hook);}
|
||||
stage='cross actor control denied';assert.equal((await call('b','read',{sessionId:owned.a.id})).exit,1);assert.equal((await call('b','navigate',{sessionId:owned.a.id,url:origin+'/app'})).exit,1);
|
||||
const register=page=>page.executeJavaScript(`(async()=>{try{await navigator.serviceWorker.register('/sw.js',{scope:'/'});await Promise.race([navigator.serviceWorker.ready,new Promise((_,r)=>setTimeout(()=>r(Error('deadline')),5000))]);if(!navigator.serviceWorker.controller)await new Promise(r=>{const t=setTimeout(r,2000);navigator.serviceWorker.addEventListener('controllerchange',()=>{clearTimeout(t);r();},{once:true});});return !!navigator.serviceWorker.controller;}catch{return false;}})()`);
|
||||
const send=(page,url,body)=>page.executeJavaScript(`new Promise(resolve=>{const worker=navigator.serviceWorker.controller;if(!worker){resolve({ok:false});return;}const c=new MessageChannel(),t=setTimeout(()=>resolve({ok:false}),5000);c.port1.onmessage=e=>{clearTimeout(t);resolve({ok:e.data.ok===true});};worker.postMessage(${JSON.stringify({url,body})},[c.port2]);})`);
|
||||
stage='background gate defaults off';if(await register(owned.a.page))assert.equal((await send(owned.a.page,origin+'/worker/a','public-worker-message')).ok,false);assert.equal((await getFacts()).facts.publicA,0);
|
||||
stage='independent background policy enable';await change(p=>p.browser.allowAgentBackgroundWorkers=true);for(const actor of ['a','b']){await until(()=>!owned[actor].page.isLoading());assert.equal(await register(owned[actor].page),true,'Owned worker registration unavailable');}
|
||||
stage='both native workers public requests allowed';for(const actor of ['a','b'])assert.equal((await send(owned[actor].page,origin+'/worker/'+actor,'public-worker-message')).ok,true);const workerRequests=requests.filter(r=>!r.hasWebContents&&!r.hasFrame);assert.ok(workerRequests.some(r=>r.nativeSession===owned.a.record.index));assert.ok(workerRequests.some(r=>r.nativeSession===owned.b.record.index));
|
||||
stage='core exact body blocks';assert.equal((await send(owned.a.page,origin+'/worker/a','CORE_COMPANY_WORKER_LITERAL')).ok,false);stage='real local YARA body blocks';assert.equal((await send(owned.a.page,origin+'/worker/a','YARA_WORKER_ONLY_SECRET')).ok,false);assert.ok(localWitnesses.some(w=>w.decision==='block'&&w.ruleId==='worker-yara-fixture'));stage='real ICAP body blocks';assert.equal((await send(owned.a.page,origin+'/worker/a','ICAP_WORKER_ONLY_SECRET')).ok,false);stage='worker destination outside grant denied';assert.equal((await send(owned.a.page,forbiddenOrigin+'/forbidden','public-worker-message')).ok,false);
|
||||
stage='human worker remains ownerless denied';const humanRegistered=await register(human);if(humanRegistered)assert.equal((await send(human,forbiddenOrigin+'/human','public-worker-message')).ok,false);
|
||||
stage='scope revoke denies running owned worker';await change(p=>p.agents.grants.find(g=>g.id==='worker-a').scopes=p.agents.grants.find(g=>g.id==='worker-a').scopes.filter(s=>s!=='network:background'));if(!owned.a.page.isDestroyed())assert.equal((await send(owned.a.page,origin+'/worker/a','public-worker-message')).ok,false);let staleAllowed=false;try{const response=await owned.a.session.fetch(origin+'/worker/a',{method:'POST',body:'public-worker-message'});staleAllowed=response.ok;}catch{}assert.equal(staleAllowed,false);assert.equal((await send(owned.b.page,origin+'/worker/b','public-worker-message')).ok,true);const bCookie=await owned.b.session.cookies.get({url:origin,name:'worker-session'});assert.equal(bCookie.length,1);assert.equal(bCookie[0].value,'B');
|
||||
stage='independent receiver validates absence';const receiver=await getFacts();assert.equal(receiver.facts.publicA,1);assert.equal(receiver.facts.publicB,2);for(const name of ['core','yara','icap','forbidden'])assert.equal(receiver.facts[name],0);assert.ok(receiver.avFacts.workerScans>=3);assert.equal(receiver.avFacts.blocks,1);
|
||||
stage='actual worker observability';await wait(1500);const events=[];let after=0;for(let batch=0;batch<20;batch++){const response=await fetch(base+`/api/admin/events/export?after=${after}&limit=1000&format=ecs`,{headers});assert.equal(response.status,200);const rows=(await response.text()).trim().split('\n').filter(Boolean).map(line=>JSON.parse(line));for(const row of rows){assert.ok(!/CORE_COMPANY_WORKER_LITERAL|YARA_WORKER_ONLY_SECRET|ICAP_WORKER_ONLY_SECRET|worker-session=/.test(JSON.stringify(row)));events.push(row);}if(rows.length<1000)break;const next=Number(response.headers.get('x-safetynet-next-cursor'));assert.ok(next>after);after=next;}const workerEvents=events.filter(e=>e.safetynet?.agent?.action==='network:background'&&e.trace?.id);for(const principal of ['worker-a','worker-b'])assert.ok(workerEvents.some(e=>e.safetynet.agent.principal_id===principal&&e.safetynet.agent.credential_generation===1&&e.safetynet.agent.outcome==='authorized'),'Missing durable native worker owner');assert.ok(workerEvents.some(e=>e.safetynet.agent.principal_id==='worker-a'&&e.safetynet.agent.outcome==='denied'),'Revoked worker denial missing owner attribution');const traced=workerEvents.filter(e=>e.safetynet.agent.outcome==='authorized');let trace,selectedTraceId;for(const event of traced.reverse()){const candidate=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.trace.id)).json();if(candidate.spans?.some(s=>s.name==='browser.request')&&candidate.spans.some(s=>s.name==='browser.inspect.local')){trace=candidate;selectedTraceId=event.trace.id;break;}}assert.ok(trace,'Native worker trace absent');assert.ok(trace.spans.some(s=>s.name==='browser.worker'),'Worker ownership stage absent');const spanMap=new Map(trace.spans.map(s=>[s.spanId,s]));const workerSpan=trace.spans.find(s=>s.name==='browser.worker');let ancestor=workerSpan,parented=false;const seen=new Set();while(ancestor&&!seen.has(ancestor.spanId)){if(ancestor.name==='browser.request'){parented=true;break;}seen.add(ancestor.spanId);ancestor=spanMap.get(ancestor.parentSpanId);}assert.ok(parented,'Native worker authorization is not correlated beneath browser.request');assert.ok(!/CORE_COMPANY_WORKER_LITERAL|YARA_WORKER_ONLY_SECRET|ICAP_WORKER_ONLY_SECRET/.test(JSON.stringify(trace)));const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0);const summary=await(await fetch('http://127.0.0.1:4318/api/summary')).json();
|
||||
fs.writeFileSync(path.resolve('artifacts/agent-worker-ownership-native-proof.json'),JSON.stringify({actualElectron:true,exactPinnedTLS:true,independentlyPublished:true,immutableNativePartitions:true,cookiesIsolated:true,crossActorAccessDenied:true,hooksInstalledBeforeFirstRequest:true,backgroundDefaultDenied:true,workerNativeAttribution:workerRequests,humanRegistered,humanBackgroundDenied:true,coreBodyBlocked:true,actualYARABlocked:true,actualICAPBlocked:true,forbiddenHostDenied:true,existingWorkerScopeRevocationEnforced:true,staleNativeSessionDenied:true,otherActorCookiePreserved:true,receiver,records,localWitnesses,traceId:selectedTraceId,workerAuditFacts:workerEvents.map(e=>({action:e.safetynet.agent.action,outcome:e.safetynet.agent.outcome,principal:e.safetynet.agent.principal_id,generation:e.safetynet.agent.credential_generation,traceId:e.trace.id})),stages:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs})),summary,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}},null,2));console.log('Native agent worker ownership proof passed');for(const page of pages)if(!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0);
|
||||
}catch(error){fs.writeFileSync(path.resolve('artifacts/agent-worker-ownership-native-error.json'),JSON.stringify({stage,name:error.name,message:error.message,records,requests,localWitnesses},null,2));console.error('Native worker proof failed at '+stage+': '+error.name);for(const page of pages)if(!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Native worker proof: real policy publication, DLP, AV and independent receivers.
|
||||
import {createServer} from 'node:https';
|
||||
import {createRequire} from 'node:module';
|
||||
import {mkdtempSync,mkdirSync,rmSync,readFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join,resolve,sep} from 'node:path';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import net from 'node:net';
|
||||
import {createApp} from '../apps/server/app.mjs';
|
||||
import {openStore,digest} from '../apps/server/store.mjs';
|
||||
import {startTelemetry} from '../apps/server/telemetry.mjs';
|
||||
const collector='http://127.0.0.1:4318';if(!(await fetch(collector+'/health')).ok)throw Error('Protected collector unavailable');
|
||||
process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry();
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-worker-owner-'));mkdirSync('artifacts',{recursive:true});
|
||||
if(!resolve(temporary).startsWith(resolve(tmpdir())+sep)||!temporary.split(/[\\/]/).at(-1).startsWith('safetynet-worker-owner-'))throw Error('Unsafe temporary fixture path');
|
||||
const markers={core:'CORE_COMPANY_WORKER_LITERAL',yara:'YARA_WORKER_ONLY_SECRET',icap:'ICAP_WORKER_ONLY_SECRET'},facts={publicA:0,publicB:0,core:0,yara:0,icap:0,forbidden:0},avFacts={scans:0,workerScans:0,blocks:0},avSockets=new Set();
|
||||
const av=net.createServer(socket=>{avSockets.add(socket);socket.on('close',()=>avSockets.delete(socket));socket.on('error',()=>{});let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(input.length>4194304){input.fill(0);socket.destroy();return;}if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options){input.fill(0);socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nAllow: 204\r\nISTag: "worker-fixture"\r\nEncapsulated: null-body=0\r\n\r\n');return;}avFacts.scans++;if(input.includes(Buffer.from('public-worker-message')))avFacts.workerScans++;const blocked=input.includes(Buffer.from(markers.icap));if(blocked)avFacts.blocks++;input.fill(0);socket.end(blocked?'ICAP/1.0 204 No Content\r\nISTag: "worker-fixture"\r\nX-Infection-Found: Type=0; Threat=synthetic-worker;\r\n\r\n':'ICAP/1.0 204 No Content\r\nISTag: "worker-fixture"\r\n\r\n');});}).listen(0,'127.0.0.1');await new Promise(r=>av.once('listening',r));
|
||||
const tls={cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')};
|
||||
const forbidden=createServer(tls,(req,res)=>{facts.forbidden++;req.resume();res.end('public');}).listen(0,'127.0.0.2');await new Promise(r=>forbidden.once('listening',r));
|
||||
const forbiddenOrigin=`https://127.0.0.2:${forbidden.address().port}`;
|
||||
const worker=`self.addEventListener('install',e=>e.waitUntil(self.skipWaiting()));self.addEventListener('activate',e=>e.waitUntil(self.clients.claim()));self.addEventListener('message',e=>{const p=e.ports[0];e.waitUntil((async()=>{try{const r=await fetch(e.data.url,{method:'POST',headers:{'content-type':'text/plain'},body:e.data.body,credentials:'include'});p.postMessage({ok:r.ok});}catch{p.postMessage({ok:false});}})());});`;
|
||||
const sink=createServer(tls,(req,res)=>{if(req.url==='/facts'){res.setHeader('content-type','application/json');res.end(JSON.stringify({facts,avFacts}));return;}if(req.url==='/sw.js'){res.setHeader('content-type','application/javascript');res.setHeader('service-worker-allowed','/');res.end(worker);return;}if(req.method==='POST'){let chunks=[],size=0;req.on('data',chunk=>{size+=chunk.length;if(size>65536){req.destroy();return;}chunks.push(chunk);});req.on('end',()=>{const body=Buffer.concat(chunks);for(const [kind,marker]of Object.entries(markers))if(body.includes(Buffer.from(marker)))facts[kind]++;if(body.equals(Buffer.from('public-worker-message'))){if(req.url==='/worker/a')facts.publicA++;else if(req.url==='/worker/b')facts.publicB++;}for(const chunk of chunks)chunk.fill(0);body.fill(0);chunks=[];res.end('accepted');});return;}if(req.url==='/seed-a')res.setHeader('set-cookie','worker-session=A; Secure; HttpOnly; SameSite=Strict; Path=/');if(req.url==='/seed-b')res.setHeader('set-cookie','worker-session=B; Secure; HttpOnly; SameSite=Strict; Path=/');res.setHeader('content-type','text/html');res.end('<!doctype html><title>Public worker fixture</title><h1>Public worker ownership fixture</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
const origin=`https://127.0.0.1:${sink.address().port}`,store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');
|
||||
const p=store.get('policy');p.browser.homeUrl=origin+'/app';p.browser.allowAgentBackgroundWorkers=false;p.dlp.rules.push({id:'worker-core-fixture',label:'Synthetic confidential worker content',kind:'literal',value:markers.core,action:'block'});p.security.yara.enabled=true;p.security.yara.rules=[{id:'worker-yara-fixture',name:'Synthetic worker YARA',source:`rule worker_yara_fixture { strings: $a = "${markers.yara}" condition: $a }`}];p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${av.address().port}/scan`,scanUploads:true,scanDownloads:false,scanClipboard:false,scanAI:false};
|
||||
const scopes=['sessions','navigate','read','tabs:create','network:background'];p.agents={...p.agents,enabled:true,identityMode:'individual',scopes,allowedHosts:['127.0.0.1'],grants:['worker-a','worker-b'].map(id=>({id,name:id,enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes,allowedHosts:['127.0.0.1'],generation:1}))};p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:collector,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'worker-fixture');
|
||||
const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement:true,requireIndependentPolicyApproval:true};const management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;const reviewer=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(reviewer),Date.now()+600000,JSON.stringify({sub:'independent-worker-reviewer',role:'admin'}));
|
||||
const child=spawn(createRequire(import.meta.url)('electron'),['scripts/agent-worker-ownership-smoke.cjs'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_REVIEWER:reviewer,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ORIGIN:origin,SAFETYNET_SMOKE_FORBIDDEN_ORIGIN:forbiddenOrigin}});child.stdout.on('data',v=>process.stdout.write(v));child.stderr.on('data',v=>process.stderr.write(v));const deadline=setTimeout(()=>child.kill(),240000);const exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(deadline);
|
||||
for(const socket of avSockets)socket.destroy();await new Promise(r=>av.close(r));for(const server of [management,sink,forbidden])server.closeAllConnections();await Promise.all([management,sink,forbidden].map(server=>new Promise(r=>server.close(r))));store.db.close();await telemetry?.shutdown();rmSync(temporary,{recursive:true,force:true});process.exit(exit);
|
||||
@@ -1,7 +1,6 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
@@ -22,5 +21,6 @@ try{
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.deepEqual(policy.browser.bookmarks.managed.map(({title,url,folder})=>({title,url,folder})),[{title:'Company intranet',url:'https://intranet.synthetic.example/',folder:'Company'}]);assert.equal(policy.browser.bookmarks.adminReadUserBookmarks,false);assert.equal(store.personalBookmarks('synthetic-bookmark-device').revision,1);assert.equal(store.personalBookmarks('synthetic-bookmark-device').items[0].title,'Remote personal reference');
|
||||
const report={actualAdministratorUi:true,managedTableRoundtrip:true,explicitPublication:true,deviceReadEdit:true,revisionCompared:true,deviceEditGlobalPolicyUnchanged:true,privacyRevoked:true,nodeUnavailable:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/bookmarks-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-bookmarks-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -6,7 +6,7 @@ app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardware
|
||||
app.whenReady().then(()=>{const requests=session.fromPartition('persist:safetynet-managed').webRequest,original=requests.onBeforeSendHeaders.bind(requests);requests.onBeforeSendHeaders=(filter,listener)=>original(filter,(details,callback)=>{if(details.url.endsWith('/public')){const entries=Object.entries(details.requestHeaders),one=entries.find(([name])=>name.toLowerCase()==='x-credential-one'),two=entries.find(([name])=>name.toLowerCase()==='x-credential-two'),known='fixtureControlledCredentialDelta0123456789';if(one&&two){headerFacts.push({names:entries.map(([name])=>name),fragmentOneIndex:entries.indexOf(one),fragmentTwoIndex:entries.indexOf(two),fullJoinMatch:entries.map(([,value])=>value).join('').includes(known),pairJoinMatch:(one[1]+two[1])===known,reversePairJoinMatch:(two[1]+one[1])===known});fs.writeFileSync(path.resolve('artifacts/controlled-login-header-facts.json'),JSON.stringify(headerFacts,null,2));}}if(fixtureCredentialHeader&&details.resourceType==='mainFrame'&&details.method==='POST')details.requestHeaders['X-Synthetic-Credential']='sk-syntheticControlledLoginCredential0123456789';listener(details,callback);});});require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms)),base=process.env.SAFETYNET_SMOKE_SERVER,origin=process.env.SAFETYNET_SMOKE_ORIGIN,headers={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`,'content-type':'application/json'};
|
||||
async function until(fn){for(let i=0;i<200;i++){const r=await fn();if(r)return r;await wait(100);}throw Error('Controlled login fixture timeout');}
|
||||
app.whenReady().then(async()=>{const pages=[];let stage='startup';try{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)=>callback(req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint?0:-2));const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);if(!result.ok)throw Object.assign(Error('Native UI operation failed'),{operation:method,reason:/not managed|not ready|paused/i.test(result.error||'')?'not-ready':/context.*changed/i.test(result.error||'')?'context-changed':/BLOCKED_BY_CLIENT/i.test(result.error||'')?'native-request-blocked':/ERR_ABORTED/i.test(result.error||'')?'native-navigation-aborted':'ui-operation-refused'});return result.data;};const overview=async()=>(await fetch(base+'/api/admin/overview',{headers})).json();stage='enrollment';await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});const page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;pages.push(page);await until(()=>!page.isLoading()&&page.getURL()===origin+'/login');const a=await ui('agentConnection','vault-a'),b=await ui('agentConnection','vault-b'),requestFile=path.join(app.getPath('userData'),'controlled-request.json');async function call(connection,action,args={}){fs.writeFileSync(requestFile,JSON.stringify({action,args}));const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.credentialPath,'--request',requestFile],{windowsHide:true,stdio:['ignore','pipe','pipe']});let output='';child.stdout.on('data',v=>output+=v);child.stderr.on('data',()=>{});const exit=await new Promise(r=>{child.once('exit',r);child.once('error',()=>r(1));});return {exit,result:exit===0?JSON.parse(output).result:undefined};}const sessionId=(await call(a,'sessions')).result[0].id,master='Synthetic master password for controlled native proof',password='sk-syntheticControlledLoginCredential0123456789',username='public-native-user',alternatePassword='fixtureControlledCredentialDelta0123456789';stage='human unlock';await ui('vaultCreate',master);const saved=await ui('vaultSave',{origin,username,password,label:'Approved synthetic login'});const reset=async(mode='200')=>{await ui('navigate',origin+'/login/'+mode);await until(()=>!page.isLoading()&&page.getURL()===origin+'/login/'+mode);await ui('vaultUnlock',master);win.focus();page.focus();await wait(100);};const stats=()=>new Promise((resolve,reject)=>{const req=require('node:https').get(origin+'/stats',{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')},res=>{let body='';res.on('data',v=>body+=v);res.on('end',()=>resolve(JSON.parse(body)));});req.on('error',reject);});const inventory=async()=>{const r=await call(a,'vault:list',{sessionId});assert.equal(r.exit,0);return r.result[0].credentialRef;};const submit=async()=>{const terminal=new Promise((resolve,reject)=>{const timer=setTimeout(()=>{page.removeListener('did-stop-loading',done);reject(Error('Native form navigation terminal timeout'));},22000);const done=()=>{clearTimeout(timer);resolve();};page.once('did-stop-loading',done);});await page.executeJavaScript("document.querySelector('form').requestSubmit()");await terminal;await wait(200);};const prepare=async()=>{const ref=await inventory(),r=await call(a,'vault:prepare-submit',{sessionId,credentialRef:ref});assert.equal(r.exit,0,'Agent prepare failed');assert.deepEqual(r.result,{completed:true,prepared:true});return ref;};const count=async()=>{const result=await fetch(base+'/api/admin/overview',{headers});assert.equal(result.status,200);return (await result.json()).events.filter(e=>e.kind==='vault.submission-authorized').length;};
|
||||
app.whenReady().then(async()=>{const pages=[];let stage='startup';try{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)=>callback(req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint?0:-2));const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);if(!result.ok)throw Object.assign(Error('Native UI operation failed'),{operation:method,reason:/not managed|not ready|paused/i.test(result.error||'')?'not-ready':/context.*changed/i.test(result.error||'')?'context-changed':/BLOCKED_BY_CLIENT/i.test(result.error||'')?'native-request-blocked':/ERR_ABORTED/i.test(result.error||'')?'native-navigation-aborted':'ui-operation-refused'});return result.data;};const overview=async()=>(await fetch(base+'/api/admin/overview',{headers})).json();stage='enrollment';await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});const page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;pages.push(page);await until(()=>!page.isLoading()&&page.getURL()===origin+'/login');const a=await ui('agentConnection','vault-a'),b=await ui('agentConnection','vault-b'),requestFile=path.join(app.getPath('userData'),'controlled-request.json');async function call(connection,action,args={}){fs.writeFileSync(requestFile,JSON.stringify({action,args}));const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.credentialPath,'--request',requestFile],{windowsHide:true,stdio:['ignore','pipe','pipe']});let output='';child.stdout.on('data',v=>output+=v);child.stderr.on('data',()=>{});const exit=await new Promise(r=>{child.once('exit',r);child.once('error',()=>r(1));});return {exit,result:exit===0?JSON.parse(output).result:undefined};}const sessionId=(await call(a,'sessions')).result[0].id,master='Synthetic master password for controlled native proof',password='sk-syntheticControlledLoginCredential0123456789',username='public-native-user',alternatePassword='fixtureControlledCredentialDelta0123456789';stage='human unlock';await ui('vaultCreate',master);const saved=await ui('vaultSave',{origin,username,password,label:'Approved synthetic login'});const reset=async(mode='200')=>{await ui('navigate',origin+'/login/'+mode);await until(()=>!page.isLoading()&&page.getURL()===origin+'/login/'+mode);await ui('vaultUnlock',master);win.focus();page.focus();await wait(100);};const stats=()=>new Promise((resolve,reject)=>{const req=require('node:https').get(origin+'/stats',{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')},res=>{let body='';res.on('data',v=>body+=v);res.on('end',()=>resolve(JSON.parse(body)));});req.on('error',reject);});const inventory=async()=>{const r=await call(a,'vault:list',{sessionId});assert.equal(r.exit,0);return r.result[0].credentialRef;};const submit=async()=>{const terminal=new Promise((resolve,reject)=>{const timer=setTimeout(()=>{page.removeListener('did-stop-loading',done);reject(Error('Native form navigation terminal timeout'));},22000);const done=()=>{clearTimeout(timer);resolve();};page.once('did-stop-loading',done);});await page.executeJavaScript("document.querySelector('form').requestSubmit()");await terminal;await wait(200);};const prepare=async()=>{const ref=await inventory(),r=await call(a,'vault:prepare-submit',{sessionId,credentialRef:ref});assert.equal(r.exit,0,'Agent prepare failed');assert.deepEqual(r.result,{completed:true,prepared:true});return ref;};
|
||||
stage='ordinary fill remains DLP blocked';await reset();assert.equal((await call(a,'vault:fill',{sessionId,credentialRef:await inventory()})).exit,0);await submit();assert.equal((await stats()).submitted,0);
|
||||
stage='agent prepare does not submit';await reset();let ref=await prepare();assert.equal((await stats()).submitted,0);assert.equal((await call(a,'vault:prepare-submit',{sessionId,credentialRef:ref})).exit,1);assert.equal((await call(b,'vault:prepare-submit',{sessionId,credentialRef:await inventory()})).exit,1);await submit();await until(()=>!page.isLoading()&&page.getURL()===origin+'/signin');assert.equal((await stats()).submitted,1);
|
||||
stage='controlled302 safeGET';await reset('302');await prepare();await submit();await until(()=>!page.isLoading()&&page.getURL()===origin+'/final');assert.equal((await stats()).submitted,2);assert.equal((await stats()).finalGets,1);
|
||||
|
||||
@@ -5,4 +5,5 @@ try{
|
||||
store=openStore(join(directory,'management'));const policy=store.get('policy');policy.browser.homeUrl=sinkUrl;policy.browser.allowedHosts=['127.0.0.1'];store.put('policy',policy);const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-developer');const config={adminToken,publicUrl:'http://127.0.0.1',secure:false};server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_DEVTOOLS_SERVER:config.publicUrl,SAFETYNET_DEVTOOLS_CODE:code,SAFETYNET_DEVTOOLS_ADMIN:adminToken,SAFETYNET_DEVTOOLS_SINK:sinkUrl,SAFETYNET_DEVTOOLS_USERDATA:join(directory,'browser'),SAFETYNET_SMOKE_USERDATA:join(directory,'browser')};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/devtools-profile-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',b=>process.stdout.write(b));child.stderr.on('data',b=>process.stderr.write(String(b).replaceAll(code,'[fixture enrollment]').replaceAll(adminToken,'[fixture admin]')));const timer=setTimeout(()=>child.kill(),90000);const exit=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(exit,0,'Native developer-profile proof failed');
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
}finally{child?.kill();for(const service of [server,sink])if(service)await new Promise(r=>{service.closeAllConnections();service.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-devtools-proof-'))throw Error('Unsafe fixture cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
||||
@@ -24,5 +24,6 @@ try{
|
||||
assert.equal(policy.diagnostics.enabled,true);assert.equal(policy.diagnostics.mode,'debug');assert.equal(policy.diagnostics.endpoint,'https://collector.synthetic.example');assert.ok(policy.agents.scopes.includes('bookmarks:mutate'));
|
||||
const report={signedDiagnosticsUi:true,newAgentScopesUi:true,adminUi:true,isolatedControlPlane:true,previewDidNotPublish:true,explicitSignedPublication:true,integrationFormRoundtrip:true,optionsOnlyProbe:true,probeContentSent:false,cspPreserved:true,nodeUnavailable:true,hiddenCpuWindow:true,policyVersion:policy.version,groupedNavigation:true,draftChangeReview:true,reviewDoesNotPublish:true,fleetAndAuditFilters:true,consoleThemePersistence:true,mobileNoHorizontalOverflow:true,readOnlyRoleControls:true,sessionInventoryUi:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/enterprise-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();for(const service of [server,icap])if(service)await new Promise(r=>{service.closeAllConnections?.();service.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-enterprise-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow,session}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),{X509Certificate}=require('node:crypto');
|
||||
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();const mode=process.env.SAFETYNET_WT_MODE,target=process.env.SAFETYNET_WT_TARGET,hashBytes=Array.from(Buffer.from(process.env.SAFETYNET_WT_HASH,'hex')),origin=process.env.SAFETYNET_SMOKE_ORIGIN;let actualDiagnostics,nativeWTCallbacks=0;
|
||||
if(mode==='managed'){const partition=session.fromPartition.bind(session),seenSessions=new WeakSet();session.fromPartition=(...args)=>{const current=partition(...args);if(!seenSessions.has(current)){seenSessions.add(current);const register=current.webRequest.onBeforeRequest.bind(current.webRequest);current.webRequest.onBeforeRequest=(...values)=>{const index=values.length-1,listener=values[index];if(typeof listener==='function')values[index]=(details,callback)=>{if(details.url?.startsWith(target))nativeWTCallbacks++;return listener(details,callback);};return register(...values);};}return current;};const diagnosticModule=require('../apps/browser/diagnostics.cjs'),createDiagnostics=diagnosticModule.createDiagnostics;diagnosticModule.createDiagnostics=(...args)=>{const value=createDiagnostics(...args);actualDiagnostics=value;return value;};require('../apps/browser/main.cjs');}else app.enableSandbox();
|
||||
// Executed in the real Chromium Window and dedicated Worker, without a shim.
|
||||
/* global WebTransport */
|
||||
async function attemptWT(url,hash){const result={api:typeof WebTransport==='function',connected:false,datagramWritten:false,streamWritten:false,errorClass:null};let transport;try{transport=new WebTransport(url,{serverCertificateHashes:[{algorithm:'sha-256',value:new Uint8Array(hash)}]});await Promise.race([transport.ready,new Promise((_,reject)=>setTimeout(()=>reject(Error('timeout')),5000))]);result.connected=true;const bytes=new TextEncoder().encode('SYNTHETIC_WEBTRANSPORT_DLP_MARKER'),datagram=transport.datagrams.writable.getWriter();await datagram.write(bytes);datagram.releaseLock();result.datagramWritten=true;const stream=await transport.createUnidirectionalStream(),writer=stream.getWriter();await writer.write(bytes);await writer.close();result.streamWritten=true;await new Promise(r=>setTimeout(r,350));}catch(error){result.errorClass=['Error','TypeError','WebTransportError','NetworkError'].includes(error.name)?error.name:'OtherError';}finally{transport?.close();}return result;}
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn){for(let i=0;i<300;i++){const value=await fn();if(value)return value;await wait(100);}throw Error('Full main WebTransport fixture timeout');}
|
||||
async function attemptRealms(page){const window=await page.executeJavaScript(`(${attemptWT.toString()})(${JSON.stringify(target)},${JSON.stringify(hashBytes)})`);const source=`const attempt=${attemptWT.toString()};onmessage=async e=>{postMessage(await attempt(e.data.url,e.data.hash));close();};`,worker=await page.executeJavaScript(`new Promise((resolve,reject)=>{const url=URL.createObjectURL(new Blob([${JSON.stringify(source)}],{type:'text/javascript'})),worker=new Worker(url),timer=setTimeout(()=>{worker.terminate();URL.revokeObjectURL(url);reject(Error('Worker timeout'));},8000);worker.onmessage=e=>{clearTimeout(timer);worker.terminate();URL.revokeObjectURL(url);resolve(e.data)};worker.onerror=()=>{clearTimeout(timer);worker.terminate();URL.revokeObjectURL(url);reject(Error('Worker fault'));};worker.postMessage({url:${JSON.stringify(target+'/worker')},hash:${JSON.stringify(hashBytes)}});})`);return {window,worker};}
|
||||
app.whenReady().then(async()=>{let page,win,stage='startup';try{const fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256;if(mode==='baseline'){session.defaultSession.setCertificateVerifyProc((req,cb)=>{let valid=false;try{valid=req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint;}catch{}cb(valid?0:-2);});let callbacks=0;session.defaultSession.webRequest.onBeforeRequest({urls:[target+'*']},(_details,callback)=>{callbacks++;callback({cancel:true});});win=new BrowserWindow({show:false,webPreferences:{sandbox:true,nodeIntegration:false,contextIsolation:true}});page=win.webContents;await win.loadURL(origin+'/workspace');const realms=await attemptRealms(page);assert.equal(callbacks,0);for(const realm of [realms.window,realms.worker])assert.ok(realm.connected&&realm.datagramWritten&&realm.streamWritten);fs.writeFileSync(process.env.SAFETYNET_WT_OUTPUT,JSON.stringify({actualElectron:true,mode,realms,webRequestCallbacks:callbacks,electron:process.versions.electron,chromium:process.versions.chrome},null,2));win.destroy();app.exit(0);return;}
|
||||
win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});for(const partition of ['persist:safetynet-managed','persist:safetynet-management'])session.fromPartition(partition).setCertificateVerifyProc((req,cb)=>{let valid=false;try{valid=req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint;}catch{}cb(valid?0:-2);});const base=process.env.SAFETYNET_SMOKE_SERVER,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN,'content-type':'application/json'},ui=async(method,value)=>{const response=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);assert.equal(response.ok,true);return response.data;};stage='actual enrollment';await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()&&page.getURL()===origin+'/workspace');const phases=[];const test=async name=>{stage=name;const before=await(await fetch(process.env.SAFETYNET_WT_STATS)).json(),resolvedProxy=await page.session.resolveProxy(target);assert.notEqual(resolvedProxy,'DIRECT');const realms=await attemptRealms(page);for(const realm of [realms.window,realms.worker])assert.ok(!realm.connected&&!realm.datagramWritten&&!realm.streamWritten);const after=await(await fetch(process.env.SAFETYNET_WT_STATS)).json();for(const field of ['packets','connections','datagrams','streams','bytes','workerBytes'])assert.equal(after[field],before[field],'WebTransport reached receiver');assert.equal(await page.executeJavaScript("fetch('/receiver',{method:'POST',body:'Public full main ordinary prompt'}).then(r=>r.ok).catch(()=>false)"),true);phases.push({name,policyVersion:(await ui('state')).policyVersion,proxyIsNonDirect:true,realms,receiverDelta:Object.fromEntries(['packets','connections','datagrams','streams','bytes','workerBytes'].map(key=>[key,after[key]-before[key]])),benignHTTPSPostAllowed:true});};await test('enrolled private relay');
|
||||
const change=async proxy=>{const p=(await(await fetch(base+'/api/admin/overview',{headers})).json()).policy;p.version++;p.proxy=proxy;const proposal=await fetch(base+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(p)});assert.equal(proposal.status,202);const review=await proposal.json();const approved=await fetch(base+`/api/admin/policy-reviews/${review.reviewId}/approve`,{method:'POST',headers:{cookie:'sn_session='+process.env.SAFETYNET_SMOKE_REVIEWER,origin:base,'content-type':'application/json'},body:JSON.stringify({payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true})});assert.equal(approved.status,200);await until(async()=>{const state=await ui('state');return state.ready&&state.policyVersion===p.version;});await until(()=>!page.isLoading()&&page.getURL()===origin+'/workspace');};stage='reviewed direct upstream policy';await change({mode:'direct'});await test('reviewed DIRECT upstream retains relay');stage='reviewed PAC direct upstream policy';await change({mode:'pac_script',pacUrl:origin+'/direct.pac'});await test('reviewed PAC DIRECT upstream retains relay');
|
||||
stage='actual ordinary request observability';assert.equal(nativeWTCallbacks,0);await actualDiagnostics.flush();await wait(1500);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);events.push(...rows);if(rows.length<1000)break;cursor=Number(response.headers.get('x-safetynet-next-cursor'));}const event=events.filter(e=>e.event.action==='upload.authorized'&&e.trace?.id).at(-1);assert.ok(event);const trace=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.trace.id)).json();assert.ok(trace.spans.some(s=>s.name==='browser.request'));const diagnostics=actualDiagnostics.status();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);fs.writeFileSync(process.env.SAFETYNET_WT_OUTPUT,JSON.stringify({actualFullMain:true,mode,phases,nativeWebRequestCallbacks:nativeWTCallbacks,ordinaryTraceId:event.trace.id,ordinaryStages:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs})),unsupportedTransportAuditGap:'Chromium WebTransport bypasses Electron webRequest; receiver absence is native containment evidence, not an attributed per-attempt DLP audit event.',diagnostics,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}},null,2));console.log('Full enrolled main WebTransport protection passed');require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0);
|
||||
}catch(error){fs.writeFileSync(path.resolve('artifacts/fullmain-wt-native-error.json'),JSON.stringify({mode,stage,name:error.name},null,2));console.error('Full main WebTransport failed at '+stage+': '+error.name);if(mode==='managed'&&page&&!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});
|
||||
@@ -0,0 +1,21 @@
|
||||
// Real Window/Worker QUIC control, then enrolled production main across policies.
|
||||
import assert from 'node:assert/strict';
|
||||
import {createRequire} from 'node:module';
|
||||
import {createServer} from 'node:https';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {randomBytes,createHash} from 'node:crypto';
|
||||
import {mkdtempSync,mkdirSync,readFileSync,writeFileSync,rmSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join,resolve,sep} from 'node:path';
|
||||
import {createApp} from '../apps/server/app.mjs';
|
||||
import {openStore,digest} from '../apps/server/store.mjs';
|
||||
import {startTelemetry} from '../apps/server/telemetry.mjs';
|
||||
const collector='http://127.0.0.1:4318';assert.equal((await fetch(collector+'/health')).ok,true);process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry(),root=mkdtempSync(join(tmpdir(),'safetynet-fullmain-wt-'));mkdirSync('artifacts',{recursive:true});assert.ok(resolve(root).startsWith(resolve(tmpdir())+sep)&&root.split(/[\\/]/).at(-1).startsWith('safetynet-fullmain-wt-'));
|
||||
const files=['apps/browser/main.cjs','apps/browser/startup-security.cjs','apps/browser/local-dlp-relay.cjs','apps/browser/diagnostics.cjs','packages/core/policy.mjs','package.json'],sourceHashFacts=Object.fromEntries(files.map(file=>[file,createHash('sha256').update(readFileSync(file)).digest('hex')]));let python,page,management,store,child,stage='receiver startup';const ordinary={posts:0,pacRequests:0};
|
||||
try{python=spawn('python',['scripts/webtransport-receiver.py',root],{env:{...process.env,PYTHONPATH:resolve('artifacts/webtransport-python')},windowsHide:true,stdio:['ignore','pipe','pipe']});python.stderr.on('data',()=>{});const metadata=await new Promise((accept,reject)=>{let text='';const timer=setTimeout(()=>reject(Error('Pinned QUIC receiver timeout')),15000);python.stdout.on('data',chunk=>{text+=chunk;try{const value=JSON.parse(text.split('\n')[0]);clearTimeout(timer);accept(value);}catch{}});python.once('error',()=>reject(Error('Pinned QUIC receiver startup refused')));python.once('exit',()=>{clearTimeout(timer);reject(Error('Pinned QUIC receiver exited'));});});const stats=`http://127.0.0.1:${metadata.statsPort}/facts`,target=`https://127.0.0.1:${metadata.quicPort}/webtransport`;
|
||||
page=createServer({cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')},(req,res)=>{if(req.url==='/direct.pac'){ordinary.pacRequests++;res.setHeader('content-type','application/x-ns-proxy-autoconfig');res.end('function FindProxyForURL(url,host) { return "DIRECT"; }');return;}if(req.method==='POST'){ordinary.posts++;req.resume();res.end('accepted');return;}res.setHeader('content-type','text/html');res.end('<!doctype html><title>Native transport fixture</title><h1>Public native transport workspace</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>page.once('listening',r));const origin=`https://127.0.0.1:${page.address().port}`,env={...process.env,SAFETYNET_WT_TARGET:target,SAFETYNET_WT_HASH:metadata.hash,SAFETYNET_WT_STATS:stats,SAFETYNET_SMOKE_ORIGIN:origin};delete env.ELECTRON_RUN_AS_NODE;
|
||||
const run=async(mode,extra={})=>{const output=join(root,mode+'.json');child=spawn(createRequire(import.meta.url)('electron'),['scripts/fullmain-wt-native.cjs'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...env,SAFETYNET_WT_MODE:mode,SAFETYNET_SMOKE_USERDATA:join(root,mode+'-profile'),SAFETYNET_WT_OUTPUT:output,...extra}});child.stdout.on('data',chunk=>process.stdout.write(chunk));child.stderr.on('data',chunk=>process.stderr.write(chunk));const timer=setTimeout(()=>child.kill(),120000),exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(exit,0,'Full main native transport phase failed');return JSON.parse(readFileSync(output,'utf8'));};
|
||||
stage='real baseline delivery';const before=await(await fetch(stats)).json(),baseline=await run('baseline'),after=await(await fetch(stats)).json();assert.ok(after.bytes-before.bytes>=132);assert.ok(after.workerBytes-before.workerBytes>=66);assert.ok(after.datagrams>before.datagrams&&after.streams>before.streams&&after.marker);const baselineReceiverDelta=Object.fromEntries(['packets','connections','datagrams','streams','bytes','workerBytes'].map(key=>[key,after[key]-before[key]]));
|
||||
store=openStore(join(root,'db'));const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url'),reviewer=randomBytes(32).toString('base64url'),policy=store.get('policy');policy.browser.homeUrl=origin+'/workspace';policy.browser.allowedHosts=['127.0.0.1'];policy.agents.enabled=false;policy.diagnostics={...policy.diagnostics,enabled:true,mode:'debug',endpoint:collector,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',policy);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'fullmain-wt-fixture');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(reviewer),Date.now()+600000,JSON.stringify({sub:'independent-wt-reviewer',role:'admin'}));const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement:true,requireIndependentPolicyApproval:true};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
stage='real enrolled main and reviewed policies';const managed=await run('managed',{SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_REVIEWER:reviewer});assert.equal(managed.phases.length,3);assert.equal(ordinary.posts,3);assert.ok(ordinary.pacRequests>=1,'PAC was not actually retrieved');for(const file of files)assert.equal(createHash('sha256').update(readFileSync(file)).digest('hex'),sourceHashFacts[file],'Production source changed during native proof');const report={actualFullMain:true,actualPinnedHTTP3Receiver:true,aioquicVersion:metadata.aioquic,sourceHashFacts,baseline,baselineReceiverDelta,managed,ordinary,unsupportedTransportAuditGap:managed.unsupportedTransportAuditGap};writeFileSync('artifacts/fullmain-webtransport-native-proof.json',JSON.stringify(report,null,2));console.log('Full main pinned Window/Worker WebTransport proof passed');
|
||||
}catch(error){writeFileSync('artifacts/fullmain-wt-harness-error.json',JSON.stringify({stage,name:error.name},null,2));console.error('Full main WT harness failed at '+stage+': '+error.name);process.exitCode=1;}finally{child?.kill();python?.kill();for(const server of [page,management])if(server){server.closeAllConnections();await new Promise(r=>server.close(r));}store?.db.close();await telemetry?.shutdown();rmSync(root,{recursive:true,force:true});}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
@@ -24,6 +23,7 @@ try{
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,config.requireIndependentPolicyApproval?2:3);const group=store.getRaw('policyGroups').find(group=>group.id==='engineering');assert.equal(group.rollout.stage,config.requireIndependentPolicyApproval?'pilot':'all');assert.deepEqual([...group.memberDeviceIds].sort(),['member-device','pilot-device']);assert.equal(group.restrictions.disableAI,true);
|
||||
const report={actualAdministratorUi:true,explicitGroupMembership:true,pilotCohortPublished:true,noNode:true,cspPreserved:true,...(config.requireIndependentPolicyApproval?{operatorProposalLeftPolicyUnchanged:true,exactMembersCohortRestrictionsReviewed:true,distinctAdministratorApproval:true}:{effectivePreview:true,explicitExpansion:true,reportedVersionDisplayed:true})};mkdirSync('artifacts',{recursive:true});writeFileSync(config.requireIndependentPolicyApproval?'artifacts/groups-review-admin-proof.json':'artifacts/groups-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-groups-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
@@ -10,7 +9,6 @@ import { randomBytes } from 'node:crypto';
|
||||
import { digest } from '../apps/server/store.mjs';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-incident-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
@@ -24,6 +22,7 @@ try{
|
||||
const row=store.db.prepare('SELECT * FROM incident_cases').get();assert.equal(row.revision,3);assert.equal(row.status,'resolved');assert.equal(row.assignee,'Synthetic SOC');
|
||||
const report={actualAdministratorUi:true,createFromSecurityEvent:true,statusSeverityAssignment:true,revisionConflictPreservesEdits:true,historyShown:true,adversarialNotesRenderedAsText:true,filters:true,auditorReadOnly:true,rawContentExcluded:true,noNode:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/incident-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-incident-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
@@ -24,5 +23,6 @@ try{
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(store.db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get('synthetic-bookmark-device').document_analysis_enabled,1);assert.equal(policy.brand.name,'Independently approved');
|
||||
const report={actualAdministratorUi:true,mandatoryIndependentApproval:true,payloadDiff:true,selfApprovalHttpDenied:true,distinctReviewer:true,operatorPrepareOnly:true,inspectionOperationVisible:true,inspectionUnchangedUntilApproval:true,rejectedAndStaleVisible:true,publishedOnlyAfterApproval:true,nodeUnavailable:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/independent-review-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-independent-review-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const {app,BrowserWindow,webContents}=require('electron');const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path');
|
||||
const {app,BrowserWindow,webContents}=require('electron');const assert=require('node:assert/strict'),fs=require('node:fs');
|
||||
app.setPath('userData',process.env.SAFETYNET_TABS_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs');
|
||||
function finish(code){for(const contents of webContents.getAllWebContents()){if(contents.getURL().endsWith('index.html'))continue;require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(contents);}app.exit(code);}
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn,label){for(let i=0;i<150;i++){if(await fn())return;await wait(100);}throw Error(label+' timed out');}
|
||||
|
||||
@@ -29,4 +29,5 @@ try{
|
||||
child=spawn(require('electron'),['scripts/native-tabs-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',b=>process.stdout.write(b));child.stderr.on('data',b=>{const message=String(b).replaceAll(code,'[fixture enrollment]').replaceAll(adminToken,'[fixture admin]');process.stderr.write(message);});
|
||||
const timer=setTimeout(()=>child.kill(),120000);const codeExit=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(codeExit,0,'Actual native tabs proof failed');
|
||||
assert.equal(posts.length,0,'Blocked fixture data reached receiver');await sdk.shutdown();sdk=null;const diagnostics=diagnosticStatus();assert.ok(diagnostics.spans>0&&diagnostics.logs>0&&diagnostics.timings&&diagnostics.parentChild&&diagnostics.correlatedLogs);assert.equal(diagnostics.sensitivePresent,false);assert.equal(diagnostics.crossProcessTrace,true,'Browser management span must parent actual backend HTTP request');for(const name of ['browser.ipc','browser.navigation','browser.dns','browser.request'])assert.ok(diagnostics.names.includes(name),`Missing native timing ${name}`);writeFileSync('artifacts/native-browser-diagnostics-proof.json',JSON.stringify(diagnostics,null,2));mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/native-tabs-receiver-proof.json',JSON.stringify({noBlockedPosts:true,requests,posts},null,2));
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
}finally{child?.kill();await sdk?.shutdown();if(previousEndpoint===undefined)delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;else process.env.OTEL_EXPORTER_OTLP_ENDPOINT=previousEndpoint;for(const server of [management,sink,collector])if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-native-tabs-'))throw Error('Unsafe fixture cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
||||
@@ -0,0 +1,16 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFile} from 'node:fs/promises';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {createRequire} from 'node:module';
|
||||
const require=createRequire(import.meta.url);
|
||||
export const hardenedElectronFuses=Object.freeze({runAsNode:false,enableCookieEncryption:true,enableNodeOptionsEnvironmentVariable:false,enableNodeCliInspectArguments:false,enableEmbeddedAsarIntegrityValidation:true,onlyLoadAppFromAsar:true});
|
||||
export function assertEmbeddedIntegrity(entries,header){
|
||||
const matching=entries.filter(row=>row.file==='resources\\app.asar');assert.equal(matching.length,1,'Missing or ambiguous embedded ASAR integrity');const entry=matching[0];assert.equal(entry.alg,'SHA256');assert.equal(entry.value,createHash('sha256').update(header).digest('hex'),'Embedded ASAR integrity does not match archive header');
|
||||
}
|
||||
export async function verifyPackagedFuses(executable,archive){
|
||||
const {getCurrentFuseWire,FuseV1Options}=require('@electron/fuses');const wire=await getCurrentFuseWire(executable);
|
||||
const expected={RunAsNode:false,EnableCookieEncryption:true,EnableNodeOptionsEnvironmentVariable:false,EnableNodeCliInspectArguments:false,EnableEmbeddedAsarIntegrityValidation:true,OnlyLoadAppFromAsar:true};
|
||||
for(const [name,enabled] of Object.entries(expected))assert.equal(wire[FuseV1Options[name]],enabled?49:48,`Packaged security fuse mismatch: ${name}`);
|
||||
const {NtExecutable,NtExecutableResource}=require('resedit'),binary=NtExecutable.from(await readFile(executable)),resources=NtExecutableResource.from(binary);const entries=resources.entries.filter(row=>row.type==='INTEGRITY'&&row.id==='ELECTRONASAR');assert.equal(entries.length,1,'Missing embedded Windows ASAR integrity resource');
|
||||
const integrity=JSON.parse(Buffer.from(entries[0].bin).toString('utf8'));const {getRawHeader}=require('@electron/asar');assertEmbeddedIntegrity(integrity,getRawHeader(archive).headerString);return expected;
|
||||
}
|
||||
+4
-1
@@ -1,3 +1,4 @@
|
||||
import {hardenedElectronFuses,verifyPackagedFuses} from './package-fuses.mjs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {readFileSync,readdirSync} from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
@@ -16,8 +17,10 @@ const electronDirectory=path.dirname(createRequire(import.meta.url)('electron'))
|
||||
if(readFileSync(path.join(electronDirectory,'version'),'utf8').trim().replace(/^v/,'')!==configuration.devDependencies.electron)throw Error('Installed Electron does not match the build pin');
|
||||
function sourceSnapshot(){const entries=[];function walk(directory){for(const item of readdirSync(directory,{withFileTypes:true})){const file=path.join(directory,item.name);if(item.isDirectory())walk(file);else if(item.isFile())entries.push([file,createHash('sha256').update(readFileSync(file)).digest('hex')]);}}for(const directory of ['apps/browser','packages/core'])walk(directory);entries.push(['package.json',createHash('sha256').update(readFileSync('package.json')).digest('hex')]);return JSON.stringify(entries.sort((a,b)=>a[0].localeCompare(b[0])));}
|
||||
const sourcesBefore=sourceSnapshot();
|
||||
const r=spawnSync(process.execPath,['node_modules/electron-builder/out/cli/cli.js',`--${platform}`,'--dir',`--config.electronDist=${electronDirectory}`,`--config.productName=${name}`,`--config.appId=${id}`],{stdio:'inherit',env:process.env});
|
||||
const r=spawnSync(process.execPath,['node_modules/electron-builder/out/cli/cli.js',`--${platform}`,'--dir',`--config.electronDist=${electronDirectory}`,`--config.productName=${name}`,`--config.appId=${id}`,...Object.entries(hardenedElectronFuses).map(([key,value])=>`--config.electronFuses.${key}=${value}`)],{stdio:'inherit',env:process.env});
|
||||
if(r.status!==0)process.exit(r.status??1);
|
||||
if(sourceSnapshot()!==sourcesBefore)throw Error('Browser sources changed during packaging; discard this artifact and rebuild from stable sources');
|
||||
const resources=path.join(output,'win-unpacked/resources');
|
||||
await verifyPackagedFuses(path.join(output,'win-unpacked',`${name}.exe`),path.join(resources,'app.asar'));
|
||||
console.log('Packaged Electron security fuses and embedded ASAR integrity verified.');
|
||||
const acl=spawnSync(path.join(resources,'python/python.exe'),[path.join(resources,'inspector/provision_runtime_acl.py'),'--provision'],{stdio:'inherit',windowsHide:true});process.exit(acl.status??1);
|
||||
@@ -1,7 +1,6 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
@@ -22,5 +21,6 @@ try{
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(policy.brand.name,'SafetyNet');assert.equal(policy.agents.enabled,false);assert.deepEqual(policy.security.dns.blockedHosts,[]);assert.deepEqual(policy.security.content.blockedPhrases,[]);
|
||||
const report={actualAdministratorUi:true,persistedHistory:true,serverDiff:true,explicitRestoreApproval:true,monotonicRestore:true,draftPreservedDuringReview:true,structuredDnsContentAgentControls:true,nodeUnavailable:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/policy-history-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-policy-history-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Run with Node --cpu-prof / --heap-prof; profiles belong in ignored artifacts.
|
||||
import {createRequire} from 'node:module';
|
||||
import {performance} from 'node:perf_hooks';
|
||||
const require=createRequire(import.meta.url);
|
||||
for(const name of ['diagnostics.cjs','inspect-text-representations.cjs']){
|
||||
const before=process.memoryUsage(),started=performance.now();
|
||||
require('../apps/browser/'+name);
|
||||
const after=process.memoryUsage();
|
||||
console.log(JSON.stringify({module:name,loadMs:Number((performance.now()-started).toFixed(3)),heapDeltaBytes:after.heapUsed-before.heapUsed,rssDeltaBytes:after.rss-before.rss}));
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
@@ -24,6 +23,7 @@ try{
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,2);
|
||||
const report={actualAdministratorUi:true,explicitDeviceAssignments:true,employeeDeveloperFlags:true,websiteRestrictions:true,noNode:true,cspPreserved:true,...(config.requireIndependentPolicyApproval?{operatorProposalLeftPolicyUnchanged:true,exactProfileReviewed:true,distinctAdministratorApproval:true}:{effectivePreview:true})};mkdirSync('artifacts',{recursive:true});writeFileSync(config.requireIndependentPolicyApproval?'artifacts/profiles-review-admin-proof.json':'artifacts/profiles-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-profiles-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Finite native fixture: synthetic teaching data, real signed policy and CPU model.
|
||||
const {app,BrowserWindow,clipboard,ClipboardItem}=require('electron');
|
||||
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
|
||||
const assert=require('node:assert/strict');
|
||||
app.setPath('userData',process.env.SAFETYNET_SEMANTIC_PROFILE);
|
||||
app.setPath('sessionData',process.env.SAFETYNET_SEMANTIC_PROFILE);
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ const inspectorPort=await new Promise((resolve,reject)=>{
|
||||
const p=store.get('policy');p.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;p.brand={name:'Acme Secure Browser',color:'#245fbc'};p.dlp.rules=[{id:'secret-roadmap',label:'Roadmap',kind:'literal',value:'Secret Roadmap',action:'block'}];if(process.env.SAFETYNET_TEST_OTLP_ENDPOINT)p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:process.env.SAFETYNET_TEST_OTLP_ENDPOINT,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'smoke-user');
|
||||
p.evidence={enabled:true,captureDownloads:true};p.browser.allowDownloads=true;p.security.yara={enabled:true,rules:[{id:'test-marker',name:'Controlled malicious marker',source:'rule Block_Test { strings: $a = "SAFETYNET_BLOCK_TEST" condition: $a }'}],maxScanBytes:1048576};p.agents={enabled:true,scopes:['sessions','navigate','back','forward','reload','read','inspect','scroll','click','type','select','check','application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group'],allowedHosts:['127.0.0.1'],allowSensitiveRead:false};if(process.env.SAFETYNET_TEST_OTLP_ENDPOINT)p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:process.env.SAFETYNET_TEST_OTLP_ENDPOINT,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,evidenceKeyFile,requirePQManagement:true,inspectorUrl:`http://127.0.0.1:${inspectorPort}`,inspectorToken};const management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.on('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
const evidenceWire=[];management.on('request',(req,res)=>{if(!req.url.startsWith('/api/device/channel/evidence'))return;const chunks=[];req.on('data',chunk=>chunks.push(chunk));req.on('end',()=>{const text=Buffer.concat(chunks).toString('utf8');evidenceWire.push({route:req.url,containsMetadata:['safe.bin','blocked.bin','sourceUrl','sha256'].some(value=>text.includes(value))});});});
|
||||
const evidenceWire=[];management.on('request',(req,_res)=>{if(!req.url.startsWith('/api/device/channel/evidence'))return;const chunks=[];req.on('data',chunk=>chunks.push(chunk));req.on('end',()=>{const text=Buffer.concat(chunks).toString('utf8');evidenceWire.push({route:req.url,containsMetadata:['safe.bin','blocked.bin','sourceUrl','sha256'].some(value=>text.includes(value))});});});
|
||||
|
||||
const child=spawn(electron,['scripts/electron-smoke.cjs'],{stdio:'inherit',windowsHide:true,env:{...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_SINK:p.browser.homeUrl,SAFETYNET_SMOKE_SECONDARY:`http://127.0.0.2:${secondary.address().port}`,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ARTIFACTS:resolve('artifacts'),SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_PUBLIC_UPLOAD:publicUploadFile,SAFETYNET_SMOKE_SECRET_UPLOAD:secretUploadFile}});
|
||||
const timeout=setTimeout(()=>child.kill(),240000);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {mkdtemp,readFile,writeFile,copyFile,mkdir,readdir,rm,access} from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {verifyPackagedFuses} from './package-fuses.mjs';
|
||||
const root=path.resolve('dist/win-unpacked'),executable=path.join(root,'SafetyNet.exe');
|
||||
let currentStage='initialization';
|
||||
function run(stage,file,args,extra={}){currentStage=stage;console.log(`Package fuse proof: ${stage}`);return new Promise((resolve,reject)=>{const env={...process.env,...extra};delete env.NODE_OPTIONS;if(extra.NODE_OPTIONS)env.NODE_OPTIONS=extra.NODE_OPTIONS;delete env.ELECTRON_RUN_AS_NODE;if(extra.ELECTRON_RUN_AS_NODE)env.ELECTRON_RUN_AS_NODE=extra.ELECTRON_RUN_AS_NODE;const child=spawn(file,args,{env,windowsHide:true,stdio:['ignore','pipe','pipe']});let output='',timedOut=false;const timer=setTimeout(()=>{timedOut=true;child.kill();},15000);child.on('error',error=>{clearTimeout(timer);reject(error);});const append=b=>{output=(output+b).slice(-16384);};child.stdout.on('data',append);child.stderr.on('data',append);child.on('close',async code=>{clearTimeout(timer);const diagnostic={stage,code,timedOut,output};await writeFile('artifacts/package-fuses-last-stage.json',JSON.stringify(diagnostic,null,2));if(timedOut)reject(Error(`Finite package fuse proof timed out during ${stage}; bounded diagnostic persisted`));else resolve({code,output});});});}
|
||||
|
||||
const fixtureRoot=path.resolve('artifacts/package-fuse-fixtures');await mkdir(fixtureRoot,{recursive:true});const directory=await mkdtemp(path.join(fixtureRoot,'case-'));
|
||||
try{
|
||||
currentStage='fuses-and-integrity';const fuses=await verifyPackagedFuses(executable,path.join(root,'resources/app.asar'));
|
||||
const args=[`--agent-connection=${path.join(directory,'missing-connection.json')}`,'--agent-action={"action":"sessions","args":{}}'];
|
||||
const baseline=await run('baseline-cli',executable,args);assert.equal(baseline.code,1);assert.match(baseline.output,/Local agent action failed/);
|
||||
const nodeMode=await run('node-mode',executable,args,{ELECTRON_RUN_AS_NODE:'1'});assert.equal(nodeMode.code,1);assert.match(nodeMode.output,/Local agent action failed/,'RUN_AS_NODE altered normal entrypoint');
|
||||
const marker=path.join(directory,'node-options-ran'),preload=path.join(directory,'injection.cjs');await writeFile(preload,`require('node:fs').writeFileSync(${JSON.stringify(marker)},'injected')`);
|
||||
const injected=await run('node-options-injection',executable,args,{NODE_OPTIONS:`--require=${JSON.stringify(preload)}`});assert.equal(injected.code,1);assert.match(injected.output,/Local agent action failed/);await assert.rejects(access(marker),{code:'ENOENT'});
|
||||
const inspector=await run('cli-inspector',executable,[...args,'--inspect=127.0.0.1:0']);assert.notEqual(inspector.code,0);assert.doesNotMatch(inspector.output,/Debugger listening|DevTools listening/);
|
||||
for(const flag of ['--ignore-certificate-errors','--ignore-certificate-errors-spki-list=synthetic-fixture','--allow-insecure-localhost','--disable-features=ConnectionAllowlists','-disable-features=ConnectionAllowlists','/disable-features=ConnectionAllowlists','/DISABLE-FEATURES=ConnectionAllowlists',' --disable-features=ConnectionAllowlists ','\u0085/disable-features=ConnectionAllowlists\u0085','--disable-blink-features=WebTransport','--enable-experimental-web-platform-features']){const rejected=await run('unsafe-startup-'+flag.split('=')[0].slice(2),executable,[...args,flag]);assert.equal(rejected.code,1);assert.match(rejected.output,/Managed browser startup denied/);assert.doesNotMatch(rejected.output,/Local agent action failed|Debugger listening|DevTools listening/);}
|
||||
const copied=path.join(directory,'copied');await mkdir(path.join(copied,'resources'),{recursive:true});for(const entry of await readdir(root,{withFileTypes:true}))if(entry.isFile())await copyFile(path.join(root,entry.name),path.join(copied,entry.name));
|
||||
const archive=await readFile(path.join(root,'resources/app.asar'));const original=Buffer.from(archive);const index=archive.indexOf(Buffer.from('apps'));assert.ok(index>16&&index<archive.readUInt32LE(12)+16,'No safe header tamper fixture offset');archive[index]^=1;await writeFile(path.join(copied,'resources/app.asar'),archive);
|
||||
const tampered=await run('tampered-asar',path.join(copied,'SafetyNet.exe'),args);assert.notEqual(tampered.code,0);assert.match(tampered.output,/integrity|hash.*mismatch/i,'Tampered archive did not fail with integrity evidence');assert.doesNotMatch(tampered.output,/Local agent action failed/,'Tampered application reached its entrypoint');
|
||||
assert.ok(original.equals(await readFile(path.join(root,'resources/app.asar'))),'Real packaged archive changed');
|
||||
const report={fuses,nodeModeDisabled:true,nodeOptionsInjectionIgnored:true,noInspectorListener:true,unsafeStartupOverridesRejected:true,embeddedAsarHeaderIntegrity:true,tamperedCopiedArchiveRejected:true,originalPackageUnmodified:true};await writeFile('artifacts/package-fuses-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}catch(error){await writeFile('artifacts/package-fuses-failure.json',JSON.stringify({stage:currentStage,message:error.message},null,2));throw error;
|
||||
// eslint-disable-next-line no-unsafe-finally -- Unsafe cleanup paths must fail closed before deletion.
|
||||
}finally{const target=path.resolve(directory);if(!target.startsWith(fixtureRoot+path.sep)||path.dirname(target)!==fixtureRoot)throw Error('Unsafe fuse fixture cleanup');await rm(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
||||
@@ -83,6 +83,7 @@ try{
|
||||
console.log('Packaged security sources, Windows upload handle lease, Windows-contained inspector, offline EmbeddingGemma 2 CPU inference, legacy and individual Windows agent helpers and bundled DPAPI runtime passed.');
|
||||
}finally{
|
||||
await service?.close();
|
||||
// eslint-disable-next-line no-unsafe-finally -- Cleanup failure must fail closed rather than report successful protected cleanup.
|
||||
if(!temporary.startsWith(path.join(os.tmpdir(),'safetynet-package-')))throw Error('Unexpected temporary directory');
|
||||
await fs.rm(temporary,{recursive:true,force:true});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
const {app,BrowserWindow,session}=require('electron');
|
||||
const {writeFileSync}=require('node:fs');
|
||||
// This fixed helper is serialized into native Chromium Window and Worker realms.
|
||||
/* global WebTransport */
|
||||
app.disableHardwareAcceleration();app.enableSandbox();
|
||||
const mode=process.env.SAFETYNET_WT_MODE;
|
||||
if(mode==='disable-quic')app.commandLine.appendSwitch('disable-quic');
|
||||
if(mode==='disable-blink')app.commandLine.appendSwitch('disable-blink-features','WebTransport');
|
||||
async function sendWebTransport(target,hash){
|
||||
const outcome={api:typeof WebTransport==='function',supportsReliableOnly:typeof WebTransport==='function'&&WebTransport.supportsReliableOnly===true,connected:false,sent:false,error:''};let transport;
|
||||
try{transport=new WebTransport(target,{serverCertificateHashes:[{algorithm:'sha-256',value:new Uint8Array(hash)}]});await Promise.race([transport.ready,new Promise((_,reject)=>setTimeout(()=>reject(Error('fixture-timeout')),6000))]);outcome.connected=true;const bytes=new TextEncoder().encode('SYNTHETIC_WEBTRANSPORT_DLP_MARKER');const datagram=transport.datagrams.writable.getWriter();await datagram.write(bytes);datagram.releaseLock();const stream=await transport.createUnidirectionalStream();const writer=stream.getWriter();await writer.write(bytes);await writer.close();outcome.sent=true;await new Promise(resolve=>setTimeout(resolve,300));}catch(error){outcome.error=error.name+':'+error.message;}finally{transport?.close();}return outcome;
|
||||
}
|
||||
app.on('login',(event,_contents,_details,authInfo,callback)=>{if(mode==='private-relay'&&authInfo.isProxy&&authInfo.host==='127.0.0.1'&&Number(authInfo.port)===Number(process.env.SAFETYNET_WT_PROXY.split(':').at(-1))){event.preventDefault();callback(process.env.SAFETYNET_WT_USERNAME,process.env.SAFETYNET_WT_PASSWORD);}});
|
||||
app.whenReady().then(async()=>{
|
||||
const target=process.env.SAFETYNET_WT_TARGET,hash=process.env.SAFETYNET_WT_HASH;let events=0;
|
||||
if(['forced-proxy','private-relay'].includes(mode))await session.defaultSession.setProxy({mode:'fixed_servers',proxyRules:process.env.SAFETYNET_WT_PROXY,proxyBypassRules:'<-loopback>'});
|
||||
session.defaultSession.webRequest.onBeforeRequest({urls:[target+'*']},(_details,callback)=>{events++;callback({cancel:true});});
|
||||
const win=new BrowserWindow({show:false,webPreferences:{sandbox:true,nodeIntegration:false,contextIsolation:true}});
|
||||
await win.loadURL(process.env.SAFETYNET_WT_PAGE);
|
||||
const hashBytes=Array.from(Buffer.from(hash,'hex')),result=await win.webContents.executeJavaScript(`(${sendWebTransport.toString()})(${JSON.stringify(target)},${JSON.stringify(hashBytes)})`);
|
||||
const workerSource=`const send=${sendWebTransport.toString()};onmessage=async event=>{postMessage(await send(event.data.target,event.data.hash));close();};`;
|
||||
const worker=await win.webContents.executeJavaScript(`new Promise((resolve,reject)=>{const url=URL.createObjectURL(new Blob([${JSON.stringify(workerSource)}],{type:'text/javascript'}));const worker=new Worker(url);const timer=setTimeout(()=>{worker.terminate();URL.revokeObjectURL(url);reject(Error('worker-fixture-timeout'));},10000);worker.onmessage=event=>{clearTimeout(timer);worker.terminate();URL.revokeObjectURL(url);resolve(event.data);};worker.onerror=()=>{clearTimeout(timer);worker.terminate();URL.revokeObjectURL(url);reject(Error('worker-fixture-failed'));};worker.postMessage({target:${JSON.stringify(target+'/worker')},hash:${JSON.stringify(hashBytes)}});})`);
|
||||
const ordinaryPageLoaded=await win.webContents.executeJavaScript("document.querySelector('h1')?.textContent==='Local WebTransport receiver proof'");
|
||||
writeFileSync(process.env.SAFETYNET_WT_OUTPUT,JSON.stringify({...result,worker,ordinaryPageLoaded,mode,resolvedProxy:await session.defaultSession.resolveProxy(target),webRequestEvents:events,chromium:process.versions.chrome,electron:process.versions.electron}));win.destroy();app.quit();
|
||||
}).catch(error=>{console.error(error.stack);app.exit(1);});
|
||||
@@ -0,0 +1,24 @@
|
||||
import {spawn} from 'node:child_process';
|
||||
import {createRequire} from 'node:module';
|
||||
import {createServer,request} from 'node:http';
|
||||
import {mkdtempSync,readFileSync,writeFileSync} from 'node:fs';
|
||||
import {resolve,join} from 'node:path';
|
||||
import assert from 'node:assert/strict';
|
||||
const require=createRequire(import.meta.url),root=mkdtempSync(resolve('artifacts/webtransport-')),env={...process.env};delete env.ELECTRON_RUN_AS_NODE;
|
||||
const python=spawn('python',['scripts/webtransport-receiver.py',root],{env:{...env,PYTHONPATH:resolve('artifacts/webtransport-python')},windowsHide:true,stdio:['ignore','pipe','pipe']});let errors='';python.stderr.on('data',b=>errors+=b);
|
||||
const metadata=await new Promise((resolve,reject)=>{let text='';const timer=setTimeout(()=>reject(Error('WebTransport receiver startup timeout: '+errors)),15000);python.stdout.on('data',b=>{text+=b;const line=text.split('\n')[0];try{const result=JSON.parse(line);clearTimeout(timer);resolve(result);}catch{}});python.once('exit',()=>{clearTimeout(timer);reject(Error('Receiver exited: '+errors));});});
|
||||
const page=createServer((_req,res)=>{res.setHeader('content-type','text/html');res.end('<!doctype html><h1>Local WebTransport receiver proof</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>page.once('listening',r));
|
||||
let forwarded=0;const proxy=createServer((req,res)=>{const target=new URL(req.url);if(target.hostname!=='127.0.0.1'||Number(target.port)!==page.address().port){res.writeHead(403);res.end();return;}forwarded++;const outgoing=request(target,{method:req.method,headers:req.headers},incoming=>{res.writeHead(incoming.statusCode,incoming.headers);incoming.pipe(res);});outgoing.on('error',()=>{res.writeHead(502);res.end();});req.pipe(outgoing);}).listen(0,'127.0.0.1');await new Promise(r=>proxy.once('listening',r));
|
||||
const outcomes=[];
|
||||
let relayAuthorizations=0;const authority={version:1},relay=await require('../apps/browser/local-dlp-relay.cjs').startLocalDlpRelay({authorize:async()=>{relayAuthorizations++;return authority;},isCurrent:a=>a===authority,resolveUpstream:async()=> 'DIRECT',resolveAddress:async()=> '127.0.0.1'});
|
||||
try{
|
||||
for(const mode of ['baseline','disable-quic','disable-blink','forced-proxy','private-relay']){
|
||||
const before=await (await fetch(`http://127.0.0.1:${metadata.statsPort}/facts`)).json(),output=join(root,mode+'.json');
|
||||
const forwardedBefore=forwarded;
|
||||
const credentials=relay.credentials(),relayBefore=relayAuthorizations;
|
||||
const child=spawn(require('electron'),['scripts/webtransport-native-probe.cjs'],{env:{...env,SAFETYNET_WT_MODE:mode,SAFETYNET_WT_USERNAME:credentials.username,SAFETYNET_WT_PASSWORD:credentials.password,SAFETYNET_WT_PROXY:mode==='private-relay'?relay.proxyRules:`127.0.0.1:${proxy.address().port}`,SAFETYNET_WT_TARGET:`https://127.0.0.1:${metadata.quicPort}/webtransport`,SAFETYNET_WT_PAGE:`http://127.0.0.1:${page.address().port}/`,SAFETYNET_WT_HASH:metadata.hash,SAFETYNET_WT_OUTPUT:output},windowsHide:true,stdio:['ignore','pipe','pipe']});let log='';child.stdout.on('data',b=>log+=b);child.stderr.on('data',b=>log+=b);const timer=setTimeout(()=>child.kill(),20000),code=await new Promise(r=>child.once('exit',r));clearTimeout(timer);if(code!==0)throw Error(`Native ${mode} exited ${code}: ${log}`);
|
||||
const after=await (await fetch(`http://127.0.0.1:${metadata.statsPort}/facts`)).json(),result=JSON.parse(readFileSync(output,'utf8'));outcomes.push({...result,forwardedHTTP:mode==='private-relay'?relayAuthorizations-relayBefore:forwarded-forwardedBefore,receiver:{connections:after.connections-before.connections,datagrams:after.datagrams-before.datagrams,streams:after.streams-before.streams,bytes:after.bytes-before.bytes,workerBytes:after.workerBytes-before.workerBytes,packets:after.packets-before.packets,markerReceived:after.bytes>before.bytes&&after.marker}});
|
||||
}
|
||||
for(const result of outcomes){assert.equal(result.ordinaryPageLoaded,true);if(['baseline','disable-quic','disable-blink'].includes(result.mode)){assert.equal(result.sent,true);assert.equal(result.worker.sent,true);assert.ok(result.receiver.bytes>=132);assert.ok(result.receiver.workerBytes>=66);assert.equal(result.webRequestEvents,0);}else{assert.equal(result.sent,false);assert.equal(result.worker.sent,false);assert.equal(result.receiver.packets,0);assert.equal(result.receiver.bytes,0);assert.ok(result.forwardedHTTP>0);}}
|
||||
const report={actualElectron:true,localPinnedReceiver:true,serverVersion:metadata.aioquic,outcomes};writeFileSync('artifacts/webtransport-native-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));
|
||||
}finally{page.closeAllConnections();proxy.closeAllConnections();await Promise.all([new Promise(r=>page.close(r)),new Promise(r=>proxy.close(r)),relay.close()]);python.kill();}
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Finite loopback-only HTTP/3 WebTransport receiver for native security proof."""
|
||||
import asyncio, datetime, hashlib, ipaddress, json, os, pathlib, sys
|
||||
from aioquic.asyncio import QuicConnectionProtocol, serve
|
||||
from aioquic.h3.connection import H3Connection, H3_ALPN
|
||||
from aioquic.h3.events import HeadersReceived, DatagramReceived, WebTransportStreamDataReceived
|
||||
from aioquic.quic.configuration import QuicConfiguration
|
||||
from aioquic.quic.events import ProtocolNegotiated
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
facts = {"packets": 0, "connections": 0, "datagrams": 0, "streams": 0, "bytes": 0, "workerBytes": 0, "marker": False}
|
||||
marker = b"SYNTHETIC_WEBTRANSPORT_DLP_MARKER"
|
||||
class Receiver(QuicConnectionProtocol):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.http = None
|
||||
self.worker_sessions = set()
|
||||
def datagram_received(self, data, addr):
|
||||
facts["packets"] += 1
|
||||
super().datagram_received(data, addr)
|
||||
def quic_event_received(self, event):
|
||||
if isinstance(event, ProtocolNegotiated):
|
||||
self.http = H3Connection(self._quic, enable_webtransport=True)
|
||||
if not self.http:
|
||||
return
|
||||
for item in self.http.handle_event(event):
|
||||
if isinstance(item, HeadersReceived):
|
||||
headers = dict(item.headers)
|
||||
if headers.get(b":method") == b"CONNECT" and headers.get(b":protocol") == b"webtransport":
|
||||
facts["connections"] += 1
|
||||
if headers.get(b":path", b"").endswith(b"/worker"):
|
||||
self.worker_sessions.add(item.stream_id)
|
||||
self.http.send_headers(item.stream_id, [(b":status", b"200"), (b"sec-webtransport-http3-draft", b"draft02")])
|
||||
self.transmit()
|
||||
elif isinstance(item, (DatagramReceived, WebTransportStreamDataReceived)):
|
||||
facts["datagrams" if isinstance(item, DatagramReceived) else "streams"] += 1
|
||||
facts["bytes"] += len(item.data)
|
||||
session_id = item.stream_id if isinstance(item, DatagramReceived) else item.session_id
|
||||
if session_id in self.worker_sessions:
|
||||
facts["workerBytes"] += len(item.data)
|
||||
facts["marker"] |= marker in item.data
|
||||
|
||||
async def main():
|
||||
root = pathlib.Path(sys.argv[1]).resolve()
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
key = ec.generate_private_key(ec.SECP256R1())
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "127.0.0.1")])
|
||||
cert = x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(now-datetime.timedelta(minutes=1)).not_valid_after(now+datetime.timedelta(days=7)).add_extension(x509.SubjectAlternativeName([x509.IPAddress(ipaddress.ip_address("127.0.0.1"))]), critical=False).sign(key, hashes.SHA256())
|
||||
cert_file, key_file = root / "certificate.pem", root / "key.pem"
|
||||
cert_file.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
|
||||
key_file.write_bytes(key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()))
|
||||
config = QuicConfiguration(is_client=False, alpn_protocols=H3_ALPN, max_datagram_frame_size=65536)
|
||||
config.load_cert_chain(str(cert_file), str(key_file))
|
||||
server = await serve("127.0.0.1", 0, configuration=config, create_protocol=Receiver)
|
||||
async def stats(reader, writer):
|
||||
await reader.read(8192)
|
||||
body = json.dumps(facts).encode()
|
||||
writer.write(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " + str(len(body)).encode() + b"\r\nConnection: close\r\n\r\n" + body)
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
control = await asyncio.start_server(stats, "127.0.0.1", 0)
|
||||
print(json.dumps({"quicPort": server._transport.get_extra_info("sockname")[1], "statsPort": control.sockets[0].getsockname()[1], "hash": hashlib.sha256(cert.public_bytes(serialization.Encoding.DER)).hexdigest(), "aioquic": __import__("aioquic").__version__}), flush=True)
|
||||
await asyncio.sleep(180)
|
||||
server.close()
|
||||
control.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
// A second Electron process reads its original DPAPI enrollment, never reenrolls.
|
||||
const {app,BrowserWindow,session,clipboard,ClipboardItem}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),https=require('node:https'),{X509Certificate}=require('node:crypto');
|
||||
assert.equal(process.env.SAFETYNET_SMOKE_CODE,undefined,'Restart must not receive an enrollment code');app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();
|
||||
const origin=process.env.SAFETYNET_SMOKE_ORIGIN,base=process.env.SAFETYNET_SMOKE_SERVER,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN},secret='MVP Company Forecast 2030',fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256,fromPartition=session.fromPartition.bind(session);
|
||||
session.fromPartition=(...args)=>{const current=fromPartition(...args);if(args[0]==='persist:safetynet-managed')current.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);});return current;};
|
||||
let actualDiagnostics;const moduleDiagnostics=require('../apps/browser/diagnostics.cjs'),createDiagnostics=moduleDiagnostics.createDiagnostics;moduleDiagnostics.createDiagnostics=(...args)=>{const value=createDiagnostics(...args);actualDiagnostics=value;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<300;i++){const result=await fn();if(result)return result;await wait(100);}throw Error('Restarted native MVP timeout');}
|
||||
const privateFacts=()=>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);});
|
||||
app.whenReady().then(async()=>{let stage='persisted startup',page,original=[];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 ui=async()=>{const result=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(result.ok,true);return result.data;};const initial=await until(async()=>{const current=await ui();return current.ready&¤t.enrolled&¤t;});assert.equal(initial.deviceId,process.env.SAFETYNET_SMOKE_EXPECT_DEVICE);assert.equal(initial.policyVersion,Number(process.env.SAFETYNET_SMOKE_EXPECT_POLICY));assert.notEqual(process.pid,Number(process.env.SAFETYNET_SMOKE_FIRST_PID));page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()&&page.getURL()===origin+'/workspace');checks.push('same-dpapi-device-current-policy-new-process');
|
||||
const before=await privateFacts(),key=async keyCode=>{win.focus();page.focus();page.sendInputEvent({type:'keyDown',keyCode,modifiers:['control']});page.sendInputEvent({type:'keyUp',keyCode,modifiers:['control']});await wait(400);await until(async()=>!(await ui()).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);})()`);const value=()=>page.executeJavaScript("document.getElementById('editor').innerText.trimEnd()");
|
||||
stage='post-restart public paste submit';await editor('');await clipboard.writeText('Public restarted AI prompt');await key('V');assert.equal(await value(),'Public restarted AI prompt');assert.equal(await page.executeJavaScript("window.mvpSubmit('editor')"),true);assert.equal((await privateFacts()).receiver.posts,before.receiver.posts+1);checks.push('restarted-public-paste-submit');stage='post-restart confidential paste held';await editor('Public preserved restart baseline');await clipboard.writeText(secret);await key('V');assert.equal(await value(),'Public preserved restart baseline');assert.equal(await page.executeJavaScript("window.mvpSubmit('editor')"),true);assert.equal((await privateFacts()).receiver.posts,before.receiver.posts+2);assert.equal((await privateFacts()).receiver.secretPosts,0);checks.push('restarted-confidential-paste-denied');stage='post-restart typed confidential body denied';assert.equal(await page.executeJavaScript(`fetch('/receiver/typed',{method:'POST',body:${JSON.stringify(secret)}}).then(r=>r.ok).catch(()=>false)`),false);assert.equal((await privateFacts()).receiver.posts,before.receiver.posts+2);checks.push('restarted-confidential-body-denied');
|
||||
stage='post-restart public file 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);let after=await privateFacts();assert.equal(after.receiver.posts,before.receiver.posts+3);assert.equal(after.receiver.filePosts,before.receiver.filePosts+1);assert.equal(after.receiver.exactPublicFile,before.receiver.exactPublicFile+1);stage='post-restart confidential file denied';assert.equal(await upload(process.env.SAFETYNET_SMOKE_SECRET_FILE),false);after=await privateFacts();assert.equal(after.receiver.posts,before.receiver.posts+3);assert.equal(after.receiver.secretPosts,0);checks.push('restarted-public-secret-ntfs-upload');
|
||||
stage='post-restart audit and traces';await actualDiagnostics.flush();await wait(1500);const events=[];let cursor=Number(process.env.SAFETYNET_SMOKE_AFTER_EVENT);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.equal(row.safetynet.policy_version,initial.policyVersion,'Restart audit policy binding changed');assert.equal(row.host.id,initial.deviceId);assert.ok(!JSON.stringify(row).includes(secret));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==='browser.started'),'New browser process start was not audited');const traces=[];for(const action of ['clipboard.paste','clipboard.block','upload.authorized','upload.block']){const event=events.filter(e=>e.event.action===action&&e.trace?.id).at(-1);assert.ok(event,action+' restart event absent');const trace=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.trace.id)).json();assert.ok(trace.spans.length);assert.ok(!JSON.stringify(trace).includes(secret));const expected=action.startsWith('clipboard')?'browser.clipboard':'browser.request';assert.ok(trace.spans.some(s=>s.name===expected));if(action.startsWith('upload.'))assert.ok(trace.spans.some(s=>s.name==='browser.upload.lease'),'Restart native file lease trace missing');const byId=new Map(trace.spans.map(s=>[s.spanId,s]));if(action.startsWith('upload.')){for(const operation of ['text.local','text.enterprise']){const normalized=trace.spans.find(s=>s.operation===operation);assert.ok(normalized,'Normalized URL stage absent');let current=normalized,linked=false;const seen=new Set();while(current&&!seen.has(current.spanId)){if(current.name==='browser.request'){linked=true;break;}seen.add(current.spanId);current=byId.get(current.parentSpanId);}assert.ok(linked,'Normalized URL parent correlation absent');}}for(const stage of trace.spans.filter(s=>s.name==='browser.upload.lease'||s.name==='browser.upload.part'||s.name==='browser.inspect.local')){let current=stage,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,'Restart span parent binding absent');}traces.push({action,traceId:event.trace.id,stages:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs}))});}const status=actualDiagnostics.status();assert.equal(status.enabled,true);assert.equal(status.failedExports,0);assert.equal(status.lifetimeFailedExports,0);const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0);
|
||||
fs.writeFileSync(path.resolve('artifacts/windows-dlp-mvp-restart-native-proof.json'),JSON.stringify({actualNewNativeProcess:true,firstProcessId:Number(process.env.SAFETYNET_SMOKE_FIRST_PID),restartedProcessId:process.pid,enrollmentCodeAbsent:true,persistedDeviceId:initial.deviceId,policyVersion:initial.policyVersion,checks,receiverBefore:before.receiver,receiverAfter:after.receiver,scannerBefore:before.scanner,scannerAfter:after.scanner,traces,diagnostics:status,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}},null,2));console.log('Windows DLP MVP actual process restart 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 privateFacts();await actualDiagnostics?.flush();}catch{}fs.writeFileSync(path.resolve('artifacts/windows-dlp-mvp-restart-native-error.json'),JSON.stringify({stage,name:error.name,message:error.message,checks,facts,diagnostics:actualDiagnostics?.status()},null,2));console.error('Restarted Windows 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);}});
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow,session,clipboard,ClipboardItem}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),https=require('node:https'),{X509Certificate}=require('node:crypto');
|
||||
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();let actualDiagnostics;const diagnosticsModule=require('../apps/browser/diagnostics.cjs'),createDiagnostics=diagnosticsModule.createDiagnostics;diagnosticsModule.createDiagnostics=(...args)=>{const instance=createDiagnostics(...args);actualDiagnostics=instance;return instance;};require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms)),base=process.env.SAFETYNET_SMOKE_SERVER,origin=process.env.SAFETYNET_SMOKE_ORIGIN,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN,'content-type':'application/json'},secret='MVP Company Forecast 2030',checks=[],editorCaretFacts=[];
|
||||
async function until(fn){for(let i=0;i<250;i++){const value=await fn();if(value)return value;await wait(100);}throw Error('Native Windows DLP fixture timeout');}
|
||||
app.whenReady().then(async()=>{let page,readFailureFacts,originalClipboard=[],stage='startup';try{for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);originalClipboard.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);});const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);assert.equal(result.ok,true,method);return result.data;};
|
||||
const privateGET=route=>new Promise((resolve,reject)=>{const request=https.get(origin+route,{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(route==='/facts'?JSON.parse(body):true));});request.on('error',reject);});
|
||||
readFailureFacts=()=>privateGET('/facts');stage='enrollment';await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()&&page.getURL()===origin+'/workspace');let loadGeneration=0;page.on('did-finish-load',()=>loadGeneration++);const navigate=async()=>{const before=loadGeneration;await ui('navigate',origin+'/workspace');await until(()=>loadGeneration>before&&!page.isLoading()&&page.getURL()===origin+'/workspace');win.focus();page.focus();await wait(200);};
|
||||
const change=async modify=>{const p=(await(await fetch(base+'/api/admin/overview',{headers})).json()).policy;modify(p);p.version++;const generation=loadGeneration,proposed=await fetch(base+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(p)});assert.equal(proposed.status,202);const review=await proposed.json();const approval=await fetch(base+`/api/admin/policy-reviews/${review.reviewId}/approve`,{method:'POST',headers:{cookie:'sn_session='+process.env.SAFETYNET_SMOKE_REVIEWER,origin:base,'content-type':'application/json'},body:JSON.stringify({payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true})});assert.equal(approval.status,200);await until(async()=>{const s=await ui('state');return s.ready&&s.policyVersion===p.version;});await until(()=>loadGeneration>generation&&!page.isLoading());win.focus();page.focus();await wait(200);};
|
||||
const key=async(code,shift=false)=>{win.focus();page.focus();page.sendInputEvent({type:'keyDown',keyCode:code,modifiers:shift?['shift']:['control']});page.sendInputEvent({type:'keyUp',keyCode:code,modifiers:shift?['shift']:['control']});await wait(400);await until(async()=>!(await ui('state')).clipboardPending);};
|
||||
const setField=(id,text='')=>page.executeJavaScript(`(()=>{const e=document.getElementById(${JSON.stringify(id)});if(e.isContentEditable){e.innerHTML='<p><br></p>';if(${JSON.stringify(text)})e.firstChild.textContent=${JSON.stringify(text)};e.focus();const range=document.createRange();range.selectNodeContents(e.firstChild);range.collapse(!e.firstChild.textContent);const s=getSelection();s.removeAllRanges();s.addRange(range);}else{e.value=${JSON.stringify(text)};e.focus();e.setSelectionRange(e.value.length,e.value.length);}})()`);
|
||||
const clickEditor=async()=>{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 wait(80);const facts=await page.executeJavaScript("(()=>{const e=document.getElementById('editor'),s=getSelection(),r=s.rangeCount===1?s.getRangeAt(0):null,p=e.firstChild;return {focused:document.activeElement===e,collapsed:!!r?.collapsed,startOwned:!!r&&e.contains(r.startContainer),endOwned:!!r&&e.contains(r.endContainer),startInParagraph:!!r&&p.contains(r.startContainer),endInParagraph:!!r&&p.contains(r.endContainer)}})()");assert.ok(facts.focused&&facts.collapsed&&facts.startOwned&&facts.endOwned);editorCaretFacts.push({nativeClick:true,...facts});};
|
||||
const value=id=>page.executeJavaScript(`(()=>{const e=document.getElementById(${JSON.stringify(id)});return e.isContentEditable?e.innerText.trimEnd():e.value;})()`);
|
||||
const post=(kind,text)=>page.executeJavaScript(`fetch(${JSON.stringify('/receiver/'+kind)},{method:'POST',headers:{'content-type':'text/plain'},body:${JSON.stringify(text)}}).then(r=>r.ok).catch(()=>false)`);
|
||||
stage='typed public body allowed';let before=(await privateGET('/facts')).receiver.posts;assert.equal(await post('typed','Public typed prompt'),true);assert.equal((await privateGET('/facts')).receiver.posts,before+1);checks.push('public-body');stage='typed confidential body blocked';before=(await privateGET('/facts')).receiver.posts;assert.equal(await post('typed',secret),false);assert.equal((await privateGET('/facts')).receiver.posts,before);checks.push('secret-body');
|
||||
for(const id of ['input','textarea','editor']){stage=id+' public paste to actual receiver';await setField(id);if(id==='editor')await clickEditor();const text='Public '+id+' prompt for AI';await clipboard.writeText(text);await key('V');stage=id+' public paste commit';assert.equal(await value(id),text,'Public AI editor paste did not commit');before=(await privateGET('/facts')).receiver.posts;if(id==='input'){stage='input native form dispatch';const loading=new Promise(resolve=>page.once('did-stop-loading',resolve));assert.equal(await page.executeJavaScript("(()=>{setTimeout(()=>document.getElementById('prompt-form').requestSubmit(),0);return true})()"),true);await Promise.race([loading,new Promise((_,reject)=>setTimeout(()=>reject(Error('Native form did not terminate')),15000))]);assert.equal((await privateGET('/facts')).receiver.posts,before+1);await navigate();}else{assert.equal(await page.executeJavaScript(`window.mvpSubmit(${JSON.stringify(id)})`),true);assert.equal((await privateGET('/facts')).receiver.posts,before+1);}checks.push(id+'-public-paste-submit');
|
||||
stage=id+' confidential paste cannot reach receiver';const baseline='Public preserved '+id;await setField(id,baseline);await clipboard.writeText(secret);await key('V');assert.equal(await value(id),baseline,'Confidential paste changed the AI editor');before=(await privateGET('/facts')).receiver.posts;assert.equal(await page.executeJavaScript(`window.mvpSubmit(${JSON.stringify(id)})`),true);const facts=await privateGET('/facts');assert.equal(facts.receiver.posts,before+1);assert.equal(facts.receiver.secretPosts,0);checks.push(id+'-secret-paste-denied');}
|
||||
stage='plain part of HTML clipboard stays inert';await setField('editor');await clickEditor();await clipboard.write([new ClipboardItem({'text/plain':new Blob(['Public HTML-source prompt'],{type:'text/plain'}),'text/html':new Blob(['<p>Public HTML-source prompt</p><img id="mvp-html-must-not-render" src="/html-clipboard-network">'],{type:'text/html'})})]);await key('V');assert.equal(await value('editor'),'Public HTML-source prompt');assert.equal(await page.executeJavaScript("!!document.querySelector('#editor img,#mvp-html-must-not-render')"),false);before=(await privateGET('/facts')).receiver.posts;assert.equal(await page.executeJavaScript("window.mvpSubmit('editor')"),true);assert.equal((await privateGET('/facts')).receiver.posts,before+1);stage='confidential plain part of HTML clipboard blocked';await setField('editor','Public HTML baseline');await clipboard.write([new ClipboardItem({'text/plain':new Blob([secret],{type:'text/plain'}),'text/html':new Blob(['<p>Public decoy</p>'],{type:'text/html'})})]);await key('V');assert.equal(await value('editor'),'Public HTML baseline');checks.push('html-clipboard-inert-plain-part-dlp');
|
||||
stage='native form typed public allowed';await setField('input','Public typed standard form');before=(await privateGET('/facts')).receiver.posts;let stopped=new Promise(resolve=>page.once('did-stop-loading',resolve));assert.equal(await page.executeJavaScript("(()=>{setTimeout(()=>document.getElementById('prompt-form').requestSubmit(),0);return true})()"),true);await Promise.race([stopped,new Promise((_,reject)=>setTimeout(()=>reject(Error('Native form did not terminate')),15000))]);assert.equal((await privateGET('/facts')).receiver.posts,before+1);await navigate();checks.push('public-standard-form');stage='native form typed confidential denied';await setField('input',secret);before=(await privateGET('/facts')).receiver.posts;stopped=new Promise(resolve=>page.once('did-stop-loading',resolve));assert.equal(await page.executeJavaScript("(()=>{setTimeout(()=>document.getElementById('prompt-form').requestSubmit(),0);return true})()"),true);await Promise.race([stopped,new Promise((_,reject)=>setTimeout(()=>reject(Error('Blocked form did not terminate')),15000))]);assert.equal((await privateGET('/facts')).receiver.posts,before);await navigate();checks.push('secret-standard-form');
|
||||
const selectCopy=text=>page.executeJavaScript(`(()=>{const e=document.getElementById('copy-source');e.textContent=${JSON.stringify(text)};const range=document.createRange();range.selectNodeContents(e);const s=getSelection();s.removeAllRanges();s.addRange(range);})()`);stage='public native copy';await selectCopy('Public copy sample');await clipboard.writeText('Public clipboard sentinel');await key('C');assert.equal(await clipboard.readText(),'Public copy sample');stage='confidential copy holds OS clipboard';await clipboard.writeText('Public clipboard sentinel');await selectCopy(secret);await key('C');assert.equal(await clipboard.readText(),'Public clipboard sentinel');stage='confidential cut preserves source';await setField('input',secret);await page.executeJavaScript("document.getElementById('input').select()");await key('X');assert.equal(await clipboard.readText(),'Public clipboard sentinel');assert.equal(await value('input'),secret);checks.push('copy-cut-secret-denied');stage='public native cut';await setField('textarea','Public cut sample');await page.executeJavaScript("document.getElementById('textarea').select()");await key('X');assert.equal(await clipboard.readText(),'Public cut sample');assert.equal(await value('textarea'),'');checks.push('public-copy-cut');
|
||||
stage='native NTFS public file';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);let facts=await privateGET('/facts');assert.equal(facts.receiver.filePosts,1);assert.equal(facts.receiver.exactPublicFile,1);stage='native NTFS confidential file denied';assert.equal(await upload(process.env.SAFETYNET_SMOKE_SECRET_FILE),false);facts=await privateGET('/facts');assert.equal(facts.receiver.filePosts,1);assert.equal(facts.receiver.secretPosts,0);checks.push('native-file-public-secret');
|
||||
stage='actual local YARA blocks';before=(await privateGET('/facts')).receiver.posts;assert.equal(await post('yara','MVP_YARA_ONLY_BLOCK'),false);assert.equal((await privateGET('/facts')).receiver.posts,before);stage='actual ICAP blocks';assert.equal(await post('icap','MVP_ICAP_ONLY_BLOCK'),false);assert.equal((await privateGET('/facts')).receiver.posts,before);stage='actual configured scanner failure fails closed';await privateGET('/scanner/fail');assert.equal(await post('scanner','Public scanner outage prompt'),false);assert.equal((await privateGET('/facts')).receiver.posts,before);await privateGET('/scanner/normal');checks.push('real-yara-icap-scanner-failure');
|
||||
stage='policy changes while real scan pending deny release';await privateGET('/scanner/gate');before=(await privateGET('/facts')).receiver.posts;const pending=post('race','Public pending scan').catch(()=>false);await until(async()=>(await privateGET('/facts')).scanner.pending);await change(p=>p.dlp.rules.push({id:'mvp-late-classification',label:'Synthetic policy race',kind:'literal',value:'Public pending scan',action:'block'}));await privateGET('/scanner/release');assert.equal(await pending,false);assert.equal((await privateGET('/facts')).receiver.posts,before);await privateGET('/scanner/normal');checks.push('policy-race-denied');
|
||||
stage='central paste disable then re-enable';await change(p=>p.security.clipboard.allowPaste=false);await setField('editor');await clipboard.writeText('Public centrally disabled paste');await key('V');assert.equal(await value('editor'),'');await change(p=>p.security.clipboard.allowPaste=true);await setField('editor');await clipboard.writeText('Public centrally restored paste');await key('V');assert.equal(await value('editor'),'Public centrally restored paste');checks.push('independent-central-paste-control');
|
||||
stage='real correlated diagnostics and SIEM';await wait(1800);const events=[];let after=0;for(let batch=0;batch<20;batch++){const response=await fetch(base+`/api/admin/events/export?after=${after}&limit=1000&format=ecs`,{headers});assert.equal(response.status,200);const rows=(await response.text()).trim().split('\n').filter(Boolean).map(line=>JSON.parse(line));for(const row of rows){assert.ok(!/MVP Company Forecast 2030|MVP_YARA_ONLY_BLOCK|MVP_ICAP_ONLY_BLOCK/.test(JSON.stringify(row)));events.push(row);}if(rows.length<1000)break;const next=Number(response.headers.get('x-safetynet-next-cursor'));assert.ok(next>after);after=next;}const traceEvidence=[];for(const action of ['clipboard.paste','clipboard.block','upload.authorized','upload.block']){const candidates=events.filter(e=>e.event.action===action&&e.trace?.id);assert.ok(candidates.length,action+' durable 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();const expected=action.startsWith('clipboard')?'browser.clipboard':'browser.request';if(trace.spans?.some(s=>s.name===expected)){assert.ok(!/MVP Company Forecast 2030|MVP_YARA_ONLY_BLOCK|MVP_ICAP_ONLY_BLOCK/.test(JSON.stringify(trace)));const byId=new Map(trace.spans.map(s=>[s.spanId,s]));if(action.startsWith('upload.')){for(const operation of ['text.local','text.enterprise']){const normalized=trace.spans.find(s=>s.operation===operation);assert.ok(normalized,'Normalized URL stage absent');let current=normalized,linked=false;const seen=new Set();while(current&&!seen.has(current.spanId)){if(current.name==='browser.request'){linked=true;break;}seen.add(current.spanId);current=byId.get(current.parentSpanId);}assert.ok(linked,'Normalized URL parent correlation absent');}}if(action==='clipboard.paste'){for(const name of ['browser.inspect.local','browser.inspect.enterprise']){const stage=trace.spans.find(s=>s.name===name);assert.ok(stage,'Clipboard inspection stage missing');let node=stage,linked=false;const seen=new Set();while(node&&!seen.has(node.spanId)){if(node.name==='browser.clipboard'){linked=true;break;}seen.add(node.spanId);node=byId.get(node.parentSpanId);}assert.ok(linked,'Clipboard inspection parent correlation missing');}}if(action==='upload.authorized'){assert.ok(trace.spans.some(s=>s.name==='browser.upload.lease'));for(const s of trace.spans.filter(s=>s.name==='browser.upload.lease'||s.name==='browser.upload.part')){let node=s,linked=false;const seen=new Set();while(node&&!seen.has(node.spanId)){if(node.name==='browser.request'){linked=true;break;}seen.add(node.spanId);node=byId.get(node.parentSpanId);}assert.ok(linked,'File stage 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,action+' exported trace missing');traceEvidence.push(found);}facts=await privateGET('/facts');assert.equal(facts.receiver.secretPosts,0);assert.equal(facts.receiver.filePosts,1);assert.equal(facts.receiver.exactPublicFile,1);assert.equal(facts.scanner.blocks,1);assert.ok(facts.scanner.failures>=1);const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0);const summary=await(await fetch('http://127.0.0.1:4318/api/summary')).json();await actualDiagnostics.flush();const diagnostics=actualDiagnostics.status();assert.equal(diagnostics.enabled,true);assert.equal(diagnostics.failedExports,0);assert.equal(diagnostics.lifetimeFailedExports,0);
|
||||
fs.writeFileSync(path.resolve('artifacts/windows-dlp-mvp-native-proof.json'),JSON.stringify({actualWindows:process.platform==='win32',actualElectron:true,processId:process.pid,deviceId:(await ui('state')).deviceId,policyVersion:(await ui('state')).policyVersion,exactPinnedHTTPS:true,independentReviewedPublication:true,checks,editorCaretFacts,receiver:facts.receiver,scanner:facts.scanner,ordinaryAuditConfidentialFree:true,traceEvidence,summary,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites},diagnostics},null,2));console.log('Windows DLP MVP native proof passed');if(originalClipboard.length)await clipboard.write(originalClipboard);else await clipboard.clear();require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0);
|
||||
}catch(error){let failureFacts;try{failureFacts=await readFailureFacts?.();await actualDiagnostics?.flush();}catch{}fs.writeFileSync(path.resolve('artifacts/windows-dlp-mvp-native-error.json'),JSON.stringify({stage,name:error.name,message:error.message,checks,editorCaretFacts,failureFacts,diagnostics:actualDiagnostics?.status()},null,2));console.error('Windows DLP MVP failed at '+stage+': '+error.name);try{if(originalClipboard.length)await clipboard.write(originalClipboard);else await clipboard.clear();}catch{}if(page&&!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});
|
||||
@@ -0,0 +1,27 @@
|
||||
// A bounded, real Windows browser DLP acceptance receiver; never a production bypass.
|
||||
import {createServer} from 'node:https';
|
||||
import {createRequire} from 'node:module';
|
||||
import {mkdtempSync,mkdirSync,rmSync,readFileSync,writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join,resolve,sep} from 'node:path';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import net from 'node:net';
|
||||
import {createApp} from '../apps/server/app.mjs';
|
||||
import {openStore,digest} from '../apps/server/store.mjs';
|
||||
import {startTelemetry} from '../apps/server/telemetry.mjs';
|
||||
const collector='http://127.0.0.1:4318';if(!(await fetch(collector+'/health')).ok)throw Error('Protected collector unavailable');process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry();
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-windows-dlp-'));mkdirSync('artifacts',{recursive:true});if(!resolve(temporary).startsWith(resolve(tmpdir())+sep)||!temporary.split(/[\\/]/).at(-1).startsWith('safetynet-windows-dlp-'))throw Error('Unsafe temporary fixture path');
|
||||
const secret='MVP Company Forecast 2030',yaraMarker='MVP_YARA_ONLY_BLOCK',icapMarker='MVP_ICAP_ONLY_BLOCK',publicFile=Buffer.from('Public Windows DLP file content\n'),files={public:join(temporary,'public.txt'),secret:join(temporary,'secret.txt')};writeFileSync(files.public,publicFile);writeFileSync(files.secret,secret);
|
||||
const receiver={posts:0,secretPosts:0,filePosts:0,exactPublicFile:0,kinds:{}},scanner={scans:0,blocks:0,failures:0,pending:false};let avMode='normal',pendingAV=null;const sockets=new Set();
|
||||
const av=net.createServer(socket=>{sockets.add(socket);socket.on('close',()=>sockets.delete(socket));socket.on('error',()=>{});let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(input.length>4194304){input.fill(0);socket.destroy();return;}if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options){input.fill(0);socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nAllow: 204\r\nISTag: "mvp-fixture"\r\nEncapsulated: null-body=0\r\n\r\n');return;}scanner.scans++;const block=input.includes(Buffer.from(icapMarker)),gate=avMode==='gate'&&input.includes(Buffer.from('Public pending scan'));input.fill(0);if(avMode==='fail'){scanner.failures++;socket.end('ICAP/1.0 500 Server Error\r\n\r\n');return;}const reply=()=>socket.end(block?'ICAP/1.0 204 No Content\r\nISTag: "mvp-fixture"\r\nX-Infection-Found: Type=0; Threat=synthetic-mvp;\r\n\r\n':'ICAP/1.0 204 No Content\r\nISTag: "mvp-fixture"\r\n\r\n');if(block)scanner.blocks++;if(gate){scanner.pending=true;pendingAV=()=>{scanner.pending=false;pendingAV=null;reply();};}else reply();});}).listen(0,'127.0.0.1');await new Promise(r=>av.once('listening',r));
|
||||
const page=`<!doctype html><meta charset="utf-8"><title>Public AI workspace</title><style>body{font:18px Arial;padding:24px}input,textarea,[contenteditable]{display:block;width:80%;min-height:44px;border:1px solid #aaa;margin:15px}button{padding:12px}</style><h1>Public AI prompt workspace</h1><form id="prompt-form" method="post" action="/receiver/form"><input id="input" name="prompt" type="text"><button id="form-submit">Send prompt</button></form><textarea id="textarea"></textarea><div id="editor" role="textbox" contenteditable="true"><p><br></p></div><input id="file" type="file"><pre id="copy-source"></pre><script>window.mvpSubmit=(id)=>{const e=document.getElementById(id),text=e.isContentEditable?e.innerText:e.value;return fetch('/receiver/'+id,{method:'POST',headers:{'content-type':'text/plain'},body:text}).then(r=>r.ok).catch(()=>false)};</script>`;
|
||||
// eslint-disable-next-line no-useless-assignment -- Explicitly clear sensitive transient text after use.
|
||||
const sink=createServer({cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')},(req,res)=>{if(req.url==='/facts'){res.setHeader('content-type','application/json');res.end(JSON.stringify({receiver,scanner}));return;}if(req.url.startsWith('/scanner/')){const mode=req.url.split('/').at(-1);if(mode==='release')pendingAV?.();else avMode=mode;res.end('ok');return;}if(req.method==='POST'){let chunks=[],size=0;req.on('data',chunk=>{size+=chunk.length;if(size>1048576){req.destroy();return;}chunks.push(chunk);});req.on('end',()=>{receiver.posts++;const body=Buffer.concat(chunks);let decoded=body.toString('utf8');try{decoded=decodeURIComponent(decoded.replace(/\+/g,' '));}catch{}if(decoded.includes(secret)||body.includes(Buffer.from(secret))||decoded.includes(yaraMarker)||decoded.includes(icapMarker))receiver.secretPosts++;const kind=/^\/receiver\/(input|textarea|editor|form|typed|file|race|scanner|yara|icap)$/.exec(req.url)?.[1]||'other';receiver.kinds[kind]=(receiver.kinds[kind]||0)+1;if(kind==='file'){receiver.filePosts++;const boundary=/boundary=(?:"([^"]+)"|([^;]+))/.exec(req.headers['content-type']||'');if(boundary){const start=body.indexOf(Buffer.from('\r\n\r\n')),end=body.indexOf(Buffer.from('\r\n--'+(boundary[1]||boundary[2])),start+4);if(start>=0&&end>=0&&body.subarray(start+4,end).equals(publicFile))receiver.exactPublicFile++;}}decoded='';body.fill(0);for(const chunk of chunks)chunk.fill(0);chunks=[];res.end('accepted');});return;}res.setHeader('content-type','text/html');res.end(page);}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
const origin=`https://127.0.0.1:${sink.address().port}`,store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');const p=store.get('policy');p.browser.homeUrl=origin+'/workspace';p.browser.allowAgentBackgroundWorkers=false;p.dlp.rules.push({id:'mvp-company-secret',label:'Synthetic company confidential forecast',kind:'literal',value:secret,action:'block'});p.security.yara.enabled=true;p.security.yara.rules=[{id:'mvp-yara-only',name:'Synthetic MVP YARA',source:`rule mvp_yara_only { strings: $a = "${yaraMarker}" condition: $a }`}];p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${av.address().port}/scan`,scanUploads:true,scanDownloads:false,scanClipboard:false,scanAI:false};p.agents.enabled=false;p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:collector,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'mvp-fixture');
|
||||
const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement:true,requireIndependentPolicyApproval:true};const management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;const reviewer=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(reviewer),Date.now()+600000,JSON.stringify({sub:'independent-mvp-reviewer',role:'admin'}));
|
||||
const executable=createRequire(import.meta.url)('electron'),environment={...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_REVIEWER:reviewer,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ORIGIN:origin,SAFETYNET_SMOKE_PUBLIC_FILE:files.public,SAFETYNET_SMOKE_SECRET_FILE:files.secret};delete environment.SAFETYNET_SMOKE_CODE;
|
||||
async function runNative(script,extra={}){const child=spawn(executable,[script],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...environment,...extra}});child.stdout.on('data',v=>process.stdout.write(v));child.stderr.on('data',v=>process.stderr.write(v));const deadline=setTimeout(()=>child.kill(),240000),exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(deadline);return {exit,pid:child.pid};}
|
||||
let exit;try{const first=await runNative('scripts/windows-dlp-mvp-smoke.cjs',{SAFETYNET_SMOKE_CODE:code});exit=first.exit;
|
||||
if(exit===0){const proof=JSON.parse(readFileSync('artifacts/windows-dlp-mvp-native-proof.json','utf8'));writeFileSync('artifacts/windows-dlp-mvp-first-process-native-proof.json',JSON.stringify(proof,null,2));const cursor=store.db.prepare('SELECT COALESCE(MAX(id),0) AS id FROM events').get().id,devicesBefore=store.db.prepare('SELECT COUNT(*) AS count FROM devices').get().count;const restarted=await runNative('scripts/windows-dlp-mvp-restart-smoke.cjs',{SAFETYNET_SMOKE_FIRST_PID:String(proof.processId),SAFETYNET_SMOKE_EXPECT_DEVICE:proof.deviceId,SAFETYNET_SMOKE_EXPECT_POLICY:String(store.get('policy').version),SAFETYNET_SMOKE_AFTER_EVENT:String(cursor)});exit=restarted.exit;if(exit===0){const second=JSON.parse(readFileSync('artifacts/windows-dlp-mvp-restart-native-proof.json','utf8'));if(first.pid===restarted.pid||proof.processId===second.restartedProcessId||devicesBefore!==1||store.db.prepare('SELECT COUNT(*) AS count FROM devices').get().count!==devicesBefore)throw Error('Native restart enrollment isolation failed');writeFileSync('artifacts/windows-dlp-mvp-process-restart-summary.json',JSON.stringify({actualTwoProcessRuns:true,sameProtectedUserData:true,sameManagementBackend:true,deviceCount:devicesBefore,enrollmentNotRepeated:true,firstProcessId:proof.processId,restartedProcessId:second.restartedProcessId,policyVersion:second.policyVersion,firstProof:'windows-dlp-mvp-first-process-native-proof.json',restartProof:'windows-dlp-mvp-restart-native-proof.json'},null,2));}}
|
||||
}catch(error){console.error('Native restart harness failed: '+error.name);exit=1;}finally{pendingAV?.();for(const socket of sockets)socket.destroy();await new Promise(r=>av.close(r));for(const server of [management,sink])server.closeAllConnections();await Promise.all([management,sink].map(server=>new Promise(r=>server.close(r))));store.db.close();await telemetry?.shutdown();rmSync(temporary,{recursive:true,force:true});}process.exit(exit);
|
||||
@@ -0,0 +1,13 @@
|
||||
'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);}});
|
||||
@@ -0,0 +1,27 @@
|
||||
// Real pinned local Gemma inference, reviewed teaching and a private HTTPS receiver.
|
||||
import assert from 'node:assert/strict';
|
||||
import {createServer} from 'node:https';
|
||||
import {createRequire} from 'node:module';
|
||||
import {mkdtempSync,mkdirSync,readFileSync,writeFileSync,rmSync,existsSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join,resolve,sep} 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';
|
||||
import {startTelemetry} from '../apps/server/telemetry.mjs';
|
||||
const collector='http://127.0.0.1:4318';assert.equal((await fetch(collector+'/health')).ok,true);process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry(),temporary=mkdtempSync(join(tmpdir(),'safetynet-semantic-mvp-'));mkdirSync('artifacts',{recursive:true});assert.ok(resolve(temporary).startsWith(resolve(tmpdir())+sep)&&temporary.split(/[\\/]/).at(-1).startsWith('safetynet-semantic-mvp-'));
|
||||
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.'};
|
||||
const runtime=resolve('.runtime/windows/semantic'),python=resolve('.runtime/windows/python/python.exe');assert.ok(existsSync(join(runtime,'models/model-manifest.json')));const publicFile=join(temporary,'public.txt'),secretFile=join(temporary,'paraphrase.txt');writeFileSync(publicFile,fixtures.publicText);writeFileSync(secretFile,fixtures.paraphrase);
|
||||
const facts={posts:0,publicPosts:0,paraphrasePosts:0,filePosts:0,exactPublicFiles:0};let inspector,management,sink,store,child,stage='startup';
|
||||
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 timer=setTimeout(()=>{inspector.kill();reject(Error('Private semantic inspector startup timed out'));},40000);inspector.once('error',()=>reject(Error('Private semantic inspector startup failed')));inspector.once('exit',()=>{clearTimeout(timer);reject(Error('Private semantic inspector exited'));});inspector.stdout.on('data',chunk=>{output+=chunk;if(output.includes('\n')){clearTimeout(timer);try{const startup=JSON.parse(output.split('\n')[0]);assert.equal(startup.containment.parserIsolation.active,true);assert.equal(startup.containment.parserIsolation.networkCapabilities,0);accept(startup.port);}catch{reject(Error('Private semantic inspector attestation invalid'));}}});});
|
||||
sink=createServer({cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')},(req,res)=>{if(req.url==='/facts'){res.setHeader('content-type','application/json');res.end(JSON.stringify(facts));return;}if(req.method==='POST'){let chunks=[],size=0;req.on('data',chunk=>{size+=chunk.length;if(size>1048576){req.destroy();return;}chunks.push(chunk);});req.on('end',()=>{facts.posts++;const bytes=Buffer.concat(chunks);if(bytes.includes(Buffer.from(fixtures.paraphrase)))facts.paraphrasePosts++;if(bytes.includes(Buffer.from(fixtures.publicText)))facts.publicPosts++;if(req.url==='/receiver/file'){facts.filePosts++;const boundary=/boundary=(?:"([^"]+)"|([^;]+))/.exec(req.headers['content-type']||'');if(boundary){const start=bytes.indexOf(Buffer.from('\r\n\r\n')),end=bytes.indexOf(Buffer.from('\r\n--'+(boundary[1]||boundary[2])),start+4);if(start>=0&&end>=0&&bytes.subarray(start+4,end).equals(Buffer.from(fixtures.publicText)))facts.exactPublicFiles++;}}bytes.fill(0);for(const chunk of chunks)chunk.fill(0);chunks=[];res.end('accepted');});return;}res.setHeader('content-type','text/html');res.end('<!doctype html><title>Public local AI workspace</title><style>body{font:18px Arial;padding:25px}[contenteditable]{border:1px solid #999;width:85%;min-height:60px;margin:25px 0}</style><h1>Public AI workspace</h1><div id="editor" role="textbox" contenteditable="true"><p><br></p></div><input id="file" type="file"><script>window.submitPrompt=()=>fetch("/receiver/prompt",{method:"POST",headers:{"content-type":"text/plain"},body:document.getElementById("editor").innerText.trimEnd()}).then(r=>r.ok).catch(()=>false)</script>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));const origin=`https://127.0.0.1:${sink.address().port}`;
|
||||
store=openStore(join(temporary,'db'));const p=store.get('policy');p.browser.homeUrl=origin+'/workspace';p.browser.allowedHosts=['127.0.0.1'];p.agents.enabled=false;p.dlp.rules=[];p.security.yara.enabled=false;p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:collector,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+1800000,'semantic-mvp-user');const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement:true,requireIndependentPolicyApproval:true,inspectorUrl:`http://127.0.0.1:${inspectorPort}`,inspectorToken};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;const reviewer=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(reviewer),Date.now()+1800000,JSON.stringify({sub:'independent-semantic-reviewer',role:'admin'}));const headers={authorization:'Bearer '+adminToken,'content-type':'application/json'};
|
||||
async function admin(route,body,method='POST',status=200){const result=await fetch(config.publicUrl+route,{method,headers,body:JSON.stringify(body),signal:AbortSignal.timeout(120000)});assert.equal(result.status,status,'Semantic admin operation refused');return result.json();}
|
||||
async function approve(review){const result=await fetch(config.publicUrl+`/api/admin/policy-reviews/${review.reviewId}/approve`,{method:'POST',headers:{cookie:'sn_session='+reviewer,origin:config.publicUrl,'content-type':'application/json'},body:JSON.stringify({payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true})});assert.equal(result.status,200);}
|
||||
stage='reviewed document teaching';const teaching=await admin('/api/admin/teaching',{label:'Synthetic restricted workforce reference',filename:'synthetic-reference.txt',data:Buffer.from(fixtures.reference).toString('base64')});assert.equal(teaching.reference.registrationReady,true);assert.ok(teaching.reference.fragmentHashes.length>0);const publication=await admin(`/api/admin/teaching/${teaching.id}/publish`,{expectedVersion:p.version,approve:true},'POST',202);await approve(publication);const retained=JSON.parse(store.db.prepare('SELECT reference FROM teaching WHERE id=?').get(teaching.id).reference);assert.deepEqual(Object.keys(retained).sort(),['bytes','contentType','coverage','fragmentHashes','registrationReady','sha256']);assert.ok(!JSON.stringify(retained).includes(fixtures.reference));assert.ok(!JSON.stringify(retained).includes('Lisbon office'));assert.ok(retained.fragmentHashes.every(hash=>/^[a-f0-9]{64}$/.test(hash)));
|
||||
stage='real pinned local document embedding';const reference=await admin('/api/admin/semantic/document-reference',{filename:'synthetic-reference.txt',data:Buffer.from(fixtures.reference).toString('base64')});const model={model:'google/embeddinggemma-2',revision:'bfcd298762cc34d0357ece5ebdd31791a3a374d8',dimensions:768,profile:'document-v1',artifactFormat:'gguf',quantization:'Q8_0',runtimeRevision:'b11461',local:true};for(const [key,value]of Object.entries(model))assert.equal(reference[key],value,'Pinned local model contract changed');assert.equal(reference.coverage.complete,true);const semanticPolicy=store.get('policy');semanticPolicy.version++;semanticPolicy.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:'semantic-workforce-fixture',label:'Synthetic restricted workforce plan',threshold:0.65,prototype:reference.vector,prototypes:reference.vectors}]};assert.equal(inspect(Buffer.from(fixtures.paraphrase),semanticPolicy).decision,'allow','Paraphrase matches deterministic teaching; semantic proof invalid');const secretResult=await admin('/api/admin/policy/test',{text:fixtures.paraphrase,policy:semanticPolicy}),publicResult=await admin('/api/admin/policy/test',{text:fixtures.publicText,policy:semanticPolicy});assert.equal(secretResult.ruleId,'semantic-workforce-fixture');assert.equal(publicResult.decision,'allow');await approve(await admin('/api/admin/policy',semanticPolicy,'PUT',202));const signed=verifyPolicy(store.envelope(),store.publicKey());assert.equal(signed.dlp.semantic.revision,model.revision);assert.ok(!JSON.stringify(signed).includes(fixtures.reference));
|
||||
const teachingProof={independentlyReviewedTeaching:true,independentlyReviewedSemanticPolicy:true,fullDocumentNotRetained:true,retainedKeys:Object.keys(retained).sort(),retainedFragmentCount:retained.fragmentHashes.length,referenceCoverageComplete:true,deterministicParaphraseAllowed:true,semanticParaphraseBlocked:true,publicSemanticAllowed:true,policyVersion:signed.version,model,paraphraseSimilarity:secretResult.similarity?.[0]?.score,publicSimilarity:publicResult.similarity?.[0]?.score};writeFileSync('artifacts/windows-semantic-teaching-proof.json',JSON.stringify(teachingProof,null,2));
|
||||
stage='actual native semantic paste and upload';const env={...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ORIGIN:origin,SAFETYNET_SMOKE_PUBLIC_FILE:publicFile,SAFETYNET_SMOKE_SECRET_FILE:secretFile,SAFETYNET_SEMANTIC_FIXTURES:JSON.stringify(fixtures),SAFETYNET_SEMANTIC_POLICY:String(signed.version)};delete env.EMBEDDING_MODEL_DIR;delete env.EMBEDDING_WORKER_PATH;child=spawn(createRequire(import.meta.url)('electron'),['scripts/windows-semantic-dlp-smoke.cjs'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env});child.stdout.on('data',chunk=>process.stdout.write(chunk));child.stderr.on('data',chunk=>process.stderr.write(chunk));const timer=setTimeout(()=>child.kill(),240000),exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(exit,0,'Native semantic MVP failed');assert.equal(facts.paraphrasePosts,0);assert.equal(facts.filePosts,1);assert.equal(facts.exactPublicFiles,1);console.log('Reviewed local Gemma semantic MVP proof passed');
|
||||
}catch(error){writeFileSync('artifacts/windows-semantic-harness-error.json',JSON.stringify({stage,name:error.name},null,2));console.error('Semantic MVP harness failed at '+stage+': '+error.name);process.exitCode=1;}finally{child?.kill();inspector?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(r=>server.close(r));}store?.db.close();await telemetry?.shutdown();rmSync(temporary,{recursive:true,force:true});}
|
||||
Reference in new issue
Block a user