Implement managed browser security, individual agents and observability

This commit is contained in:
Karl committed 2026-10-13 16:58:15 +02:00
1 parent ff482ebe23
commit ea448de2d4
172 files changed
+5404 -347

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
// Normalize the diagnostic index completely before committing an encrypted batch.
function object(value){if(!value||typeof value!=='object'||Array.isArray(value))throw Error('Invalid OTLP object');return value;}
function array(value){if(value===undefined)return [];if(!Array.isArray(value))throw Error('Invalid OTLP array');return value;}
function time(value){if(typeof value!=='string'||!/^\d{1,20}$/.test(value))throw Error('Invalid span timestamp');const number=BigInt(value);if(number>18446744073709551615n)throw Error('Span timestamp overflow');return number;}
export function normalizeCollectorBatch(signal,payload){
object(payload);const normalized={spans:[],logs:0,metrics:0};
if(signal==='traces')for(const resource of array(payload.resourceSpans))for(const scope of array(object(resource).scopeSpans))for(const entry of array(object(scope).spans)){
const span=object(entry);if(typeof span.traceId!=='string'||! /^[a-f0-9]{32}$/.test(span.traceId)||/^0+$/.test(span.traceId)||typeof span.spanId!=='string'||! /^[a-f0-9]{16}$/.test(span.spanId)||/^0+$/.test(span.spanId))throw Error('Invalid span identity');
if(span.parentSpanId!==undefined&&span.parentSpanId!==''&&(typeof span.parentSpanId!=='string'||! /^[a-f0-9]{16}$/.test(span.parentSpanId)))throw Error('Invalid parent span identity');
if(typeof span.name!=='string')throw Error('Invalid span name');const start=time(span.startTimeUnixNano),end=time(span.endTimeUnixNano),duration=end-start;if(duration<0n||duration>BigInt(Number.MAX_SAFE_INTEGER))throw Error('Invalid span duration');
const attributes=array(span.attributes);for(const attribute of attributes)object(attribute);const value=attributes.find(a=>a.key==='safetynet.operation')?.value?.stringValue;
normalized.spans.push({traceId:span.traceId,spanId:span.spanId,parentSpanId:span.parentSpanId||null,name:span.name.slice(0,80),operation:typeof value==='string'?value.slice(0,80):'',durationMs:Number(duration)/1e6});
}
else if(signal==='logs')for(const resource of array(payload.resourceLogs))for(const scope of array(object(resource).scopeLogs)){const records=array(object(scope).logRecords);for(const record of records)object(record);normalized.logs+=records.length;}
else if(signal==='metrics'){for(const resource of array(payload.resourceMetrics))for(const scope of array(object(resource).scopeMetrics))for(const metric of array(object(scope).metrics))object(metric);normalized.metrics=1;}
else throw Error('Unknown OTLP signal');
return normalized;
}
+39
View File
@@ -0,0 +1,39 @@
// Encrypted developer telemetry only; this is not durable security evidence.
import {lstatSync,realpathSync,readdirSync,existsSync,renameSync,unlinkSync,openSync,writeSync,fsyncSync,closeSync} from 'node:fs';
import {resolve,join,dirname} from 'node:path';
import {randomUUID} from 'node:crypto';
export function collectorStorage(directory,{segmentBytes=50*1024*1024,retiredSegments=2}={}) {
if(!Number.isSafeInteger(segmentBytes)||segmentBytes<1024||!Number.isSafeInteger(retiredSegments)||retiredSegments<0||retiredSegments>10)throw Error('Invalid collector storage limits');
directory=resolve(directory);
const active=join(directory,'signals.encrypted.ndjson'),legacy=join(directory,'signals.legacy.encrypted.ndjson');
let rotations=0,droppedSegments=0,droppedBytes=0,rejectedWrites=0;
function guard(){
for(let current=directory;;current=dirname(current)){const s=lstatSync(current);if(!s.isDirectory()||s.isSymbolicLink()||realpathSync(current).toLowerCase()!==resolve(current).toLowerCase())throw Error('Unsafe collector directory');if(dirname(current)===current)break;}
const names=readdirSync(directory);
for(const name of names){if(!['collector-key.dpapi','signals.encrypted.ndjson','signals.legacy.encrypted.ndjson'].includes(name)&&!/^signals\.rolling\.[0-9]{13}\.[a-f0-9-]{36}\.encrypted\.ndjson$/.test(name))throw Error('Unexpected collector storage entry');const path=join(directory,name),s=lstatSync(path);if(!s.isFile()||s.isSymbolicLink()||s.nlink!==1||realpathSync(path).toLowerCase()!==path.toLowerCase())throw Error('Unsafe collector storage entry');}
return names.filter(n=>n.startsWith('signals.rolling.')).sort();
}
guard();
// The first old archive is pinned and never retention-deleted, including after restart.
function prune(){let retired=guard();while(retired.length>retiredSegments){const name=retired.shift(),path=join(directory,name);guard();const size=lstatSync(path).size;unlinkSync(path);droppedSegments++;droppedBytes+=size;}}
function rotate(){
guard();
if(existsSync(active)){
if(!existsSync(legacy))renameSync(active,legacy);
else renameSync(active,join(directory,`signals.rolling.${Date.now()}.${randomUUID()}.encrypted.ndjson`));
rotations++;
}
prune();
}
function append(line){
try{
guard();const bytes=Buffer.byteLength(line);if(bytes>segmentBytes)throw Error('Collector batch exceeds segment budget');prune();
const size=existsSync(active)?lstatSync(active).size:0;if(size+bytes>segmentBytes)rotate();
guard();const fd=openSync(active,existsSync(active)?'a':'wx',0o600);
try{const buffer=Buffer.from(line);let offset=0;while(offset<buffer.length){const count=writeSync(fd,buffer,offset,buffer.length-offset);if(count<1)throw Error('Collector short write');offset+=count;}fsyncSync(fd);}finally{closeSync(fd);}
}catch(error){rejectedWrites++;throw error;}
}
function summary(){const retired=guard(),activeBytes=existsSync(active)?lstatSync(active).size:0,legacyBytes=existsSync(legacy)?lstatSync(legacy).size:0;return {storedBytes:activeBytes,activeBytes,legacyBytes,retiredBytes:retired.reduce((n,name)=>n+lstatSync(join(directory,name)).size,0),retiredSegments:retired.length,segmentBytes,maxRollingBytes:segmentBytes*(retiredSegments+1),rotations,droppedSegments,droppedBytes,rejectedWrites};}
return {append,summary};
}
+41
View File
@@ -0,0 +1,41 @@
// Local development receiver. This is not a production SIEM or audit store.
import {createServer} from 'node:http';
import {mkdirSync,existsSync,readFileSync,writeFileSync} from 'node:fs';
import {collectorStorage} from './debug-collector-storage.mjs';
import {normalizeCollectorBatch} from './debug-collector-batch.mjs';
import {resolve} from 'node:path';
import {spawnSync} from 'node:child_process';
import {randomBytes,createCipheriv} from 'node:crypto';
const port=Number(process.env.DEBUG_COLLECTOR_PORT||4318),directory=resolve(process.env.DEBUG_COLLECTOR_DIRECTORY||'data/observability');
if(process.platform!=='win32')throw Error('Local protected collector currently requires Windows DPAPI');
mkdirSync(directory,{recursive:true});
const storage=collectorStorage(directory,{segmentBytes:Number(process.env.DEBUG_COLLECTOR_SEGMENT_BYTES||50*1024*1024),retiredSegments:Number(process.env.DEBUG_COLLECTOR_RETIRED_SEGMENTS||2)});
const acl=spawnSync('powershell.exe',['-NoProfile','-NonInteractive','-Command',"$ErrorActionPreference='Stop';$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User;$a=New-Object System.Security.AccessControl.DirectorySecurity;$a.SetOwner($sid);$a.SetAccessRuleProtection($true,$false);$a.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($sid,'FullControl','ContainerInherit,ObjectInherit','None','Allow')));Set-Acl -LiteralPath $env:SAFETYNET_COLLECTOR_DIRECTORY -AclObject $a"],{windowsHide:true,timeout:10000,env:{SystemRoot:process.env.SystemRoot,SAFETYNET_COLLECTOR_DIRECTORY:directory},stdio:'pipe'});
if(acl.status!==0)throw Error('Collector directory protection failed');
function protect(operation,value){const result=spawnSync(resolve('.runtime/windows/python/python.exe'),[resolve('apps/inspector/dpapi.py'),operation],{input:value,encoding:'utf8',windowsHide:true,timeout:5000,env:{SystemRoot:process.env.SystemRoot,USERPROFILE:process.env.USERPROFILE,LOCALAPPDATA:process.env.LOCALAPPDATA,PYTHONDONTWRITEBYTECODE:'1'}});if(result.status!==0)throw Error('Collector DPAPI key protection failed');return result.stdout.trim();}
const keyFile=resolve(directory,'collector-key.dpapi');
let key;if(existsSync(keyFile)){const value=protect('unprotect',readFileSync(keyFile,'utf8'));if(!value.startsWith('observability-v1:'))throw Error('Collector key binding rejected');key=Buffer.from(value.slice(17),'base64');}else{key=randomBytes(32);writeFileSync(keyFile,protect('protect','observability-v1:'+key.toString('base64')),{flag:'wx',mode:0o600});}
if(key.length!==32)throw Error('Invalid collector key');
const counts={traces:0,logs:0,metrics:0},recent=[];let requests=0;
function summary(){return {service:'safetynet-local-debug',protectedStorage:'Windows DPAPI + AES-256-GCM',requests,counts,...storage.summary(),retainedSpans:recent.length,slowest:recent.slice().sort((a,b)=>b.durationMs-a.durationMs).slice(0,15)};}
const server=createServer((req,res)=>{
res.setHeader('cache-control','no-store');res.setHeader('x-content-type-options','nosniff');
const origin=`http://127.0.0.1:${port}`;
if(req.headers.host!==`127.0.0.1:${port}`||(req.headers.origin&&req.headers.origin!==origin)){res.writeHead(403);res.end();return;}
if(req.method==='GET'&&['/','/health','/api/summary'].includes(req.url)){res.setHeader('content-type','application/json');try{res.end(JSON.stringify(summary(),null,2));}catch{res.writeHead(503);res.end('{"error":"Protected collector storage unavailable"}');}return;}
if(req.method==='GET'&&/^\/api\/trace\/[a-f0-9]{32}$/.test(req.url)){res.setHeader('content-type','application/json');res.end(JSON.stringify({traceId:req.url.slice(11),spans:recent.filter(span=>span.traceId===req.url.slice(11))},null,2));return;}
if(req.method!=='POST'||!['/v1/traces','/v1/logs','/v1/metrics'].includes(req.url)||req.headers['content-encoding']){res.writeHead(404);res.end();return;}
const chunks=[];let size=0;req.on('data',chunk=>{size+=chunk.length;if(size>2*1024*1024)req.destroy();else chunks.push(chunk);});
req.on('end',()=>{try{
const payload=JSON.parse(Buffer.concat(chunks).toString('utf8')),signal=req.url.slice(4);if(!payload||typeof payload!=='object'||Array.isArray(payload))throw Error('Invalid OTLP');
const normalized=normalizeCollectorBatch(signal,payload);
const nonce=randomBytes(12),cipher=createCipheriv('aes-256-gcm',key,nonce);cipher.setAAD(Buffer.from('safetynet-debug-v1'));
const plain=Buffer.from(JSON.stringify({signal,receivedAt:new Date().toISOString(),payload})),encrypted=Buffer.concat([cipher.update(plain),cipher.final()]);plain.fill(0);
const line=JSON.stringify({v:1,nonce:nonce.toString('base64'),tag:cipher.getAuthTag().toString('base64'),ciphertext:encrypted.toString('base64')})+'\n';
try{storage.append(line);}catch{res.writeHead(507);res.end('{"error":"Protected collector storage unavailable"}');return;}requests++;
counts.traces+=normalized.spans.length;counts.logs+=normalized.logs;counts.metrics+=normalized.metrics;recent.push(...normalized.spans);while(recent.length>5000)recent.shift();
res.setHeader('content-type','application/json');res.end('{}');
}catch{res.writeHead(400);res.end('{"error":"Invalid telemetry batch"}');}});
});
server.listen(port,'127.0.0.1',()=>console.log(`Local debug collector: http://127.0.0.1:${port}/api/summary`));
function stop(){server.closeAllConnections();server.close(()=>{key.fill(0);process.exit(0);});}process.on('SIGINT',stop);process.on('SIGTERM',stop);
+91 -5
View File
@@ -28,12 +28,46 @@ app.whenReady().then(async()=>{
const blocked=await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:'Secret Roadmap'}).then(()=>false).catch(()=>true)`);assert.equal(blocked,true);
const counts=await (await fetch(url+'/count')).json();assert.equal(counts.posts,1,'Secret payload reached the destination');
log('Request blocking passed');
// Use the already attached privileged page guard; no additional debugging port.
const fileAuditHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};
const filePriorIds=new Set((await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:fileAuditHeaders})).json()).events.map(e=>e.id));
await page.executeJavaScript('(()=>{const input=document.createElement("input");input.type="file";input.id="native-upload-fixture";document.body.append(input);})()');
const fileDocument=await page.debugger.sendCommand('DOM.getDocument');
const fileNode=await page.debugger.sendCommand('DOM.querySelector',{nodeId:fileDocument.root.nodeId,selector:'#native-upload-fixture'});
async function nativeFileUpload(file){await page.debugger.sendCommand('DOM.setFileInputFiles',{nodeId:fileNode.nodeId,files:[file]});return page.executeJavaScript(`(()=>{const data=new FormData();data.append('file',document.getElementById('native-upload-fixture').files[0]);return fetch(${JSON.stringify(url+'/file-upload-sink')},{method:'POST',body:data}).then(r=>r.text()).catch(()=>false);})()`);}
const fileBefore=(await (await fetch(url+'/count')).json()).fileUploadRequests;
assert.equal(await nativeFileUpload(process.env.SAFETYNET_SMOKE_PUBLIC_UPLOAD),'file received','Clean native file upload was blocked');
const fileAfter=(await (await fetch(url+'/count')).json());assert.equal(fileAfter.fileUploadRequests,fileBefore+1);assert.equal(fileAfter.fileUploadExact,true,'Actual file bytes differed at receiver');
assert.equal(await nativeFileUpload(process.env.SAFETYNET_SMOKE_SECRET_UPLOAD),false,'Confidential native file upload escaped');
assert.equal((await (await fetch(url+'/count')).json()).fileUploadRequests,fileBefore+1,'Blocked native file upload reached receiver');
let fileEvents;await clipboardWait(async()=>{fileEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:fileAuditHeaders})).json()).events.filter(e=>!filePriorIds.has(e.id));return fileEvents.some(e=>e.kind==='upload.authorized')&&fileEvents.some(e=>e.kind==='upload.block'&&e.metadata.ruleId==='secret-roadmap');});
const fileAuditText=JSON.stringify(fileEvents),fileCrypto=require('node:crypto');
for(const fixture of [process.env.SAFETYNET_SMOKE_PUBLIC_UPLOAD,process.env.SAFETYNET_SMOKE_SECRET_UPLOAD]){const bytes=fs.readFileSync(fixture);assert.equal(fileAuditText.includes(bytes.toString('utf8').trim()),false,'File contents entered ordinary audit');assert.equal(fileAuditText.includes(fileCrypto.createHash('sha256').update(bytes).digest('hex')),false,'File hash entered ordinary request audit');bytes.fill(0);}
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'native-file-upload-proof.json'),JSON.stringify({actualElectron:true,cleanFileReceivedExactlyOnce:true,exactFileBytesPreserved:true,secretFileNeverReachedReceiver:true,contentsAndHashesAbsentFromAudit:true,traceIds:fileEvents.map(e=>e.metadata.traceId).filter(Boolean)},null,2));
await page.executeJavaScript('document.getElementById("native-upload-fixture").remove()');
log('Managed native file upload receiver and confidentiality proof passed');
// Native Chromium header inspection must run before the dedicated receiver sees bytes.
const headerAuditHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};
const headerPriorIds=new Set((await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:headerAuditHeaders})).json()).events.map(e=>e.id));
const headerBefore=(await (await fetch(url+'/count')).json()).headerRequests;
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(url+'/header-sink')},{headers:{'X-SafetyNet-Test':'Secret Roadmap'}}).then(()=>false).catch(()=>true)`),true,'Secret custom header escaped inspection');
assert.equal((await (await fetch(url+'/count')).json()).headerRequests,headerBefore,'Blocked header request reached receiver');
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(url+'/header-sink')},{headers:{'X-SafetyNet-Test':'Public header fixture'}}).then(r=>r.text())`),'header received','Clean custom header was blocked');
assert.equal((await (await fetch(url+'/count')).json()).headerRequests,headerBefore+1,'Clean header request did not arrive exactly once');
let headerEvents,headerDecision;
await clipboardWait(async()=>{headerEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:headerAuditHeaders})).json()).events;headerDecision=headerEvents.find(e=>!headerPriorIds.has(e.id)&&e.kind==='dlp.block'&&e.metadata.ruleId==='secret-roadmap'&&(!process.env.SAFETYNET_TEST_OTLP_ENDPOINT||e.metadata.traceId));return !!headerDecision;});
assert.equal(JSON.stringify(headerEvents).includes('Secret Roadmap'),false,'Header secret appeared in audit');
assert.equal(JSON.stringify(headerEvents).includes('Public header fixture'),false,'Allowed header value appeared in audit');
assert.equal(JSON.stringify(headerEvents).includes('X-SafetyNet-Test'),false,'Header name appeared in audit');
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'request-headers-native-proof.json'),JSON.stringify({actualElectron:true,blockedHeaderNeverReachedReceiver:true,cleanHeaderReceivedOnce:true,headerNamesAndValuesAbsentFromAudit:true,traceId:headerDecision.metadata.traceId},null,2));
log('Native custom header DLP blocks before transmission and preserves clean requests');
// Real Chromium gestures and the Windows clipboard: never execute fixture commands.
const {clipboard}=require('electron');
const originalClipboardText=await clipboard.readText();
const clipboardSentinel='SafetyNet public clipboard fixture';
async function clipboardKey(key,shift=false){page.focus();page.sendInputEvent({type:'keyDown',keyCode:key,modifiers:shift?['shift']:['control']});page.sendInputEvent({type:'keyUp',keyCode:key,modifiers:shift?['shift']:['control']});await wait(800);await clipboardWait(async()=>!(await win.webContents.executeJavaScript('window.safetynet.state()')).data.clipboardPending);}
async function clipboardWait(predicate){for(let attempt=0;attempt<200;attempt++){if(await predicate())return;await wait(100);}throw Error('Clipboard fixture operation timed out');}
async function clipboardWait(predicate,stage='Native fixture'){for(let attempt=0;attempt<200;attempt++){if(await predicate())return;await wait(100);}throw Error(`${stage} operation timed out`);}
async function selectClipboardText(text){await page.executeJavaScript(`(()=>{let node=document.getElementById('clipboard-source');if(!node){node=document.createElement('pre');node.id='clipboard-source';document.body.append(node)}node.textContent=${JSON.stringify(text)};const range=document.createRange();range.selectNodeContents(node);const selection=getSelection();selection.removeAllRanges();selection.addRange(range);})()`);}
try{
await clipboard.writeText(clipboardSentinel);
@@ -73,7 +107,9 @@ app.whenReady().then(async()=>{
}finally{await clipboard.writeText(originalClipboardText);await page.executeJavaScript("document.getElementById('clipboard-source')?.remove();document.getElementById('public-input').value=''");}
const connection=await win.webContents.executeJavaScript('window.safetynet.agentConnection()');assert.equal(connection.ok,true,connection.error);
const agentRequest=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'agent-request.json');
let agentWindowStarted=Date.now(),agentCalls=0;
async function localAgent(body){
if(++agentCalls>20){const remaining=Math.max(0,61000-(Date.now()-agentWindowStarted));await wait(Math.min(remaining,30000));if(remaining>30000)await wait(remaining-30000);agentWindowStarted=Date.now();agentCalls=1;}
fs.writeFileSync(agentRequest,JSON.stringify(body));
const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.data.credentialPath,'--request',agentRequest],{stdio:['ignore','pipe','pipe'],windowsHide:true});let output='',errors='';child.stdout.on('data',c=>output+=c);child.stderr.on('data',c=>errors+=c);
const exit=await new Promise((resolve,reject)=>{child.on('exit',resolve);child.on('error',reject);});return {exit,output,errors};
@@ -82,8 +118,30 @@ app.whenReady().then(async()=>{
const readResult=await localAgent({action:'read',args:{sessionId}});assert.equal(readResult.exit,0,readResult.errors);assert.equal(readResult.output.includes('never-export-this'),false);
const typeResult=await localAgent({action:'type',args:{sessionId,selector:'#public-input',text:'Agent typed public text'}});assert.equal(typeResult.exit,0,typeResult.errors);assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Agent typed public text');
const deniedType=await localAgent({action:'type',args:{sessionId,selector:'#private-input',text:'forbidden'}});assert.equal(deniedType.exit,1);
const inspectResult=await localAgent({action:'inspect',args:{sessionId}});assert.equal(inspectResult.exit,0,inspectResult.errors);const inspected=JSON.parse(inspectResult.output).result;assert.ok(inspected.controls.some(control=>control.selector==='#public-input'&&control.value==='Agent typed public text'));assert.equal(inspectResult.output.includes('never-export-this'),false);
const scrollResult=await localAgent({action:'scroll',args:{sessionId,x:0,y:100}});assert.equal(scrollResult.exit,0,scrollResult.errors);
log('Authenticated agent sessions, sanitized read, type and sensitive-field denial passed');
let structuredAgentEvents;
await clipboardWait(async()=>{structuredAgentEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:{authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`}})).json()).events.filter(e=>e.metadata.agent);return structuredAgentEvents.some(e=>e.metadata.agent.action==='read'&&e.metadata.agent.outcome==='completed')&&structuredAgentEvents.some(e=>e.metadata.agent.action==='type'&&e.kind==='agent.denied'&&['denied','failed'].includes(e.metadata.agent.outcome));},'Structured agent audit');
const completedRead=structuredAgentEvents.find(e=>e.metadata.agent.action==='read'&&e.metadata.agent.outcome==='completed');
assert.equal(completedRead.metadata.agent.scope,'browser:read');assert.equal(completedRead.metadata.agent.sessionId,sessionId);
assert.equal(await page.executeJavaScript('document.getElementById("private-input").value'),'never-export-this','Blocked agent changed a sensitive field');
const blockedType=structuredAgentEvents.find(e=>e.metadata.agent.action==='type'&&e.kind==='agent.denied'&&['denied','failed'].includes(e.metadata.agent.outcome));
const serializedAgentEvents=JSON.stringify(structuredAgentEvents);
for(const privateValue of ['Agent typed public text','never-export-this','forbidden'])assert.equal(serializedAgentEvents.includes(privateValue),false,'Agent arguments reached durable audit');
assert.ok(structuredAgentEvents.every(e=>!Object.hasOwn(e.metadata.agent,'principalId')),'Shared capability fabricated individual identity');
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'structured-agent-audit-native-proof.json'),JSON.stringify({actualElectron:true,completedReadHasScopeAndSession:true,blockedActionRecorded:true,blockedActionOutcome:blockedType.metadata.agent.outcome,sensitiveFieldUnchanged:true,noArgumentsOrFabricatedIdentity:true,traceId:completedRead.metadata.traceId,eventId:completedRead.id},null,2));
async function agentApp(action,args={}){const response=await localAgent({action,args});assert.equal(response.exit,0,response.errors);return JSON.parse(response.output).result;}
await agentApp('select',{sessionId,selector:'#public-select',value:'two'});assert.equal(await page.executeJavaScript('document.getElementById("public-select").value'),'two');
const lockedSelect=await localAgent({action:'select',args:{sessionId,selector:'#public-select',value:'locked'}});assert.equal(lockedSelect.exit,1);
await agentApp('check',{sessionId,selector:'#public-checkbox',checked:true});assert.equal(await page.executeJavaScript('document.getElementById("public-checkbox").checked'),true);
await agentApp('check',{sessionId,selector:'#public-checkbox',checked:false});assert.equal(await page.executeJavaScript('document.getElementById("public-checkbox").checked'),false);
await agentApp('navigate',{sessionId,url:url+'/history-second'});assert.ok(page.getURL().endsWith('/history-second'));
await agentApp('back',{sessionId});assert.equal(new URL(page.getURL()).pathname,'/');
await agentApp('forward',{sessionId});assert.ok(page.getURL().endsWith('/history-second'));
await agentApp('reload',{sessionId});assert.ok(page.getURL().endsWith('/history-second'));
await agentApp('back',{sessionId});assert.equal(new URL(page.getURL()).pathname,'/');
log('PQ agent history back/forward and same-URL reload passed');
const appStatus=await agentApp('application:state');assert.equal(appStatus.activeTabId,sessionId);
await agentApp('settings:update',{appearance:{mode:'dark'}});assert.equal((await agentApp('settings:get')).appearance.mode,'dark');
const library=await agentApp('bookmarks:list');const bookmark={id:'agent-native-proof',title:'Public fixture bookmark',url:url+'/'};
@@ -138,7 +196,7 @@ app.whenReady().then(async()=>{
await win.webContents.executeJavaScript('document.getElementById("passwords").click()');await wait(200);
assert.equal(await win.webContents.executeJavaScript('document.getElementById("vault").hidden'),false);
try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'browser-vault.png'),(await win.webContents.capturePage()).toPNG());}catch{log('Vault screenshot unavailable');}
win.emit('blur');await wait(100);assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultStatus()')).data.locked,true);
win.emit('blur');await wait(100);const blurVaultStatus=await win.webContents.executeJavaScript('window.safetynet.vaultStatus()');if(!blurVaultStatus.ok){const failedState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;log('Vault status denial: '+JSON.stringify({error:blurVaultStatus.error,ready:failedState.ready,message:failedState.message,policyVersion:failedState.policyVersion}));}assert.equal(blurVaultStatus.ok,true,blurVaultStatus.error);assert.equal(blurVaultStatus.data.locked,true);
assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultList()')).ok,false);
assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultUnlock("wrong-fixture-password-only")')).ok,false);
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultUnlock(${JSON.stringify(fixtureMaster)})`)).ok,true);
@@ -151,10 +209,20 @@ app.whenReady().then(async()=>{
await page.executeJavaScript('document.getElementById("safe-download").click()');
const safeFile=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'safe.bin');for(let n=0;n<600;n++){if(fs.existsSync(safeFile)&&fs.statSync(safeFile).size===5)break;await wait(100);}
assert.equal(fs.existsSync(safeFile),true,'Scanned benign download was not released');assert.deepEqual(fs.readFileSync(safeFile),Buffer.from([0,1,2,3,255]));assert.equal(saveCalls,1);
await clipboardWait(async()=>{const completed=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;return completed.ready&&completed.message==='Download scanned and saved.';});
await page.executeJavaScript('document.getElementById("blocked-download").click()');
for(let n=0;n<300;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.message.startsWith('Download blocked:'))break;await wait(100);}
assert.equal(fs.existsSync(path.join(process.env.SAFETYNET_SMOKE_USERDATA,'blocked.bin')),false);assert.equal(saveCalls,1,'Blocked download reached save dialog');
log('Real YARA download gate: safe binary saved; blocked file never reaches save dialog/filesystem');
const evidenceHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};let recorded;
await clipboardWait(async()=>{recorded=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:evidenceHeaders})).json()).events.filter(e=>e.kind==='evidence.recorded');return recorded.length>=2;});
const captured=[];for(const event of recorded){const response=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/evidence/${event.metadata.evidenceId}?reason=investigation`,{headers:evidenceHeaders});assert.equal(response.status,200);captured.push((await response.json()).payload);}
assert.ok(captured.some(p=>p.decision==='allow'&&p.name==='safe.bin'&&p.bytes===5&&p.sourceUrl===url+'/download-safe'));
assert.ok(captured.some(p=>p.decision==='block'&&p.name==='blocked.bin'&&p.sourceUrl===url+'/download-blocked'));
assert.ok(captured.every(p=>!p.content&&!p.data&&!p.filePath));
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'download-evidence-native-proof.json'),JSON.stringify({actualElectron:true,allowedDownloadCaptured:true,blockedDownloadCaptured:true,noFileContents:true,protectedAccessAudited:true,eventIds:recorded.map(e=>e.metadata.sourceEventId)},null,2));
log('Native allowed/blocked download metadata captured in encrypted evidence with audited analyst access');
}finally{dialog.showSaveDialog=originalSave;}
const sample=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'secret-sample.docx');
const runtime=path.resolve('.runtime/windows/python/python.exe');
@@ -167,6 +235,16 @@ app.whenReady().then(async()=>{
}
await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:new Uint8Array([255,254])}).catch(()=>{})`);
assert.equal((await (await fetch(url+'/count')).json()).posts,1,'Binary upload reached the destination');
const requestAuditHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};
const credentialBody='password=synthetic-low-entropy-only';
await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:${JSON.stringify(credentialBody)}}).then(r=>r.text())`);
let requestEvents;await clipboardWait(async()=>{requestEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:requestAuditHeaders})).json()).events;return requestEvents.some(e=>e.kind==='upload.authorized'&&e.metadata.request?.bytes===Buffer.byteLength(credentialBody));});
const credentialHash=require('node:crypto').createHash('sha256').update(credentialBody).digest('hex');
assert.equal(JSON.stringify(requestEvents).includes(credentialBody),false,'Credential request body leaked into central events');
assert.equal(JSON.stringify(requestEvents).includes(credentialHash),false,'Credential request fingerprint leaked into central events');
assert.ok(requestEvents.filter(e=>e.kind.startsWith('upload.')).every(e=>!e.metadata.file),'Upload request bodies remained classified as hash-bearing files');
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'request-privacy-native-proof.json'),JSON.stringify({actualElectron:true,credentialRequestReceived:true,bodyNotLogged:true,bodyHashNotLogged:true,safeBytesRetained:true,traceId:requestEvents.find(e=>e.kind==='upload.authorized'&&e.metadata.request?.bytes===Buffer.byteLength(credentialBody))?.metadata.traceId},null,2));
log('Real credential POST preserves byte count without body or fingerprint logging');
log('Capturing browser');
try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'browser.png'),(await win.webContents.capturePage()).toPNG());}catch(e){log('Screenshot unavailable: '+e.message);}
log('Loading admin console');
@@ -175,6 +253,7 @@ app.whenReady().then(async()=>{
await admin.webContents.executeJavaScript(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_SMOKE_ADMIN)};document.getElementById('connect').click();`);
for(let n=0;n<30;n++){if(await admin.webContents.executeJavaScript('!document.getElementById("workspace").hidden'))break;await wait(100);}
assert.equal(await admin.webContents.executeJavaScript('document.getElementById("workspace").hidden'),false);
assert.equal(await admin.webContents.executeJavaScript('document.getElementById("evidence-policy-enabled").checked&&document.getElementById("evidence-policy-downloads").checked'),true,'Signed evidence policy missing from actual admin controls');
try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'admin.png'),(await admin.webContents.capturePage()).toPNG());}catch(e){log('Screenshot unavailable: '+e.message);}
const headers={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`,'content-type':'application/json'};
const secondary=process.env.SAFETYNET_SMOKE_SECONDARY;
@@ -189,6 +268,7 @@ app.whenReady().then(async()=>{
const publication=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(managedPolicy)});assert.equal(publication.status,200);
for(let n=0;n<180;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===3)break;await wait(100);}
const navigation=await win.webContents.executeJavaScript(`window.safetynet.navigate('http://proxy-check.invalid/')`);assert.equal(navigation.ok,true,navigation.error);
for(let n=0;n<50;n++){if((await (await fetch(url+'/count')).json()).proxyRequests>0)break;await wait(100);}
assert.ok((await (await fetch(url+'/count')).json()).proxyRequests>0,'Managed proxy did not receive browser navigation');log('Managed proxy enforcement passed');
const reference='The confidential engineering roadmap contains restricted architecture budgets and unreleased customer contracts.';
const draftResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/teaching`,{method:'POST',headers,body:JSON.stringify({label:'Internal engineering reference',filename:'internal.txt',data:Buffer.from(reference).toString('base64')})});assert.equal(draftResponse.status,200);
@@ -236,9 +316,15 @@ app.whenReady().then(async()=>{
for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===9&&!s.data.loginCheckPending)break;await wait(100);}
assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,true,'Approved visual origin blocked');
const postsBeforePhish=(await (await fetch(url+'/count')).json()).posts;
await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url+'/visual-login')})`);
for(let n=0;n<120;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready)break;await wait(100);}
const phishingState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;assert.equal(phishingState.ready,false);assert.equal(phishingState.message,'Blocked: this login resembles a protected brand on an unapproved origin.');
const phishingNavigation=await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url+'/visual-login')})`);
const expectedPhishingMessage='Blocked: this login resembles a protected brand on an unapproved origin.';
for(let n=0;n<120;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready&&s.data.message===expectedPhishingMessage)break;await wait(100);}
const phishingState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;
const visualDiagnosticEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).events.filter(e=>e.kind==='phishing.block');
// Only fixed synthetic fixture state and safe identifiers enter this artifact.
const knownMessages=[expectedPhishingMessage,'Login protection could not approve this view. Credential actions are held.','Blocked: this login could not be inspected safely.'];
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'visual-phishing-native-diagnostic.json'),JSON.stringify({stage:'unapproved-synthetic-login',navigationOK:phishingNavigation.ok===true,navigationErrorClass:phishingNavigation.error?.includes('ERR_BLOCKED_BY_CLIENT')?'ERR_BLOCKED_BY_CLIENT':phishingNavigation.error?'other-error':null,ready:phishingState.ready,policyVersion:phishingState.policyVersion,loginCheckPending:phishingState.loginCheckPending,message:knownMessages.includes(phishingState.message)?phishingState.message:'other-status',phishingEvents:visualDiagnosticEvents.map(e=>({id:e.id,ruleId:e.metadata.ruleId,traceId:e.metadata.traceId})),destinationPostsUnchanged:(await (await fetch(url+'/count')).json()).posts===postsBeforePhish},null,2));
assert.equal(phishingState.ready,false,'Unapproved visual login did not enter quarantine; see visual-phishing-native-diagnostic.json');assert.equal(phishingState.message,expectedPhishingMessage);
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(page.getURL()+'#same-document')})`)).ok,false,'Hash-only navigation escaped visual quarantine');
let visualEvents;await clipboardWait(async()=>{visualEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).events;return visualEvents.some(e=>e.kind==='phishing.block'&&e.metadata.ruleId==='identity-lab');});assert.ok(visualEvents.some(e=>e.kind==='phishing.block'&&e.metadata.ruleId==='identity-lab'),'Visual brand decision was not audited');
assert.equal((await (await fetch(url+'/count')).json()).posts,postsBeforePhish);
+56
View File
@@ -0,0 +1,56 @@
'use strict';
const {app,BrowserWindow}=require('electron');
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs');
const wait=ms=>new Promise(r=>setTimeout(r,ms)),headers={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`,'content-type':'application/json'};
async function until(fn){for(let n=0;n<200;n++){const result=await fn();if(result)return result;await wait(100);}throw Error('Individual native fixture timed out');}
const overview=async()=> (await fetch(process.env.SAFETYNET_SMOKE_SERVER+'/api/admin/overview',{headers})).json();
app.whenReady().then(async()=>{let page;try{
const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});
if(process.env.SAFETYNET_SMOKE_PHASE==='restart'){
await until(async()=> (await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready);
page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;
assert.equal((await win.webContents.executeJavaScript('window.safetynet.agentConnection("reader-a")')).ok,false,'Restart accepted restored generation below durable floor');
const state=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;assert.ok(Array.isArray(state.agentGrants));assert.ok(!state.agentGrants.some(g=>g.id==='reader-a'),'Restored old generation visible after restart');assert.ok(state.agentGrants.some(g=>g.id==='state-b'),'Unaffected actor missing after restart');
const oldFile=path.join(app.getPath('userData'),'old-generation-fixture.json'),requestFile=path.join(app.getPath('userData'),'restart-request.json');fs.writeFileSync(requestFile,JSON.stringify({action:'sessions',args:{}}));
const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',oldFile,'--request',requestFile],{windowsHide:true,stdio:['ignore','ignore','ignore']});assert.equal(await new Promise(r=>child.once('exit',r)),1,'Old descriptor reached restarted listener');
const proofFile=path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-native-proof.json'),proof=JSON.parse(fs.readFileSync(proofFile,'utf8'));proof.durableGenerationFloorAfterRestart=true;proof.oldDescriptorRejectedAfterRestart=true;fs.writeFileSync(proofFile,JSON.stringify(proof,null,2));
require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);console.log('Individual generation floor restart proof passed');app.exit(0);return;
}
const enroll=await win.webContents.executeJavaScript(`window.safetynet.enroll(${JSON.stringify({server:process.env.SAFETYNET_SMOKE_SERVER,code:process.env.SAFETYNET_SMOKE_CODE})})`);assert.equal(enroll.ok,true);
page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading());
assert.equal((await win.webContents.executeJavaScript('window.safetynet.agentConnection()')).ok,false);
await win.webContents.executeJavaScript('document.getElementById("agents").click()');await until(()=>win.webContents.executeJavaScript('document.getElementById("agent-pairing")?.open'));
assert.deepEqual(await win.webContents.executeJavaScript('Array.from(document.querySelectorAll(".agent-grant-choice strong"),e=>e.textContent)'),['Reader A','State B']);
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-pairing-ui.png'),(await win.webContents.capturePage()).toPNG());
for(const index of [0,1]){await win.webContents.executeJavaScript(`document.querySelectorAll('.agent-grant-choice')[${index}].click()`);await until(()=>win.webContents.executeJavaScript(`document.getElementById('agent-pairing-result').textContent.startsWith(${JSON.stringify('Identity: '+(index===0?'Reader A':'State B'))})`));}
await win.webContents.executeJavaScript('document.getElementById("agent-pairing").close()');
const pair=async id=>{const r=await win.webContents.executeJavaScript(`window.safetynet.agentConnection(${JSON.stringify(id)})`);assert.equal(r.ok,true);return r.data;};
const a=await pair('reader-a'),b=await pair('state-b');assert.notEqual(a.credentialPath,b.credentialPath);assert.equal(fs.existsSync(path.join(app.getPath('userData'),'agent-connection.json')),false);
fs.copyFileSync(a.credentialPath,path.join(app.getPath('userData'),'old-generation-fixture.json'));
for(const c of [a,b]){const descriptor=JSON.parse(fs.readFileSync(c.credentialPath,'utf8'));assert.equal(descriptor.format,'windows-dpapi-v1');assert.equal(Object.hasOwn(descriptor,'token'),false);}
const requestFile=path.join(app.getPath('userData'),'individual-fixture-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 sessions=await call(a,'sessions');assert.equal(sessions.exit,0);const sessionId=sessions.result[0].id;
assert.equal((await call(a,'read',{sessionId})).exit,0);assert.equal((await call(a,'application:state')).exit,1);assert.equal((await call(b,'application:state')).exit,0);assert.equal((await call(b,'read',{sessionId})).exit,1);
assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_SINK+'/allowed'})).exit,0,'Approved host navigation failed');
assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_FORBIDDEN})).exit,1,'Actor reached forbidden host');
assert.equal((await call(a,'navigate',{sessionId,url:process.env.SAFETYNET_SMOKE_SINK+'/redirect'})).exit,1,'Actor followed forbidden redirect');
assert.equal((await (await fetch(process.env.SAFETYNET_SMOKE_SINK+'/count')).json()).forbiddenRequests,0,'Forbidden receiver received actual network request');
const humanNavigation=await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(process.env.SAFETYNET_SMOKE_SINK+'/human')})`);assert.equal(humanNavigation.ok,true,'Human navigation failed');await until(()=>!page.isLoading());
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(process.env.SAFETYNET_SMOKE_SINK+'/count')}).then(r=>r.ok)`),true,'Owned tab request failed after human navigation');
const managedSession=require('electron').session.fromPartition('persist:safetynet-managed');let ownerlessRejected=false;try{await managedSession.fetch(process.env.SAFETYNET_SMOKE_SINK+'/ownerless');}catch{ownerlessRejected=true;}assert.equal(ownerlessRejected,true,'Ownerless session request escaped individual-mode ownership guard');
assert.equal((await (await fetch(process.env.SAFETYNET_SMOKE_SINK+'/count')).json()).ownerlessRequests,0,'Ownerless request reached receiver');
const change=async modify=>{const policy=(await overview()).policy;modify(policy);policy.version++;const r=await fetch(process.env.SAFETYNET_SMOKE_SERVER+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(policy)});assert.equal(r.status,200);await until(async()=>{const s=await win.webContents.executeJavaScript('window.safetynet.state()');return s.data.ready&&s.data.policyVersion===policy.version;});};
await change(p=>{p.agents.grants.find(g=>g.id==='reader-a').enabled=false;});assert.equal((await call(a,'sessions')).exit,1);assert.equal((await call(b,'application:state')).exit,0);
await change(p=>{const g=p.agents.grants.find(g=>g.id==='reader-a');g.enabled=true;g.generation=2;});const generationTwo=await pair('reader-a');assert.equal(generationTwo.generation,2);assert.equal((await call(generationTwo,'sessions')).exit,0);
await change(p=>{p.agents.grants.find(g=>g.id==='reader-a').generation=1;});assert.equal((await win.webContents.executeJavaScript('window.safetynet.agentConnection("reader-a")')).ok,false,'Live rollback bypassed generation floor');
const events=await until(async()=>{const e=(await overview()).events.filter(e=>e.metadata.agent?.principalId);return e.some(e=>e.metadata.agent.principalId==='reader-a'&&e.kind==='agent.action')&&e.some(e=>e.metadata.agent.principalId==='state-b'&&e.kind==='agent.action')&&e;});
await wait(1500);let traceProof;
for(const event of events.filter(e=>e.kind==='agent.action'&&e.metadata.traceId)){const r=await fetch('http://127.0.0.1:4318/api/trace/'+event.metadata.traceId);if(!r.ok)continue;const trace=await r.json();const spans=trace.spans||trace; if(Array.isArray(spans)&&spans.some(s=>s.name==='browser.agent'&&s.operation==='agent.request')){traceProof={traceId:event.metadata.traceId,principalId:event.metadata.agent.principalId,stages:spans.map(s=>({name:s.name,operation:s.operation,parentSpanId:s.parentSpanId,spanId:s.spanId,durationMs:s.durationMs}))};break;}}
assert.ok(traceProof,'Actual agent request trace missing');
const root=traceProof.stages.find(s=>s.operation==='agent.request');assert.equal(root.parentSpanId,null);
for(const operation of ['agent.decrypt','agent.authorize','agent.dispatch','agent.project','agent.audit','agent.encrypt'])assert.ok(traceProof.stages.some(s=>s.operation===operation&&s.parentSpanId===root.spanId&&Number.isFinite(s.durationMs)&&s.durationMs>=0),'Missing correlated stage '+operation);
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'individual-agent-native-proof.json'),JSON.stringify({actualElectron:true,explicitGrantSelection:true,renderedGrantChoices:true,uiPairingBothActors:true,separateDPAPIDescriptors:true,noLegacyDescriptor:true,twoActors:true,scopeIntersection:true,approvedHostNavigation:true,forbiddenHostDeniedBeforeTransmission:true,forbiddenRedirectDeniedBeforeTransmission:true,forbiddenReceiverRequests:0,humanNavigationPreserved:true,ownedTabFetchAfterHumanNavigation:true,ownerlessSessionFetchBlockedBeforeTransmission:true,ownerlessReceiverRequests:0,targetedRevocation:true,unaffectedPeer:true,liveGenerationRollbackDenied:true,attributedAudit:true,traceProof},null,2));console.log('Individual agent native proof passed');
require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0);
}catch(error){console.error(error.stack);if(page)require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});
+29
View File
@@ -0,0 +1,29 @@
import {createServer} from 'node:http';
import {createRequire} from 'node:module';
import {mkdtempSync,mkdirSync,rmSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join,resolve} from 'node:path';
import {spawn} from 'node:child_process';
import {randomBytes} from 'node:crypto';
import {createApp} from '../apps/server/app.mjs';
import {openStore,digest} from '../apps/server/store.mjs';
import {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-individual-'));mkdirSync('artifacts',{recursive:true});
if(!resolve(temporary).startsWith(resolve(tmpdir())+requireSeparator())||!temporary.split(/[\\/]/).at(-1).startsWith('safetynet-individual-'))throw Error('Unsafe temporary fixture path');
function requireSeparator(){return process.platform==='win32'?'\\':'/';}
const store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');
let forbiddenRequests=0,ownerlessRequests=0;const forbidden=createServer((_req,res)=>{forbiddenRequests++;res.end('Forbidden receiver');}).listen(0,'127.0.0.2');await new Promise(r=>forbidden.once('listening',r));
const forbiddenUrl=`http://127.0.0.2:${forbidden.address().port}/forbidden`;
const sink=createServer((req,res)=>{if(req.url==='/count'){res.setHeader('content-type','application/json');res.end(JSON.stringify({forbiddenRequests,ownerlessRequests}));return;}if(req.url==='/ownerless')ownerlessRequests++;if(req.url==='/redirect'){res.writeHead(302,{location:forbiddenUrl});res.end();return;}res.setHeader('content-type','text/html');res.end('<!doctype html><title>Individual actor fixture</title><h1>Public actor fixture</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
const p=store.get('policy');p.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;
p.agents={...p.agents,enabled:true,identityMode:'individual',scopes:['sessions','navigate','read','application:state','settings:get'],allowedHosts:['127.0.0.1','127.0.0.2'],grants:[{id:'reader-a',name:'Reader A',enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes:['sessions','navigate','read'],allowedHosts:['127.0.0.1'],generation:1},{id:'state-b',name:'State B',enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes:['application:state'],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,'individual-fixture');
const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement: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 electron=createRequire(import.meta.url)('electron');
async function run(phase){const child=spawn(electron,['scripts/individual-agent-electron.cjs'],{windowsHide:true,stdio:'inherit',env:{...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ARTIFACTS:resolve('artifacts'),SAFETYNET_SMOKE_PHASE:phase,SAFETYNET_SMOKE_SINK:p.browser.homeUrl,SAFETYNET_SMOKE_FORBIDDEN:forbiddenUrl}});const deadline=setTimeout(()=>child.kill(),180000);const exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(deadline);return exit;}
let exit=await run('initial');if(exit===0)exit=await run('restart');
for(const server of [management,sink,forbidden])server.closeAllConnections();await Promise.all([new Promise(r=>management.close(r)),new Promise(r=>sink.close(r)),new Promise(r=>forbidden.close(r))]);store.db.close();await telemetry?.shutdown();rmSync(temporary,{recursive:true,force:true});process.exit(exit);
+6 -5
View File
@@ -7,12 +7,13 @@ import {join,resolve,sep,basename} from 'node:path';
import {spawn} from 'node:child_process';
import {randomBytes} from 'node:crypto';
import {createApp} from '../apps/server/app.mjs';
import {startTelemetry} from '../apps/server/telemetry.mjs';
import {openStore,digest} from '../apps/server/store.mjs';
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-native-tabs-'));
let sink,management,collector,store,child;const requests={},posts=[],diagnosticRequests=[];let privateSentinels=[];
const diagnosticStatus=()=>{const spans=diagnosticRequests.filter(r=>r.path.endsWith('/traces')).flatMap(r=>r.payload.resourceSpans||[]).flatMap(r=>r.scopeSpans||[]).flatMap(r=>r.spans||[]);const logs=diagnosticRequests.filter(r=>r.path.endsWith('/logs')).flatMap(r=>r.payload.resourceLogs||[]).flatMap(r=>r.scopeLogs||[]).flatMap(r=>r.logRecords||[]);return {exports:diagnosticRequests.length,names:[...new Set(spans.map(span=>span.name))],spans:spans.length,logs:logs.length,timings:spans.every(span=>BigInt(span.endTimeUnixNano)>=BigInt(span.startTimeUnixNano)&&span.attributes.some(a=>a.key==='safetynet.duration.ms')),parentChild:spans.some(child=>child.parentSpanId&&spans.some(parent=>parent.spanId===child.parentSpanId&&parent.traceId===child.traceId)),correlatedLogs:logs.some(log=>log.traceId&&spans.some(span=>span.traceId===log.traceId&&span.spanId===log.spanId)),sensitivePresent:privateSentinels.some(secret=>JSON.stringify(diagnosticRequests).includes(secret))};};
let sink,management,collector,store,child,sdk;const previousEndpoint=process.env.OTEL_EXPORTER_OTLP_ENDPOINT;const requests={},posts=[],diagnosticRequests=[];let privateSentinels=[];
const diagnosticStatus=()=>{const spans=diagnosticRequests.filter(r=>r.path.endsWith('/traces')).flatMap(r=>r.payload.resourceSpans||[]).flatMap(r=>r.scopeSpans||[]).flatMap(r=>r.spans||[]);const logs=diagnosticRequests.filter(r=>r.path.endsWith('/logs')).flatMap(r=>r.payload.resourceLogs||[]).flatMap(r=>r.scopeLogs||[]).flatMap(r=>r.logRecords||[]);return {exports:diagnosticRequests.length,names:[...new Set(spans.map(span=>span.name))],spans:spans.length,logs:logs.length,timings:spans.every(span=>BigInt(span.endTimeUnixNano)>=BigInt(span.startTimeUnixNano)&&span.attributes.some(a=>a.key==='safetynet.duration.ms')),parentChild:spans.some(child=>child.parentSpanId&&spans.some(parent=>parent.spanId===child.parentSpanId&&parent.traceId===child.traceId)),crossProcessTrace:spans.some(child=>child.name==='server.request'&&spans.some(parent=>parent.name==='browser.management'&&parent.spanId===child.parentSpanId&&parent.traceId===child.traceId)),crossProcessTraceIds:[...new Set(spans.filter(child=>child.name==='server.request'&&spans.some(parent=>parent.name==='browser.management'&&parent.spanId===child.parentSpanId&&parent.traceId===child.traceId)).map(span=>span.traceId))],correlatedLogs:logs.some(log=>log.traceId&&spans.some(span=>span.traceId===log.traceId&&span.spanId===log.spanId)),sensitivePresent:privateSentinels.some(secret=>JSON.stringify(diagnosticRequests).includes(secret))};};
try{
collector=createServer((req,res)=>{if(req.method==='GET'&&req.url==='/status'){res.setHeader('content-type','application/json');res.end(JSON.stringify(diagnosticStatus()));return;}const chunks=[];req.on('data',chunk=>chunks.push(chunk));req.on('end',()=>{diagnosticRequests.push({path:req.url,payload:JSON.parse(Buffer.concat(chunks).toString())});res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');await new Promise(r=>collector.once('listening',r));const collectorUrl=`http://127.0.0.1:${collector.address().port}`;
collector=createServer((req,res)=>{if(req.method==='GET'&&req.url==='/status'){res.setHeader('content-type','application/json');res.end(JSON.stringify(diagnosticStatus()));return;}const chunks=[];req.on('data',chunk=>chunks.push(chunk));req.on('end',()=>{diagnosticRequests.push({path:req.url,payload:JSON.parse(Buffer.concat(chunks).toString())});res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');await new Promise(r=>collector.once('listening',r));const collectorUrl=`http://127.0.0.1:${collector.address().port}`;process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collectorUrl;sdk=startTelemetry();
sink=createServer((req,res)=>{const path=new URL(req.url,'http://localhost').pathname;requests[path]=(requests[path]||0)+1;
if(path==='/counts'){res.setHeader('content-type','application/json');res.end(JSON.stringify({requests,posts}));return;}
if(req.method==='POST'){posts.push(path);req.resume();res.end('received');return;}
@@ -24,5 +25,5 @@ try{
const env={...process.env,SAFETYNET_TABS_COLLECTOR:collectorUrl,SAFETYNET_TABS_SERVER:config.publicUrl,SAFETYNET_TABS_CODE:code,SAFETYNET_TABS_ADMIN:adminToken,SAFETYNET_TABS_SINK:url,SAFETYNET_TABS_USERDATA:join(directory,'browser'),SAFETYNET_SMOKE_USERDATA:join(directory,'browser')};delete env.ELECTRON_RUN_AS_NODE;
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');const diagnostics=diagnosticStatus();assert.ok(diagnostics.spans>0&&diagnostics.logs>0&&diagnostics.timings&&diagnostics.parentChild&&diagnostics.correlatedLogs);assert.equal(diagnostics.sensitivePresent,false);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));
}finally{child?.kill();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});}
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));
}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});}
+16 -10
View File
@@ -15,31 +15,37 @@ root=Path.cwd().resolve()
directory=root/'.runtime'/'windows'/'python'
directory.mkdir(parents=True,exist_ok=True)
requirements=root/'apps'/'inspector'/'requirements.lock.txt'
requirements_hash=hashlib.sha256(requirements.read_bytes()).hexdigest()
macro_requirements=root/'apps'/'inspector'/'requirements-macros.lock.txt'
requirements_hash=hashlib.sha256(requirements.read_bytes()+macro_requirements.read_bytes()).hexdigest()
manifest=directory/'safetynet-runtime.json'
expected={'python':VERSION,'architecture':'amd64','sha256':CHECKSUM,'requirementsHash':requirements_hash}
if not manifest.exists() or json.loads(manifest.read_text())!=expected:
previous=json.loads(manifest.read_text()) if manifest.exists() else {}
if previous!=expected:
archive=directory.parent/f'python-{VERSION}-embed-amd64.zip'
if not archive.exists() or hashlib.sha256(archive.read_bytes()).hexdigest()!=CHECKSUM:
with urllib.request.urlopen(f'https://www.python.org/ftp/python/{VERSION}/python-{VERSION}-embed-amd64.zip',timeout=60) as response:
payload=response.read(20*1024*1024)
if hashlib.sha256(payload).hexdigest()!=CHECKSUM: raise SystemExit('Python runtime checksum mismatch')
archive.write_bytes(payload)
with zipfile.ZipFile(archive) as package:
for entry in package.infolist():
target=(directory/entry.filename).resolve()
if not target.is_relative_to(directory): raise SystemExit('Runtime archive path rejected')
if not entry.is_dir():
target.parent.mkdir(parents=True,exist_ok=True)
target.write_bytes(package.read(entry))
if not (directory/'python.exe').exists() or any(previous.get(k)!=expected[k] for k in ('python','architecture','sha256')):
with zipfile.ZipFile(archive) as package:
for entry in package.infolist():
target=(directory/entry.filename).resolve()
if not target.is_relative_to(directory): raise SystemExit('Runtime archive path rejected')
if not entry.is_dir():
target.parent.mkdir(parents=True,exist_ok=True)
target.write_bytes(package.read(entry))
(directory/'python313._pth').write_text('python313.zip\n.\nLib/site-packages\n../inspector\nimport site\n')
subprocess.run([sys.executable,'-m','pip','install','--target',str(directory/'Lib'/'site-packages'),
'--platform','win_amd64','--python-version','3.13','--implementation','cp','--abi','cp313',
'--only-binary=:all:','--require-hashes','--upgrade','-r',str(requirements)],check=True)
subprocess.run([sys.executable,'-m','pip','install','--target',str(directory/'Lib'/'site-packages'),
'--platform','win_amd64','--python-version','3.13','--implementation','cp','--abi','cp313',
'--only-binary=:all:','--no-deps','--require-hashes','--upgrade','-r',str(macro_requirements)],check=True)
manifest.write_text(json.dumps(expected,indent=2))
inspector=directory.parent/'inspector'
inspector.mkdir(exist_ok=True)
for name in ['engine.py','server.py','semantic.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py']:
for name in ['stage_timings.py','office_macros.py','engine.py','server.py','semantic.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py']:
shutil.copyfile(root/'apps'/'inspector'/name,inspector/name)
subprocess.run([str(directory/'python.exe'),'-c','import pypdf, PIL, defusedxml, yara, engine; assert engine.inspect_content(b"public", {"dlp":{"maxBytes":1024,"rules":[],"semantic":{"enabled":False}}})["decision"] == "allow"; assert yara.compile(source="rule test { condition: true }").match(data=b"test"); engine.local_ocr(); print("Bundled Windows inspector, YARA and offline ONNX OCR runtime verified.")'],check=True)
subprocess.run([str(directory/'python.exe'),str(root/'apps/inspector/provision_runtime_acl.py'),'--provision'],check=True)
+31 -11
View File
@@ -1,19 +1,36 @@
import { createServer } from 'node:http';
import { createRequire } from 'node:module';
import { mkdtempSync,rmSync,mkdirSync } from 'node:fs';
import { mkdtempSync,rmSync,mkdirSync,writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join,resolve } from 'node:path';
import { spawn } from 'node:child_process';
import { spawn,execFileSync } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { createApp } from '../apps/server/app.mjs';
import {startTelemetry} from '../apps/server/telemetry.mjs';
import { openStore,digest } from '../apps/server/store.mjs';
const require=createRequire(import.meta.url),electron=require('electron');
if(process.env.SAFETYNET_TEST_OTLP_ENDPOINT)process.env.OTEL_EXPORTER_OTLP_ENDPOINT=process.env.SAFETYNET_TEST_OTLP_ENDPOINT;
const smokeTelemetry=startTelemetry();
const temporary=mkdtempSync(join(tmpdir(),'safetynet-smoke-'));mkdirSync('artifacts',{recursive:true});
let posts=0,proxyRequests=0,secondaryRequests=0;
const evidenceKeyFile=join(temporary,'evidence.key');writeFileSync(evidenceKeyFile,randomBytes(32),{mode:0o600});
if(process.platform==='win32')execFileSync(join(process.env.SystemRoot,'System32/WindowsPowerShell/v1.0/powershell.exe'),['-NoProfile','-NonInteractive','-Command',"$ErrorActionPreference='Stop';$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User;$acl=New-Object System.Security.AccessControl.FileSecurity;$acl.SetOwner($sid);$acl.SetAccessRuleProtection($true,$false);$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($sid,'FullControl','Allow')));Set-Acl -LiteralPath $env:TEST_EVIDENCE_KEY -AclObject $acl"],{windowsHide:true,env:{SystemRoot:process.env.SystemRoot,TEST_EVIDENCE_KEY:evidenceKeyFile},stdio:'pipe'});
let posts=0,proxyRequests=0,secondaryRequests=0,headerRequests=0,fileUploadRequests=0,fileUploadExact=false;
const publicUploadBytes=Buffer.from("Public managed native upload fixture\n");
const publicUploadFile=join(temporary,"public-upload.txt"),secretUploadFile=join(temporary,"secret-upload.txt");
writeFileSync(publicUploadFile,publicUploadBytes);writeFileSync(secretUploadFile,"Secret Roadmap\n");
const visualLogin='<!doctype html><meta charset="utf-8"><title>Visual identity fixture</title><style>body{font-family:Arial;background:#fff;color:#202124;margin:0}form{margin:60px auto;padding:32px;border:2px solid #3267cb;border-radius:12px;width:340px;height:330px;box-sizing:content-box}h1{color:#3267cb;font-size:27px}input{display:block;margin:20px 0;padding:12px;border:1px solid #aaa;width:260px}button{background:#3267cb;color:white;padding:12px}</style><form method="post" action="/sink"><h1>SafetyNet Identity Lab</h1><h2>Sign in</h2><p>Use your laboratory account</p><input name="username" type="email" autocomplete="username"><input type="password" autocomplete="current-password"><button type="submit">Continue</button></form>';
const receiver=(req,res)=>{
res.setHeader('Access-Control-Allow-Origin','*');
if(req.url==='/count'){res.setHeader('content-type','application/json');res.end(JSON.stringify({posts,proxyRequests,secondaryRequests}));return;}
if(req.url==='/count'){res.setHeader('content-type','application/json');res.end(JSON.stringify({posts,proxyRequests,secondaryRequests,headerRequests,fileUploadRequests,fileUploadExact}));return;}
// Dedicated count-only endpoint: header values never enter fixture logs or artifacts.
if(req.url==='/file-upload-sink'){
fileUploadRequests++;const chunks=[];let size=0;
req.on('data',chunk=>{size+=chunk.length;if(size>1048576){req.destroy();return;}chunks.push(chunk);});
req.on('end',()=>{const body=Buffer.concat(chunks),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);fileUploadExact=start>=0&&end>=0&&body.subarray(start+4,end).equals(publicUploadBytes);}
body.fill(0);for(const chunk of chunks)chunk.fill(0);res.end('file received');});return;
}
if(req.url==='/header-sink'){headerRequests++;req.resume();res.end('header received');return;}
if(req.url==='/visual-login'){res.setHeader('content-type','text/html');res.end(visualLogin);return;}
if(req.url==='/download-safe'||req.url==='/download-blocked'){
res.setHeader('Content-Type','application/octet-stream');res.setHeader('Content-Disposition',`attachment; filename="${req.url==='/download-safe'?'safe.bin':'blocked.bin'}"`);
@@ -21,7 +38,7 @@ const receiver=(req,res)=>{
}
if(req.url.startsWith('http://proxy-check.invalid/'))proxyRequests++;
if(req.method==='POST'){posts++;req.resume();res.end('received');return;}
res.setHeader('content-type','text/html');res.end('<!doctype html><title>SafetyNet enforcement fixture</title><h1>Managed browsing is active</h1><p>This controlled receiver verifies that blocked payloads never arrive.</p><input id="public-input" type="text"><input id="private-input" type="password" value="never-export-this"><a id="safe-download" href="/download-safe">Download safe sample</a><a id="blocked-download" href="/download-blocked">Download blocked sample</a>');
res.setHeader('content-type','text/html');res.end('<!doctype html><title>SafetyNet enforcement fixture</title><h1>Managed browsing is active</h1><p>This controlled receiver verifies that blocked payloads never arrive.</p><input id="public-input" type="text"><select id="public-select"><option value="one">First</option><option value="two">Second</option><option value="locked" disabled>Locked</option></select><input id="public-checkbox" type="checkbox"><input id="private-input" type="password" value="never-export-this"><a id="safe-download" href="/download-safe">Download safe sample</a><a id="blocked-download" href="/download-blocked">Download blocked sample</a>');
};
const sink=createServer(receiver).listen(0,'127.0.0.1');await new Promise(r=>sink.on('listening',r));
const secondary=createServer((req,res)=>{secondaryRequests++;res.setHeader('Access-Control-Allow-Origin','*');res.end('secondary');}).listen(0,'127.0.0.2');await new Promise(r=>secondary.on('listening',r));
@@ -41,14 +58,17 @@ const inspectorPort=await new Promise((resolve,reject)=>{
});
inspectorProcess.stderr.on('data',()=>{});
});
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'}];store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'smoke-user');
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','read','click','type','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};store.put('policy',p);
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,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 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}});
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 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);
const exitCode=await new Promise(resolve=>{child.on('exit',code=>resolve(code??1));child.on('error',()=>resolve(1));});clearTimeout(timeout);
for(const server of [management,sink,secondary])server.closeAllConnections();
await Promise.all([new Promise(r=>management.close(r)),new Promise(r=>sink.close(r)),new Promise(r=>secondary.close(r))]);store.db.close();
await Promise.all([new Promise(r=>management.close(r)),new Promise(r=>sink.close(r)),new Promise(r=>secondary.close(r))]);if(exitCode===0){if(!evidenceWire.some(r=>r.route==='/api/device/channel/evidence')||evidenceWire.some(r=>r.containsMetadata))throw Error('Protected evidence wire proof failed');writeFileSync(resolve('artifacts/evidence-pq-wire-proof.json'),JSON.stringify({nativeBrowser:true,encryptedSubmissions:evidenceWire.filter(r=>r.route==='/api/device/channel/evidence').length,metadataAbsentFromWire:true,plaintextEndpointDisabled:true},null,2));}
store.db.close();
inspectorProcess.kill();
// Temporary paths originate from mkdtemp under the OS temp directory.
rmSync(temporary,{recursive:true,force:true});process.exit(exitCode);
rmSync(temporary,{recursive:true,force:true});await smokeTelemetry?.shutdown();process.exit(exitCode);
+20 -5
View File
@@ -31,7 +31,7 @@ if (!profile.startsWith(path.resolve(os.tmpdir()) + path.sep)) throw Error('Unsa
app.setPath('userData', profile);
const output = path.join(root, 'artifacts');
fs.mkdirSync(output, { recursive: true });
const methods = ['createTab','activateTab','closeTab','groupTab','mutateBookmarks','updateSettings','authorizeSetting','state', 'windowControls', 'setChromeLayout', 'shareURL', 'enroll', 'sso', 'navigate', 'back', 'forward', 'reload', 'ai', 'inspectFile', 'agentConnection', 'toggleAI', 'toggleVault', 'vaultStatus', 'vaultCreate', 'vaultUnlock', 'vaultLock', 'vaultList', 'vaultSave', 'vaultDelete', 'vaultFill', 'vaultGenerate'];
const methods = ['organizeLibrary','applyLibraryOrganization','createTab','activateTab','closeTab','groupTab','mutateBookmarks','updateSettings','authorizeSetting','state', 'windowControls', 'setChromeLayout', 'shareURL', 'enroll', 'sso', 'navigate', 'back', 'forward', 'reload', 'ai', 'inspectFile', 'agentConnection', 'toggleAI', 'toggleVault', 'vaultStatus', 'vaultCreate', 'vaultUnlock', 'vaultLock', 'vaultList', 'vaultSave', 'vaultDelete', 'vaultFill', 'vaultGenerate'];
const preload = path.join(profile, 'showcase-preload.cjs');
fs.writeFileSync(preload, `const {contextBridge,ipcRenderer}=require('electron'); const methods=${JSON.stringify(methods)}; const api={};for(const method of methods)api[method]=(...args)=>ipcRenderer.invoke('showcase:call',method,args);api.onState=callback=>{const listener=(_event,state)=>callback(state);ipcRenderer.on('showcase:state',listener);return()=>ipcRenderer.removeListener('showcase:state',listener);};contextBridge.exposeInMainWorld('safetynet',api);`);
const fixtures = [
@@ -68,6 +68,8 @@ ipcMain.handle('showcase:call', (event, method, args) => {
else if(method==='createTab'){const tab={id:`showcase-native-${++nextTab}`,url:'',title:'New Tab',group:args[0]?.group||'',startPage:true,color:'#b8c7dd',canGoBack:false,canGoForward:false};state.tabs.push(tab);state.activeTabId=tab.id;data=publish(event.sender);}
else if(method==='activateTab'){assert.ok(state.tabs.some(tab=>tab.id===args[0]));state.activeTabId=args[0];data=publish(event.sender);}
else if(method==='closeTab'){const index=state.tabs.findIndex(tab=>tab.id===args[0]);assert.ok(index>=0);state.tabs.splice(index,1);if(!state.tabs.length){state.tabs.push({id:`showcase-native-${++nextTab}`,url:'',title:'New Tab',group:'',startPage:true,color:'#b8c7dd',canGoBack:false,canGoForward:false});}if(state.activeTabId===args[0])state.activeTabId=state.tabs[Math.min(index,state.tabs.length-1)].id;data=publish(event.sender);}
else if(method==='organizeLibrary'){assert.ok(state.libraryAI?.suggestionsEnabled);data={proposalId:'fixture-review-id',plan:{bookmarks:[{id:'showcase-1',folder:'Engineering'}],tabs:[]}};}
else if(method==='applyLibraryOrganization'){assert.equal(args[0].proposalId,'fixture-review-id');assert.equal(args[0].confirm,true);state.bookmarks.personal.items.find(item=>item.id==='showcase-1').folder='Engineering';state.bookmarks.personal.revision++;data={completed:true};}
else if(method==='groupTab'){assert.ok(state.settings.visible&&!state.settings.hiddenSections.includes('library')&&!state.settings.lockedSections.includes('library'));const tab=state.tabs.find(tab=>tab.id===args[0].id);assert.ok(tab);tab.group=args[0].group;data=publish(event.sender);}
else if(method==='mutateBookmarks'){const input=args[0];if(!state.settings.visible||state.settings.hiddenSections.includes('library')||state.settings.lockedSections.includes('library')||input.expectedRevision!==state.bookmarks.personal.revision)return {ok:false,error:'Managed bookmark refused'};assert.equal(input.action,'add');state.bookmarks.personal={revision:state.bookmarks.personal.revision+1,items:[...state.bookmarks.personal.items,input.bookmark]};data=state;event.sender.send('showcase:state',state);}
else if(method==='updateSettings'){if(!state.settings.available||!state.settings.visible||state.settings.hiddenSections.includes('appearance')||state.settings.lockedSections.includes('appearance'))return{ok:false,error:'Managed setting refused'};state.settings={...state.settings,appearance:{...args[0]}};data=state.settings;}
@@ -113,7 +115,7 @@ async function accent(value) {
async function shot(name) {
console.log('Showcase capture: ' + name);
await execute('document.fonts.ready'); await delay(90);
const measurements = await execute(`(()=>{const rect=id=>{const element=document.getElementById(id),r=element.getBoundingClientRect();return{hidden:element.hidden,x:r.x,y:r.y,width:r.width,height:r.height}};const brokenImages=[...document.images].filter(image=>!image.complete||!image.naturalWidth).map(image=>image.getAttribute('src'));return{viewport:{width:innerWidth,height:innerHeight,zoom:devicePixelRatio},theme:document.documentElement.dataset.theme,effectiveTheme:document.documentElement.dataset.effectiveTheme,accent:document.documentElement.dataset.accent,tabMode:document.documentElement.dataset.tabMode,chrome:rect('chrome'),toolbar:document.querySelector('.toolbar').getBoundingClientRect().height,library:rect('library'),ai:rect('ai'),vault:rect('vault'),startPage:rect('start-page'),overview:rect('overview'),tools:rect('tools-menu'),scrollWidth:document.documentElement.scrollWidth,brokenImages,error:document.getElementById('error').textContent,passwordFieldsEmpty:[...document.querySelectorAll('input[type=password]')].every(input=>!input.value)}})()`);
const measurements = await execute(`(()=>{const rect=id=>{const element=document.getElementById(id),r=element.getBoundingClientRect();return{hidden:element.hidden,x:r.x,y:r.y,width:r.width,height:r.height}};const brokenImages=[...document.images].filter(image=>image.hasAttribute('src')&&(!image.complete||!image.naturalWidth)).map(image=>image.getAttribute('src'));return{viewport:{width:innerWidth,height:innerHeight,zoom:devicePixelRatio},theme:document.documentElement.dataset.theme,effectiveTheme:document.documentElement.dataset.effectiveTheme,accent:document.documentElement.dataset.accent,tabMode:document.documentElement.dataset.tabMode,chrome:rect('chrome'),toolbar:document.querySelector('.toolbar').getBoundingClientRect().height,library:rect('library'),ai:rect('ai'),vault:rect('vault'),startPage:rect('start-page'),overview:rect('overview'),tools:rect('tools-menu'),scrollWidth:document.documentElement.scrollWidth,brokenImages,error:document.getElementById('error').textContent,passwordFieldsEmpty:[...document.querySelectorAll('input[type=password]')].every(input=>!input.value)}})()`);
assert.equal(measurements.toolbar, 64); assert.ok([64, 104].includes(measurements.chrome.height));
assert.equal(measurements.scrollWidth, measurements.viewport.width); assert.deepEqual(measurements.brokenImages, []); assert.equal(measurements.passwordFieldsEmpty, true); assert.equal(measurements.error, '');
if (!measurements.library.hidden) assert.equal(measurements.library.width, 240);
@@ -153,9 +155,22 @@ app.whenReady().then(async () => {
await waitFor(`document.getElementById('bookmark-editor').open`,false);
assert.equal(state.bookmarks.managed.length+state.bookmarks.personal.items.length,7);
assert.equal(await execute(`JSON.parse(localStorage.getItem('sbrowser.bookmarks')).length`), 6,'User bookmarks must not be mutated through renderer storage');
await click('new-tab'); await click('sidebar-toggle');
await click('new-tab'); assert.equal(await execute(`document.getElementById('library').hidden`),true,'Sidebar starts collapsed');
assert.equal(await execute(`document.querySelectorAll('#favorites-grid .favorite-card').length`), 7);
await click('sidebar-toggle');await execute(`document.getElementById('group-name').value='Work'`);await click('group-add');assert.equal(state.tabs.find(t=>t.id===state.activeTabId).group,'Work');await execute(`document.getElementById('group-name').value='Research';document.querySelector('[aria-label="Rename group Work"]').click()`);await waitFor(`document.querySelector('[aria-label="Ungroup tabs in Research"]')!==null`,true);assert.equal(state.tabs.find(t=>t.id===state.activeTabId).group,'Research');await execute(`document.querySelector('[aria-label="Ungroup tabs in Research"]').click()`);await waitFor(`document.querySelector('[aria-label="Ungroup tabs in Research"]')===null`,true);assert.equal(state.tabs.find(t=>t.id===state.activeTabId).group,'');await click('sidebar-toggle');
const aligned=await execute(`(()=>{const cards=[...document.querySelectorAll('#favorites-grid .favorite-card')];const first=cards[0].getBoundingClientRect().top;return cards.filter(c=>c.getBoundingClientRect().top===first).map(c=>c.querySelector('.favorite-icon').getBoundingClientRect().top)})()`);assert.ok(aligned.length>1);assert.ok(aligned.every(top=>Math.abs(top-aligned[0])<1),'Managed and personal favorites share icon baseline');
await click('favorites-manage');assert.equal(await execute(`document.getElementById('bookmark-manager').open`),true);assert.equal(await execute(`document.querySelectorAll('#manager-list .manager-row').length`),7);assert.equal(await execute(`document.querySelector('#manager-list [data-bookmark-id="showcase-company"] .manager-row-actions')===null`),true);assert.equal(await execute(`document.getElementById('manager-ai').disabled`),true);await execute(`document.getElementById('bookmark-search').value='github';document.getElementById('bookmark-search').dispatchEvent(new Event('input'))`);assert.equal(await execute(`document.querySelectorAll('#manager-list .manager-row').length`),1);await shot('sbrowser-bookmark-manager');state.libraryAI={suggestionsEnabled:true,autoApplyEnabled:false,busy:false};window.webContents.send('showcase:state',snapshot());await delay(100);await click('manager-ai');assert.equal(await execute(`document.getElementById('manager-proposal').hidden`),false);assert.match(await execute(`document.getElementById('manager-diff').textContent`),/GitHub.*Engineering/);assert.equal(state.bookmarks.personal.items.find(item=>item.id==='showcase-1').folder,'Work','Preview has no side effects');await click('manager-apply');assert.equal(state.bookmarks.personal.items.find(item=>item.id==='showcase-1').folder,'Engineering');await click('bookmark-manager-close');
await shot('sbrowser-light');
state.brand={name:'Northstar Company',color:'#166b68',logoDataUrl:'data:image/png;base64,'+fs.readFileSync(path.join(root,'docs/brand/safetynet-icon.png')).toString('base64')};
state.startPage={title:'Welcome to Northstar.',subtitle:'Your company workspace, news and favorite tools.',newsEnabled:true,news:[{id:'news-1',title:'Company update: our new intranet is here',url:'https://intranet.example.test/news',publishedAt:'2026-10-13T12:00:00Z'},{id:'news-2',title:'Security week: join the local AI workshop',url:'https://intranet.example.test/events'}]};
window.webContents.send('showcase:state',snapshot());await delay(150);
assert.equal(await execute(`document.getElementById('start-brand-name').textContent`),'Northstar Company');
assert.equal(await execute(`document.querySelectorAll('#company-news-items .company-news-card').length`),2);
assert.equal(await execute(`document.getElementById('start-company-logo').hidden`),false);
await shot('sbrowser-company-branding');
state.startPage={newsEnabled:false};state.brand={name:'SafetyNet'};window.webContents.send('showcase:state',snapshot());await delay(100);
assert.equal(await execute(`document.getElementById('company-news').hidden`),true);assert.equal(await execute(`document.getElementById('start-company-logo').hasAttribute('src')`),false);
await appearance('dark', 'compact'); await shot('sbrowser-dark');
await click('tab-overview'); assert.equal(await execute(`document.querySelectorAll('#overview-grid .overview-card').length`), 2); await shot('sbrowser-overview');
await click('overview-close'); await appearance('light', 'separate'); await click('tools-toggle'); await shot('sbrowser-settings');
@@ -163,7 +178,7 @@ app.whenReady().then(async () => {
await appearance('light', 'separate'); await accent('blue');
await click('passwords'); await shot('sbrowser-vault'); await click('vault-close');
await appearance('light', 'compact'); window.setContentSize(800, 650); await delay(150); await shot('sbrowser-compact');
await click('tools-toggle'); await click('ai-toggle'); await shot('sbrowser-compact-panels'); await click('ai-close');
await click('tools-toggle'); await click('ai-toggle'); await shot('sbrowser-compact-panels'); await click('ai-close');window.setContentSize(480,740);await delay(120);await shot('sbrowser-narrow');window.setContentSize(800,650);
await execute(`document.getElementById('address').value='https://example.test/slow';document.getElementById('address-form').requestSubmit();`);
await click('new-tab'); await delay(300);
assert.equal(await execute(`document.querySelector('.browser-tab.active .tab-label').textContent`), 'New Tab');
@@ -193,7 +208,7 @@ app.whenReady().then(async () => {
assert.equal(records.filter(record => record.method === 'windowControls').length, 3);
fs.writeFileSync(path.join(output, 'sbrowser-ui-layout.json'), JSON.stringify({ fixtureOnly: true, managedSettingsProof:{liveLocks:true,hiddenSections:true,globalHidden:true,localStorageCannotOverride:true,reloadPreservesPolicy:true,directUpdateRejected:true,globalHiddenPreservesApprovedFeatures:true},screenshots, records, consoleErrors }, null, 2));
assert.deepEqual(consoleErrors, []);
finished = true; clearTimeout(watchdog); console.log('UI showcase passed: eight screenshots, live system themes, persisted accents, real DOM interactions and delayed-navigation regression.');
finished = true; clearTimeout(watchdog); console.log('UI showcase passed: ten screenshots, live system themes, persisted accents, real DOM interactions and delayed-navigation regression.');
if(window.webContents.debugger.isAttached())window.webContents.debugger.detach();
window.destroy(); app.exit(0);
}).catch(error => { clearTimeout(watchdog); console.error(error.message); if (!finished) app.exit(1); });
+38 -6
View File
@@ -10,12 +10,17 @@ const {startInspector}=require('../apps/browser/inspection.cjs');
const root=path.resolve('dist/win-unpacked');
if(process.platform!=='win32')throw Error('Windows package verification requires Windows');
const {extractFile}=require('@electron/asar');
for(const file of ['apps/browser/policy-assignment.cjs','packages/core/policy-groups.mjs','apps/browser/tab-registry.cjs','apps/browser/agent-adapters.cjs','apps/browser/agent-control.cjs','apps/browser/password-fill.cjs','apps/browser/diagnostics.cjs','packages/core/diagnostics.mjs','packages/core/security.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged device/session policy source is stale: ${file}`);
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize('packages/core/agent-audit.cjs')),await fs.readFile('packages/core/agent-audit.cjs'),'Packaged structured agent audit schema is stale');
for(const file of ['apps/browser/upload-lease.cjs','apps/browser/file-upload-gate.cjs','apps/browser/request-source.cjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged upload binding source is stale: ${file}`);
assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector/upload_lease.py')),await fs.readFile('apps/inspector/upload_lease.py'),'Packaged Windows upload lease helper is stale');
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize('apps/browser/header-inspection.cjs')),await fs.readFile('apps/browser/header-inspection.cjs'),'Packaged outgoing header DLP source is stale');
for(const file of ['apps/browser/evidence-capture.cjs','apps/browser/evidence-delivery.cjs','packages/core/request-transfer-facts.cjs','packages/core/transfer-facts.cjs','packages/core/inspection-stages.cjs','apps/browser/history-navigation.cjs','apps/browser/agent-transport-client.cjs','apps/browser/agent-client.cjs','apps/browser/library-organization.cjs','packages/core/library.mjs','packages/core/branding.mjs','packages/core/file-transfer.cjs','packages/core/file-transfer.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged new security/branding source is stale: ${file}`);
for(const file of ['apps/browser/agent-grants.cjs','apps/browser/policy-assignment.cjs','packages/core/policy-groups.mjs','apps/browser/tab-registry.cjs','apps/browser/agent-adapters.cjs','apps/browser/agent-control.cjs','apps/browser/password-fill.cjs','apps/browser/diagnostics.cjs','packages/core/diagnostics.mjs','packages/core/security.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged device/session policy source is stale: ${file}`);
for(const file of ['apps/browser/main.cjs','apps/browser/preload.cjs','apps/browser/site-preload.cjs','apps/browser/clipboard-deny.cjs','apps/browser/clipboard-guard.cjs','apps/browser/clipboard-dom.cjs','apps/browser/page-clipboard-guard.cjs','apps/browser/startup-security.cjs','apps/browser/visual-capture.cjs','apps/browser/renderer.js','apps/browser/index.html','apps/browser/style.css','packages/core/clipboard.mjs']){
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged source is stale: ${file}`);
}
for(const file of ['apps/browser/inspection.cjs','apps/browser/managed-settings.cjs','apps/browser/management-channel.cjs','apps/browser/enterprise-inspection.cjs','apps/browser/icap-client.cjs','apps/browser/network-posture.cjs','apps/browser/download-gate.cjs','packages/core/enterprise.mjs','packages/core/semantic.mjs','packages/core/managed-settings.mjs','packages/core/policy-channel.cjs','packages/core/policy.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged source is stale: ${file}`);
for(const file of ['semantic.py','engine.py','server.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py'])assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector',file)),await fs.readFile(path.join('apps/inspector',file)),`Packaged inspector is stale: ${file}`);
for(const file of ['stage_timings.py','office_macros.py','semantic.py','engine.py','server.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py'])assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector',file)),await fs.readFile(path.join('apps/inspector',file)),`Packaged inspector is stale: ${file}`);
const temporary=await fs.mkdtemp(path.join(os.tmpdir(),'safetynet-package-'));
for(const file of ['apps/browser/bookmarks.cjs','packages/core/bookmarks.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged bookmark source is stale: ${file}`);
function run(executable,args,input,timeoutMs=15000){return new Promise((resolve,reject)=>{
@@ -26,29 +31,56 @@ function run(executable,args,input,timeoutMs=15000){return new Promise((resolve,
});}
let service;
try{
const uploadFile=path.join(temporary,'packaged-upload.txt'),uploadBytes=Buffer.from('Synthetic packaged upload lease verification');
await fs.writeFile(uploadFile,uploadBytes);
const uploadLease=await require('../apps/browser/upload-lease.cjs').acquireUploadLease({paths:[uploadFile],maxBytes:4096,
pythonPath:path.join(root,'resources/python/python.exe'),helperPath:path.join(root,'resources/inspector/upload_lease.py')});
try{
assert.deepEqual(uploadLease.files[0].bytes,uploadBytes,'Packaged upload helper snapshot differs');
await assert.rejects(fs.writeFile(uploadFile,'Unapproved replacement'),'Packaged upload handles did not prevent overwrite');
await assert.rejects(fs.rename(uploadFile,path.join(temporary,'moved-upload.txt')),'Packaged upload handles did not prevent rename');
}finally{uploadLease.files[0].bytes.fill(0);await uploadLease.release();}
await fs.writeFile(uploadFile,'Released after terminal-equivalent verifier cleanup');
const inspectorBytecode=path.join(root,'resources/inspector/__pycache__');
const bytecodeExists=()=>fs.stat(inspectorBytecode).then(()=>true,error=>{if(error.code==='ENOENT')return false;throw error;});
assert.equal(await bytecodeExists(),false,'Package must start with no inspector bytecode cache');
const packagedInspector=startInspector(path.join(root,'resources'),true);
const packagedStages=[];
const packagedInspector=startInspector(path.join(root,'resources'),true,{onStages:batch=>packagedStages.push(batch)});
assert.ok(packagedInspector,'Packaged inspector runtime is missing');
try{
const verdict=await packagedInspector.inspect(Buffer.from('Public package verification text'),{dlp:{maxBytes:1048576,rules:[],semantic:{enabled:false}}});
const verdict=await packagedInspector.inspect(Buffer.from('Public package verification text'),{diagnostics:{enabled:true,mode:'debug'},dlp:{maxBytes:1048576,rules:[],semantic:{enabled:false}}});
assert.equal(verdict.decision,'allow','Packaged contained inspector failed');
assert.equal(verdict.coverage.complete,true);
const macro=Buffer.from(await fs.readFile('tests/fixtures/office/benign-vba-project.base64.txt','utf8'),'base64');
for(const blocked of [false,true]){
const result=await packagedInspector.scanDownload(macro,{diagnostics:{enabled:true,mode:'debug'},security:{yara:{enabled:true,scanOfficeMacros:true,rules:[{id:'packaged-macro-marker',source:blocked?'rule source_marker { strings: $m = "Hello from Python!" condition: $m }':'rule clean { condition: false }'}]}}});
assert.equal(result.decision,blocked?'block':'allow','Packaged VBA source scanning failed');
assert.equal(result.officeMacros?.modules,5,'Packaged VBA extraction missing');
}
const stages=packagedStages.flatMap(batch=>batch?.records?.map(row=>row.stage)||[]);for(const stage of ['inspect','parse','yara','macros'])assert.ok(stages.includes(stage),`Packaged internal stage missing: ${stage}`);
}finally{packagedInspector.stop();}
const semanticPath=path.join(root,'resources/semantic');
const actual=await run(path.join(root,'resources/python/python.exe'),['-c','import sys; sys.path.insert(0,sys.argv[1]); from semantic import NativeSemanticModel; m=NativeSemanticModel(sys.argv[2],sys.argv[3]); vectors=m.text_vectors("Public information about weather forecasting."); assert len(vectors)>=1 and len(vectors[0])==768; m.close(); print("Offline packaged semantic CPU inference verified.")',path.join(root,'resources/inspector'),path.join(semanticPath,'models'),path.join(semanticPath,'searchi-gemma-worker.exe')],undefined,45000);
assert.equal(actual.code,0,'Packaged offline semantic inference failed');assert.ok(actual.output.includes('Offline packaged semantic CPU inference verified.'));
service=await startAgentControl({authorize:async()=>({allowed:true,role:'agent',scopes:['sessions:list']}),audit:async()=>{},actions:{sessions:async()=>[{id:'managed',url:'https://example.test/'}]}});
const encrypted=await run(path.join(root,'resources/python/python.exe'),[path.join(root,'resources/inspector/dpapi.py'),'protect'],JSON.stringify({address:service.address,token:service.token}));
const encrypted=await run(path.join(root,'resources/python/python.exe'),[path.join(root,'resources/inspector/dpapi.py'),'protect'],JSON.stringify({address:service.address,token:service.token,edPublic:service.edPublic,pqPublic:service.pqPublic,protocol:service.protocol}));
assert.equal(encrypted.code,0,'Packaged DPAPI failed');
const connection=path.join(temporary,'connection.json'),request=path.join(temporary,'request.json');
await fs.writeFile(connection,JSON.stringify({format:'windows-dpapi-v1',protectedConnection:encrypted.output}));
await fs.writeFile(request,JSON.stringify({action:'sessions'}));
const result=await run(path.join(root,'SafetyNet.exe'),[`--agent-connection=${connection}`,`--agent-action=${request}`]);
assert.equal(result.code,0,'Packaged agent helper failed');assert.equal(JSON.parse(result.output).result[0].id,'managed');assert.equal(result.output.includes(service.token),false);
await service.close();
const expiresAt=new Date(Date.now()+60000).toISOString(),individualEvents=[];
service=await startAgentControl({principalMode:'individual',principals:[{id:'packaged-reader',generation:1,enabled:true,expiresAt,scopes:['sessions'],allowedHosts:[]}],authorize:async({principalId,generation})=>({allowed:principalId==='packaged-reader'&&generation===1,role:'agent',scopes:['sessions:list']}),audit:async event=>individualEvents.push(event),actions:{sessions:async()=>[{id:'individual-managed'}]}});
const individual=service.issuePrincipal('packaged-reader',1,expiresAt);
const protectedIndividual=await run(path.join(root,'resources/python/python.exe'),[path.join(root,'resources/inspector/dpapi.py'),'protect'],JSON.stringify(individual));
assert.equal(protectedIndividual.code,0,'Packaged individual DPAPI failed');
const individualConnection=path.join(temporary,'individual.json');await fs.writeFile(individualConnection,JSON.stringify({format:'windows-dpapi-v1',protectedConnection:protectedIndividual.output}));
const individualResult=await run(path.join(root,'SafetyNet.exe'),[`--agent-connection=${individualConnection}`,`--agent-action=${request}`]);
assert.equal(individualResult.code,0,'Packaged individual agent helper failed');assert.equal(JSON.parse(individualResult.output).result[0].id,'individual-managed');assert.equal(individualResult.output.includes(individual.token),false);assert.ok(individualEvents.some(event=>event.principalId==='packaged-reader'&&event.generation===1&&event.outcome==='completed'));
assert.equal(await bytecodeExists(),false,'Cold packaged startup must not create inspector bytecode files');
console.log('Packaged security sources, Windows-contained inspector, offline EmbeddingGemma 2 CPU inference, Windows agent helper and bundled DPAPI runtime passed.');
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();
if(!temporary.startsWith(path.join(os.tmpdir(),'safetynet-package-')))throw Error('Unexpected temporary directory');