first commit
This commit is contained in:
commit
ff482ebe23
251 files changed
+19126
No files matched your search
@@ -0,0 +1,8 @@
|
||||
// Trusted local-process helper. Capability decryption stays in Electron's OS key store.
|
||||
const {app}=require('electron');
|
||||
const {configureClient,runClient}=require('../apps/browser/agent-client.cjs');
|
||||
const args=process.argv.slice(2);
|
||||
const value=name=>{const i=args.indexOf(name);return i>=0?args[i+1]:null;};
|
||||
const connectionFile=value('--connection'),requestFile=value('--request');
|
||||
try{configureClient(connectionFile);app.whenReady().then(()=>runClient(connectionFile,requestFile)).catch(error=>{console.error(`Local agent action failed (${error.clientStage||'startup'}). Check policy, session and encrypted pairing file.`);app.exit(1);});}
|
||||
catch{console.error('Provide --connection absolute-file and --request JSON-file');app.exit(1);}
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Actual Windows AppContainer OS boundary and local model proof, synthetic fixtures."""
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import queue
|
||||
import secrets
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
import urllib.request
|
||||
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0,str(ROOT/'apps/inspector'))
|
||||
import test_windows_appcontainer
|
||||
suite=unittest.defaultTestLoader.loadTestsFromModule(test_windows_appcontainer)
|
||||
result=unittest.TextTestRunner(verbosity=2).run(suite)
|
||||
if not result.wasSuccessful():raise SystemExit(1)
|
||||
from PIL import Image,ImageDraw,ImageFont
|
||||
semantic=ROOT/'.runtime/windows/semantic'
|
||||
token=secrets.token_urlsafe(32)
|
||||
env={key:value for key,value in os.environ.items() if key.upper() in {'SYSTEMROOT','WINDIR','USERPROFILE','LOCALAPPDATA','APPDATA','TEMP','TMP'}}
|
||||
env.update({'INSPECTOR_TOKEN':token,'INSPECTOR_PORT':'0','EMBEDDING_MODEL_DIR':str(semantic/'models'),'EMBEDDING_WORKER_PATH':str(semantic/'searchi-gemma-worker.exe')})
|
||||
child=subprocess.Popen([sys.executable,str(ROOT/'apps/inspector/server.py')],stdout=subprocess.PIPE,stderr=subprocess.PIPE,env=env,creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
lines=queue.Queue();threading.Thread(target=lambda:lines.put(child.stdout.readline()),daemon=True).start()
|
||||
proof={'nativeYaraUploadDownload':True,'osTests':{'passed':result.testsRun,'failed':len(result.failures)+len(result.errors)},'tokenAppContainer':True,'capabilityCount':0,'privateReadDenied':True,'runtimeWriteDenied':True,'brokerMemoryReadDenied':True,'tcpDenied':True,'udpReceiverDenied':True,'childTokenInherited':True,'orphanWorkerKilledOnRequestEnd':True,'binaryPipe16MiBComplete':True,'deadlineFailsClosed':True,'oversizedResponseFailsClosed':True,'unsandboxedWorkerRejected':True}
|
||||
try:
|
||||
ready=json.loads(lines.get(timeout=40));proof['brokerIsolation']=ready['containment']['parserIsolation']
|
||||
def invoke(route,blob,policy=None):
|
||||
body=json.dumps({'data':base64.b64encode(blob).decode(),'policy':policy or {}}).encode()
|
||||
request=urllib.request.Request('http://127.0.0.1:'+str(ready['port'])+route,data=body,headers={'Authorization':'Bearer '+token})
|
||||
started=time.monotonic()
|
||||
with urllib.request.urlopen(request,timeout=30) as response: answer=json.load(response)
|
||||
return answer,round(time.monotonic()-started,3)
|
||||
image=Image.new('RGB',(1000,200),'white');draw=ImageDraw.Draw(image)
|
||||
draw.text((20,30),'Internal secret invoice 482190',fill='black',font=ImageFont.truetype('C:/Windows/Fonts/arial.ttf',40))
|
||||
blob=io.BytesIO();image.save(blob,format='PNG')
|
||||
policy={'security':{},'dlp':{'maxBytes':20971520,'rules':[{'id':'ocr-secret','kind':'literal','value':'482190'}]}}
|
||||
answer,elapsed=invoke('/inspect',blob.getvalue(),policy)
|
||||
if answer['decision']!='block' or answer['ruleId']!='ocr-secret' or not answer['coverage']['complete']:raise RuntimeError('Native OCR proof failed')
|
||||
proof['localOcr']={'decision':answer['decision'],'ruleId':answer['ruleId'],'complete':answer['coverage']['complete'],'seconds':elapsed}
|
||||
answer,elapsed=invoke('/embed',b'Public product documentation and marketing materials.')
|
||||
if answer['dimensions']!=768 or answer['model']!='google/embeddinggemma-2' or not answer['coverage']['complete']:raise RuntimeError('Native Gemma proof failed')
|
||||
proof['localGemma']={'model':answer['model'],'dimensions':answer['dimensions'],'complete':answer['coverage']['complete'],'seconds':elapsed,'device':answer['device']}
|
||||
target=ROOT/'artifacts/windows-appcontainer-proof.json';target.write_text(json.dumps(proof,indent=2)+'\n',encoding='utf-8')
|
||||
print(json.dumps({'passed':True,'proof':str(target)}))
|
||||
finally:
|
||||
child.kill();child.wait(timeout=5);child.stdout.close();child.stderr.close()
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1');
|
||||
let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000);
|
||||
async function evaluate(source){return window.webContents.executeJavaScript(source,true);}
|
||||
async function waitFor(source){const until=Date.now()+12000;while(Date.now()<until){if(await evaluate(source))return;await new Promise(r=>setTimeout(r,75));}throw Error('Administrator UI state did not arrive');}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);
|
||||
await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`);
|
||||
assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'});
|
||||
const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff');
|
||||
await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);
|
||||
await evaluate(`document.getElementById('managed-bookmark-add').click();const fields=document.querySelectorAll('#managed-bookmark-rows input');for(const [index,value]of ['Company intranet','https://intranet.synthetic.example/','Company'].entries()){fields[index].value=value;fields[index].dispatchEvent(new Event('input'));}document.getElementById('bookmarks-admin-read').checked=true;document.getElementById('publish').click()`);
|
||||
await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`);
|
||||
assert.deepEqual(await evaluate(`Array.from(document.querySelectorAll('#managed-bookmark-rows input'),i=>i.value)`),['Company intranet','https://intranet.synthetic.example/','Company']);
|
||||
await evaluate(`document.getElementById('bookmark-device-load').click()`);await waitFor(`!document.getElementById('bookmark-device-editor').hidden`);
|
||||
await evaluate(`document.getElementById('personal-bookmark-add').click();const personalFields=document.querySelectorAll('#personal-bookmark-rows input');for(const [index,value]of ['Remote personal reference','https://reference.synthetic.example/','Research'].entries()){personalFields[index].value=value;personalFields[index].dispatchEvent(new Event('input'));}document.getElementById('bookmark-device-save').click()`);
|
||||
await waitFor(`document.getElementById('bookmark-device-status').textContent.includes('Saved revision 1')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');
|
||||
await evaluate(`document.getElementById('bookmark-device-refresh').click()`);await waitFor(`document.getElementById('bookmark-device-status').textContent.includes('Loaded device revision 1')`);assert.equal(await evaluate(`document.querySelector('#personal-bookmark-rows input').value`),'Remote personal reference');
|
||||
await evaluate(`document.getElementById('bookmarks-admin-read').checked=false;document.getElementById('publish').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('published')`);
|
||||
await evaluate(`document.getElementById('bookmark-device-load').click()`);await waitFor(`document.getElementById('status').className==='error'`);assert.equal(await evaluate(`document.getElementById('bookmark-device-editor').hidden`),true);assert.equal(await evaluate(`globalThis.__proofErrors`),0);
|
||||
await evaluate(`document.getElementById('bookmarks').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/bookmarks-admin.png'),(await window.webContents.capturePage()).toPNG());
|
||||
console.log('BOOKMARK_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('BOOKMARK_ADMIN_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-bookmarks-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false};
|
||||
store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('synthetic-bookmark-device','Synthetic bookmark device','win32','synthetic-user','synthetic-unused',new Date().toISOString());
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/bookmarks-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('BOOKMARK_ADMIN_PROOF_OK'))console.log('Bookmark administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('BOOKMARK_ADMIN_PROOF_'))console.error(text.trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Bookmark administrator interface proof failed');
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.deepEqual(policy.browser.bookmarks.managed.map(({title,url,folder})=>({title,url,folder})),[{title:'Company intranet',url:'https://intranet.synthetic.example/',folder:'Company'}]);assert.equal(policy.browser.bookmarks.adminReadUserBookmarks,false);assert.equal(store.personalBookmarks('synthetic-bookmark-device').revision,1);assert.equal(store.personalBookmarks('synthetic-bookmark-device').items[0].title,'Remote personal reference');
|
||||
const report={actualAdministratorUi:true,managedTableRoundtrip:true,explicitPublication:true,deviceReadEdit:true,revisionCompared:true,deviceEditGlobalPolicyUnchanged:true,privacyRevoked:true,nodeUnavailable:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/bookmarks-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-bookmarks-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const {app,BrowserWindow}=require('electron');
|
||||
const assert=require('node:assert/strict');
|
||||
app.setPath('userData',process.env.SAFETYNET_BOOKMARK_PROFILE);app.setPath('sessionData',process.env.SAFETYNET_BOOKMARK_PROFILE);app.disableHardwareAcceleration();process.env.SAFETYNET_SMOKE_USERDATA=process.env.SAFETYNET_BOOKMARK_PROFILE;
|
||||
require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms)),deadline=setTimeout(()=>app.exit(1),110000);
|
||||
app.whenReady().then(async()=>{try{
|
||||
let win;for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}assert.ok(win);
|
||||
const call=(name,input)=>win.webContents.executeJavaScript(`window.safetynet[${JSON.stringify(name)}](${JSON.stringify(input)??''})`),state=async()=>(await call('state')).data;
|
||||
const result=await call('enroll',{server:process.env.SAFETYNET_BOOKMARK_SERVER,code:process.env.SAFETYNET_BOOKMARK_CODE});assert.equal(result.ok,true,result.error);assert.equal(result.data.ready,true);
|
||||
const id=result.data.deviceId,headers={authorization:`Bearer ${process.env.SAFETYNET_BOOKMARK_ADMIN}`,'content-type':'application/json'},base=process.env.SAFETYNET_BOOKMARK_SERVER;
|
||||
async function api(route,body,method=body?'POST':'GET'){const r=await fetch(base+route,{method,headers,body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};}
|
||||
async function until(fn){for(let i=0;i<200;i++){if(await fn())return;await wait(100);}throw Error('Bookmark refresh timed out');}
|
||||
async function mutate(action,bookmark){for(let i=0;i<20;i++){const s=await state();const r=await call('mutateBookmarks',{action,bookmark,expectedRevision:s.bookmarks.personal.revision,policyVersion:s.policyVersion});if(r.ok)return r;if(!/Management update in progress/.test(r.error||''))return r;await wait(100);}throw Error('Bookmark mutation unavailable');}
|
||||
const initial=await state();assert.equal(initial.bookmarks.managed[0].id,'company-intranet');assert.equal(initial.bookmarks.personal.items.length,0);
|
||||
assert.equal((await mutate('update',{...initial.bookmarks.managed[0],title:'User tamper'})).ok,false);
|
||||
const personal={id:'personal-fixture',title:'Personal work link',url:process.env.SAFETYNET_BOOKMARK_ORIGIN+'personal',folder:'Work'};const added=await mutate('add',personal);assert.equal(added.ok,true,added.error);assert.equal(added.data.policyVersion,initial.policyVersion);assert.equal(added.data.bookmarks.personal.revision,1);
|
||||
let adminRead=await api(`/api/admin/devices/${id}/bookmarks`);assert.equal(adminRead.status,200);assert.equal(adminRead.body.items[0].title,personal.title);
|
||||
const otherCode=(await api('/api/admin/enrollments',{user:'public-other-user'})).body.code;
|
||||
const other=(await api('/api/device/enroll',{code:otherCode,name:'Public other device',platform:'win32'})).body;
|
||||
const edited={...personal,title:'Administrator edited work link',url:process.env.SAFETYNET_BOOKMARK_ORIGIN+'admin-edited'};const changed=await api(`/api/admin/devices/${id}/bookmarks`,{expectedRevision:adminRead.body.revision,policyVersion:initial.policyVersion,items:[edited]},'PUT');assert.equal(changed.status,200);
|
||||
await until(async()=>{const s=await state();return s.ready&&s.bookmarks.personal.revision===2;});assert.equal((await state()).policyVersion,initial.policyVersion);assert.equal((await state()).bookmarks.personal.items[0].title,edited.title);
|
||||
const otherBookmarks=await api(`/api/admin/devices/${other.id}/bookmarks`);assert.equal(otherBookmarks.status,200);assert.deepEqual(otherBookmarks.body.items,[]);assert.equal(otherBookmarks.body.revision,0);
|
||||
const ui=await win.webContents.executeJavaScript("({company:document.getElementById('library-company')?.textContent,personal:document.getElementById('library-bookmarks')?.textContent})");assert.ok(JSON.stringify(ui).includes(edited.title),'Live authoritative bookmark missing from UI');
|
||||
async function publish(mutator){const current=(await api('/api/admin/overview')).body.policy;mutator(current);current.version++;assert.equal((await api('/api/admin/policy',current,'PUT')).status,200);await until(async()=>{const s=await state();return s.ready&&s.policyVersion===current.version;});return current.version;}
|
||||
const lockedVersion=await publish(p=>{p.browser.bookmarks.allowUserAdd=false;p.browser.bookmarks.allowUserEdit=false;p.browser.bookmarks.allowUserDelete=false;});assert.equal((await mutate('update',personal)).ok,false);const s=await state();assert.equal((await call('mutateBookmarks',{action:'delete',id:personal.id,expectedRevision:s.bookmarks.personal.revision,policyVersion:lockedVersion})).ok,false);
|
||||
adminRead=await api(`/api/admin/devices/${id}/bookmarks`);assert.equal((await api(`/api/admin/devices/${id}/bookmarks`,{expectedRevision:adminRead.body.revision,policyVersion:lockedVersion,items:[]},'PUT')).status,200);await until(async()=>(await state()).bookmarks.personal.revision===3);
|
||||
await publish(p=>p.browser.settings.visible=false);assert.equal((await state()).bookmarks.managed[0].id,'company-intranet','Hiding settings removed required company links');assert.equal((await state()).bookmarks.allowUserAdd,false);
|
||||
await publish(p=>p.browser.bookmarks.adminReadUserBookmarks=false);assert.equal((await api(`/api/admin/devices/${id}/bookmarks`)).status,403);assert.equal((await api(`/api/admin/devices/${id}/bookmarks`,{expectedRevision:3,policyVersion:(await state()).policyVersion,items:[personal]},'PUT')).status,403);
|
||||
const events=(await api('/api/admin/overview')).body.events;assert.equal(JSON.stringify(events).includes(edited.title),false);assert.equal(JSON.stringify(events).includes(personal.url),false);
|
||||
console.log(JSON.stringify({nativeBookmarks:true,managedReadOnly:true,personalRemoteEdit:true,revisionStablePolicy:true,permissionLocks:true}));clearTimeout(deadline);app.quit();
|
||||
}catch(error){console.error(JSON.stringify({nativeBookmarks:false,error:error.message}));clearTimeout(deadline);app.exit(1);}});
|
||||
@@ -0,0 +1,22 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {createRequire} from 'node:module';
|
||||
import {createServer} from 'node:http';
|
||||
import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} 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';
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-bookmarks-')),electron=createRequire(import.meta.url)('electron');
|
||||
let store,management,sink,child;
|
||||
try{
|
||||
const admin=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url');
|
||||
sink=createServer((_req,res)=>res.end('<!doctype html><title>Public bookmark fixture</title><h1>Public company intranet fixture</h1>')).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
store=openStore(join(temporary,'db'));const policy=store.get('policy');policy.browser.homeUrl=`http://127.0.0.1:${sink.address().port}/`;policy.browser.allowedHosts=['127.0.0.1'];policy.browser.bookmarks.managed=[{id:'company-intranet',title:'Company intranet',url:policy.browser.homeUrl+'intranet',folder:'Company'}];store.put('policy',policy);
|
||||
store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+60000,'public-bookmark-user');
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken:admin,requirePQManagement:true};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
const env={...process.env,SAFETYNET_BOOKMARK_PROFILE:join(temporary,'browser'),SAFETYNET_BOOKMARK_SERVER:config.publicUrl,SAFETYNET_BOOKMARK_ADMIN:admin,SAFETYNET_BOOKMARK_CODE:code,SAFETYNET_BOOKMARK_ORIGIN:policy.browser.homeUrl};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(electron,['scripts/bookmarks-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),120000);const status=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(status,0,'Native bookmarks proof failed');
|
||||
const report={native:true,companyBookmarksReadOnly:true,personalSynced:true,administratorRemoteEdit:true,deviceRevisionIsolated:true,policyVersionStableOnPersonalEdit:true,remotePermissionsEnforced:true,adminReadPermissionEnforced:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/bookmarks-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{child?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(r=>server.close(r));}store?.db.close();}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
const electron=createRequire(import.meta.url)('electron');
|
||||
const env={...process.env};
|
||||
for(const name of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','INSPECTOR_TOKEN','SIEM_HTTP_TOKEN','SIEM_KEY_FILE'])delete env[name];
|
||||
const child=spawn(electron,['.'],{stdio:'inherit',env,windowsHide:true});child.on('exit',code=>process.exit(code??1));
|
||||
process.on('SIGINT',()=>child.kill());process.on('SIGTERM',()=>child.kill());
|
||||
@@ -0,0 +1,7 @@
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
let failed=false;
|
||||
function walk(dir){for(const entry of readdirSync(dir,{withFileTypes:true})){const p=join(dir,entry.name);if(entry.isDirectory())walk(p);else if(/\.(mjs|cjs|js)$/.test(p)){const result=spawnSync(process.execPath,['--check',p],{encoding:'utf8'});if(result.status!==0){console.error(result.stderr);failed=true;}}}}
|
||||
for(const directory of ['apps','packages','scripts','tests'])walk(directory);
|
||||
if(failed)process.exit(1);console.log('All JavaScript syntax checks passed.');
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
if(!process.versions.electron){const {spawnSync}=require('node:child_process');const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;const r=spawnSync(require('electron'),[__filename],{env,stdio:'inherit',windowsHide:true,timeout:20000});process.exit(r.status??1);}
|
||||
else{
|
||||
const {app,BrowserWindow,WebContentsView,clipboard,ClipboardItem}=require('electron');const {attachPageClipboardGuard,disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs');
|
||||
app.enableSandbox();app.disableHardwareAcceleration();
|
||||
app.whenReady().then(async()=>{let win,view,original;try{
|
||||
win=new BrowserWindow({show:false});view=new WebContentsView({webPreferences:{sandbox:true,contextIsolation:true,nodeIntegration:false}});win.contentView.addChildView(view);await view.webContents.loadURL('about:blank');
|
||||
let failed=false;await attachPageClipboardGuard(view.webContents,()=>{failed=true;});
|
||||
await new Promise(resolve=>{view.webContents.once('devtools-opened',resolve);view.webContents.openDevTools({mode:'detach',activate:false});});
|
||||
await new Promise(r=>setTimeout(r,500));
|
||||
original=[];for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);if(Object.keys(data).length)original.push(new ClipboardItem(data));}
|
||||
const sentinel='SYNTHETIC_DEVTOOLS_DLP_BYPASS_PROBE';await clipboard.writeText('Public probe sentinel');
|
||||
const front=view.webContents.devToolsWebContents;
|
||||
const host=await front.executeJavaScript(`(()=>{if(typeof InspectorFrontendHost==='undefined'||typeof InspectorFrontendHost.copyText!=='function')return false;InspectorFrontendHost.copyText(${JSON.stringify(sentinel)});return true;})()`);
|
||||
await new Promise(r=>setTimeout(r,200));
|
||||
const result={devToolsOpened:view.webContents.isDevToolsOpened(),privateDebuggerAttached:view.webContents.debugger.isAttached(),guardFailure:failed,nativeFrontendCopyAvailable:host,frontendCopyBypassesSiteGuard:await clipboard.readText()===sentinel};
|
||||
require('node:fs').writeFileSync('artifacts/devtools-privilege-probe.json',JSON.stringify(result,null,2));console.log(JSON.stringify(result));
|
||||
}catch(e){console.error(e.message);process.exitCode=1;}finally{if(original!==undefined)await clipboard.write(original);if(view&&!view.webContents.isDestroyed()){disposePageClipboardGuard(view.webContents);view.webContents.closeDevTools();view.webContents.close({waitForBeforeUnload:false});}win?.destroy();app.exit(process.exitCode||0);}});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow,webContents}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs');const {disposePageClipboardGuard}=require('../apps/browser/page-clipboard-guard.cjs');
|
||||
app.setPath('userData',process.env.SAFETYNET_DEVTOOLS_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs');const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn,label){for(let i=0;i<200;i++){if(await fn())return;await wait(100);}throw Error(label+' timed out');}
|
||||
app.whenReady().then(async()=>{let win;try{
|
||||
await until(()=>{win=BrowserWindow.getAllWindows()[0];return win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html');},'Shell');const call=async(name,arg)=>{const result=await win.webContents.executeJavaScript(`window.safetynet[${JSON.stringify(name)}](${JSON.stringify(arg)})`);assert.equal(result.ok,true,result.error);return result.data;};const state=()=>call('state');
|
||||
await call('enroll',{server:process.env.SAFETYNET_DEVTOOLS_SERVER,code:process.env.SAFETYNET_DEVTOOLS_CODE});let page;await until(()=>{page=webContents.getAllWebContents().find(w=>w.getURL().startsWith(process.env.SAFETYNET_DEVTOOLS_SINK));return page&&!page.isLoading();},'Managed page');
|
||||
const toolsOpened=()=>BrowserWindow.getAllWindows().some(w=>w!==win&&w.isVisible()&&!w.webContents.isLoading()&&w.webContents.getURL().startsWith('devtools://devtools/bundled/'));const key=async(code,modifiers=[])=>{page.focus();page.sendInputEvent({type:'keyDown',keyCode:code,modifiers});page.sendInputEvent({type:'keyUp',keyCode:code,modifiers});await wait(300);};
|
||||
await key('F12');assert.equal(toolsOpened(),false,'Default policy opened DevTools');
|
||||
const api=async(path,method,body)=>{const r=await fetch(process.env.SAFETYNET_DEVTOOLS_SERVER+path,{method,headers:{authorization:`Bearer ${process.env.SAFETYNET_DEVTOOLS_ADMIN}`,'content-type':'application/json'},body:JSON.stringify(body)});assert.equal(r.status,200);return r.json();};
|
||||
await api('/api/admin/user-profile-configuration','PUT',{expectedVersion:1,categories:[],delegation:{devTools:true}});await api('/api/admin/user-profiles/developer','PUT',{expectedVersion:2,profile:{id:'developer',name:'Developer',deviceIds:[(await state()).deviceId],features:{devTools:'allow'}}});
|
||||
await until(async()=>{const s=await state();return s.ready&&s.policyVersion===3&&!page.isLoading();},'Signed developer grant');
|
||||
await key('F12');await until(()=>toolsOpened(),'Granted F12');assert.equal(page.debugger.isAttached(),true,'DevTools detached site security guard');assert.equal(win.webContents.isDevToolsOpened(),false,'Trusted chrome DevTools opened');
|
||||
await key('F12');await until(()=>!toolsOpened(),'F12 close');await key('I',['control','shift']);await until(()=>toolsOpened(),'Granted CtrlShiftI');
|
||||
await api('/api/admin/user-profiles/developer','DELETE',{expectedVersion:3});await until(async()=>{const s=await state();return s.policyVersion===4&&s.ready&&!toolsOpened();},'Live developer grant revocation');await key('F12');assert.equal(toolsOpened(),false,'Revoked developer grant reopened tools');assert.equal(page.debugger.isAttached(),true);
|
||||
fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync('artifacts/devtools-profile-proof.json',JSON.stringify({actualNativeShortcut:true,defaultDenied:true,centralSignedProfileGranted:true,f12Opened:true,ctrlShiftIOpened:true,privateClipboardGuardRetained:true,trustedChromeToolsDenied:true,liveProfileRevocationClosed:true,revokedCannotReopen:true},null,2));console.log('Native developer profile deny/grant/revocation proof passed.');
|
||||
}catch(error){console.error(error.message);process.exitCode=1;}finally{for(const contents of webContents.getAllWebContents())if(contents!==win?.webContents&&!contents.isDestroyed())disposePageClipboardGuard(contents);app.exit(process.exitCode||0);}});
|
||||
@@ -0,0 +1,8 @@
|
||||
import assert from 'node:assert/strict';import {createServer} from 'node:http';import {createRequire} from 'node:module';import {mkdtempSync,rmSync} from 'node:fs';import {tmpdir} from 'node:os';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 {openStore,digest} from '../apps/server/store.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-devtools-proof-'));let sink,server,store,child;
|
||||
try{
|
||||
sink=createServer((_req,res)=>{res.setHeader('content-type','text/html');res.end('<!doctype html><title>Developer fixture</title><h1>Synthetic developer page</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));const sinkUrl=`http://127.0.0.1:${sink.address().port}`;
|
||||
store=openStore(join(directory,'management'));const policy=store.get('policy');policy.browser.homeUrl=sinkUrl;policy.browser.allowedHosts=['127.0.0.1'];store.put('policy',policy);const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-developer');const config={adminToken,publicUrl:'http://127.0.0.1',secure:false};server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_DEVTOOLS_SERVER:config.publicUrl,SAFETYNET_DEVTOOLS_CODE:code,SAFETYNET_DEVTOOLS_ADMIN:adminToken,SAFETYNET_DEVTOOLS_SINK:sinkUrl,SAFETYNET_DEVTOOLS_USERDATA:join(directory,'browser'),SAFETYNET_SMOKE_USERDATA:join(directory,'browser')};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/devtools-profile-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',b=>process.stdout.write(b));child.stderr.on('data',b=>process.stderr.write(String(b).replaceAll(code,'[fixture enrollment]').replaceAll(adminToken,'[fixture admin]')));const timer=setTimeout(()=>child.kill(),90000);const exit=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(exit,0,'Native developer-profile proof failed');
|
||||
}finally{child?.kill();for(const service of [server,sink])if(service)await new Promise(r=>{service.closeAllConnections();service.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-devtools-proof-'))throw Error('Unsafe fixture cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
||||
@@ -0,0 +1,284 @@
|
||||
const {app,BrowserWindow}=require('electron');
|
||||
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),'Electron JavaScript started.\n');
|
||||
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);
|
||||
app.disableHardwareAcceleration();
|
||||
require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms));
|
||||
const log=message=>fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),message+'\n');
|
||||
app.whenReady().then(async()=>{
|
||||
try{
|
||||
fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),'Electron ready.\n');
|
||||
let win;
|
||||
for(let n=0;n<100;n++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}
|
||||
await wait(300);
|
||||
log('Reading initial shell state: '+win.webContents.getURL());
|
||||
const initial=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(initial.data.ready,false);
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultCreate("fixture-master-password-only")')).ok,false,'Unenrolled vault access was allowed');
|
||||
log('Starting enrollment');
|
||||
const input={server:process.env.SAFETYNET_SMOKE_SERVER,code:process.env.SAFETYNET_SMOKE_CODE};
|
||||
const result=await win.webContents.executeJavaScript(`window.safetynet.enroll(${JSON.stringify(input)})`);assert.equal(result.ok,true,result.error);assert.equal(result.data.ready,true);assert.equal(result.data.brand.name,'Acme Secure Browser');
|
||||
await wait(600);
|
||||
log('Enrollment passed; testing managed page');
|
||||
const page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;
|
||||
log('Managed page URL: '+page.getURL());
|
||||
const url=process.env.SAFETYNET_SMOKE_SINK;
|
||||
const publicResult=await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:'Public information'}).then(r=>r.text()).catch(e=>({error:e.message}))`);
|
||||
log('Public request: '+JSON.stringify(publicResult));assert.equal(publicResult,'received');
|
||||
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');
|
||||
// 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 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);
|
||||
await selectClipboardText('Public code example');await clipboardKey('C');
|
||||
await clipboardWait(async()=>await clipboard.readText()==='Public code example');
|
||||
assert.equal(await clipboard.readText(),'Public code example','Allowed native copy failed');
|
||||
await clipboard.writeText(clipboardSentinel);await selectClipboardText('Secret Roadmap');await clipboardKey('C');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Blocked secret reached OS clipboard');
|
||||
await selectClipboardText('powershell -NoProfile -EncodedCommand VABFAFMAVAA=');await clipboardKey('C');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'ClickFix command reached OS clipboard');
|
||||
await selectClipboardText('SAFETYNET_BLOCK_TEST');await clipboardKey('C');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'YARA-blocked clipboard text reached OS clipboard');
|
||||
await page.executeJavaScript("document.addEventListener('copy',event=>{event.clipboardData.setData('text/plain','Secret Roadmap');event.preventDefault()},{once:true})");
|
||||
await selectClipboardText('Public selection despite hostile copy handler');await clipboardKey('C');
|
||||
assert.equal((await clipboard.readText()).includes('Secret Roadmap'),false,'Page copy handler bypassed inspection');
|
||||
await clipboard.writeText(clipboardSentinel);
|
||||
await page.executeJavaScript("(()=>{const e=document.getElementById('public-input');e.value='';e.focus()})()");await clipboardKey('V');
|
||||
await clipboardWait(async()=>await page.executeJavaScript('document.getElementById("public-input").value')===clipboardSentinel);
|
||||
assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),clipboardSentinel,'Allowed native paste failed');
|
||||
await clipboard.writeText('Secret Roadmap');await clipboardKey('V');
|
||||
assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),clipboardSentinel,'Secret was inserted by native paste');
|
||||
await page.executeJavaScript("(()=>{const e=document.getElementById('public-input');e.value='Secret Roadmap';e.focus();e.select()})()");await clipboard.writeText(clipboardSentinel);await clipboardKey('X');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Blocked cut wrote OS clipboard');assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Secret Roadmap','Blocked cut deleted source');
|
||||
await page.executeJavaScript("(()=>{const e=document.getElementById('public-input');e.value='Public cut sample';e.focus();e.select()})()");await clipboardKey('Delete',true);
|
||||
assert.equal(await clipboard.readText(),'Public cut sample','Allowed Shift+Delete cut failed');assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'','Allowed cut preserved deleted selection');
|
||||
await clipboardKey('Insert',true);
|
||||
assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Public cut sample','Allowed Shift+Insert paste failed');
|
||||
await clipboard.writeText(clipboardSentinel);
|
||||
// Programmatic legacy copy must not acquire the native user gesture capability.
|
||||
await selectClipboardText('Secret Roadmap');await page.executeJavaScript("document.execCommand('copy')",true);await wait(250);
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Legacy scripted copy bypassed clipboard gate');
|
||||
const clipboardEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:{authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`}})).json()).events.filter(event=>event.kind.startsWith('clipboard.'));
|
||||
assert.ok(clipboardEvents.some(event=>event.kind==='clipboard.block'),'Clipboard blocks were not centrally audited');
|
||||
assert.equal(JSON.stringify(clipboardEvents).includes('Secret Roadmap'),false,'Clipboard contents appeared in audit records');
|
||||
assert.equal(JSON.stringify(clipboardEvents).includes('VABFAFMAVAA='),false,'Command contents appeared in audit records');
|
||||
log('Native clipboard: safe copy/paste allowed, confidential copy/paste/cut held, ClickFix execution command and hostile copy handler blocked');
|
||||
}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');
|
||||
async function localAgent(body){
|
||||
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};
|
||||
}
|
||||
const sessionResult=await localAgent({action:'sessions'});assert.equal(sessionResult.exit,0,sessionResult.errors);const sessionId=JSON.parse(sessionResult.output).result[0].id;assert.match(sessionId,/^[0-9a-f-]{36}$/i);assert.equal(sessionId,(await win.webContents.executeJavaScript('window.safetynet.state()')).data.activeTabId);
|
||||
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);
|
||||
log('Authenticated agent sessions, sanitized read, type and sensitive-field denial passed');
|
||||
async function agentApp(action,args={}){const response=await localAgent({action,args});assert.equal(response.exit,0,response.errors);return JSON.parse(response.output).result;}
|
||||
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+'/'};
|
||||
await agentApp('bookmarks:mutate',{action:'add',bookmark,expectedRevision:library.personal.revision,policyVersion:library.policyVersion});
|
||||
const updatedLibrary=await agentApp('bookmarks:list');assert.equal(updatedLibrary.personal.items.some(item=>item.id===bookmark.id),true);
|
||||
await agentApp('bookmarks:mutate',{action:'delete',id:bookmark.id,expectedRevision:updatedLibrary.personal.revision,policyVersion:updatedLibrary.policyVersion});
|
||||
const createdTab=await agentApp('tabs:create',{group:'Agent proof'});assert.notEqual(createdTab.sessionId,sessionId);
|
||||
await agentApp('tabs:group',{sessionId:createdTab.sessionId,group:'Public work'});
|
||||
await agentApp('tabs:activate',{sessionId});await agentApp('tabs:close',{sessionId:createdTab.sessionId});
|
||||
assert.equal((await agentApp('application:state')).activeTabId,sessionId);
|
||||
log('First-class agent settings, centrally revisioned bookmarks and native tab controls passed');
|
||||
const fixtureMaster='fixture-master-password-for-smoke-only';
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultCreate(${JSON.stringify(fixtureMaster)})`)).ok,true);
|
||||
const generated=await win.webContents.executeJavaScript('window.safetynet.vaultGenerate()');assert.equal(generated.ok,true);assert.equal(generated.data.length,32);
|
||||
const login={origin:'https://vault-login.test',label:'Fixture login',username:'fixture-user',password:generated.data};
|
||||
const saved=await win.webContents.executeJavaScript(`window.safetynet.vaultSave(${JSON.stringify(login)})`);assert.equal(saved.ok,true);
|
||||
const listed=await win.webContents.executeJavaScript('window.safetynet.vaultList()');assert.equal(listed.ok,true);assert.equal(listed.data[0].password,undefined);
|
||||
const encryptedVault=fs.readFileSync(path.join(process.env.SAFETYNET_SMOKE_USERDATA,'password-vault.json'),'utf8');assert.equal(encryptedVault.includes(login.password),false);assert.equal(encryptedVault.includes(login.username),false);assert.equal(encryptedVault.includes(fixtureMaster),false);
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultFill(${JSON.stringify(saved.data.id)})`)).ok,false,'HTTP page accepted vault fill');
|
||||
// Protocol fixture exercises Chromium's actual isolated DOM fill; production TLS remains mandatory.
|
||||
await page.session.protocol.handle('https',request=>{if(new URL(request.url).origin!==login.origin)throw Error('Unexpected fixture origin');return new Response('<!doctype html><title>Vault login fixture</title><form method="post" action="/signin"><label>Username<input id="login-user" name="username" autocomplete="username"></label><label>Password<input id="login-password" type="password" autocomplete="current-password"></label><button type="submit">Sign in</button></form>',{headers:{'content-type':'text/html'}});});
|
||||
try{
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.navigate("https://vault-login.test/login")')).ok,true);
|
||||
const beforeScriptedClipboard=await clipboard.readText();
|
||||
try{
|
||||
await clipboard.writeText(clipboardSentinel);
|
||||
const scriptWrite=await Promise.race([page.executeJavaScript("navigator.clipboard.writeText('Hidden page clipboard replacement').then(()=>true).catch(()=>false)",true),wait(3000).then(()=>false)]);
|
||||
assert.equal(scriptWrite,false,'Secure page acquired unguarded Clipboard API write');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Secure page overwrote OS clipboard through Clipboard API');
|
||||
await page.executeJavaScript("(()=>{const frame=document.createElement('iframe');frame.id='clipboard-child';frame.srcdoc='<p id=selected>Secret Roadmap</p>';document.body.append(frame)})()");await wait(350);
|
||||
const childCapabilities=await page.executeJavaScript("(()=>{const child=document.getElementById('clipboard-child').contentWindow;return [typeof child.require,typeof child.process,typeof child.safetynet]})()");
|
||||
assert.deepEqual(childCapabilities,['undefined','undefined','undefined'],'Child frame obtained Node or browser bridge capabilities');
|
||||
assert.equal(page.getLastWebPreferences().sandbox,true,'Managed renderer sandbox was disabled');
|
||||
await page.executeJavaScript("(()=>{const frame=document.getElementById('clipboard-child');try{const child=frame.contentWindow,d=child.document;if(!d.getElementById('selected'))return;const r=d.createRange();r.selectNodeContents(d.getElementById('selected'));child.getSelection().removeAllRanges();child.getSelection().addRange(r);child.focus();d.execCommand('copy')}catch{}finally{frame.remove()}})()",true);await wait(200);
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Child frame legacy copy bypassed clipboard protection');
|
||||
await page.executeJavaScript("(()=>{const frame=document.createElement('iframe');document.body.append(frame);try{const child=frame.contentWindow,d=child.document;d.body.textContent='Secret Roadmap';const r=d.createRange();r.selectNodeContents(d.body);child.getSelection().removeAllRanges();child.getSelection().addRange(r);child.focus();d.execCommand('copy')}finally{frame.remove()}})()",true);await wait(200);
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Immediate about:blank child copy bypassed clipboard protection');
|
||||
const childReset=await page.executeJavaScript("(()=>{const frame=document.createElement('iframe');document.body.append(frame);try{const child=frame.contentWindow,d=child.document;d.open();d.write('<p>Secret Roadmap</p>');d.close();const r=d.createRange();r.selectNodeContents(d.body);child.getSelection().removeAllRanges();child.getSelection().addRange(r);child.focus();d.execCommand('copy');return 'reset-completed'}catch(error){return error.name}finally{frame.remove()}})()",true);await wait(200);
|
||||
assert.equal(childReset,'SecurityError','Child document replacement was not blocked');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Document reset bypassed child clipboard protection');
|
||||
const mainReset=await page.executeJavaScript("(()=>{try{document.open();document.write('<p>Secret Roadmap</p>');document.close();const r=document.createRange();r.selectNodeContents(document.body);getSelection().removeAllRanges();getSelection().addRange(r);document.execCommand('copy');return 'reset-completed'}catch(error){return error.name}})()",true);await wait(200);
|
||||
assert.equal(mainReset,'SecurityError','Main document replacement was not blocked');
|
||||
assert.equal(await clipboard.readText(),clipboardSentinel,'Document reset bypassed main-frame clipboard protection');
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.navigate("https://vault-login.test/login")')).ok,true);
|
||||
}finally{await clipboard.writeText(beforeScriptedClipboard);}
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultFill(${JSON.stringify(saved.data.id)})`)).ok,true,'Exact HTTPS origin fill failed');
|
||||
assert.equal(await page.executeJavaScript('document.getElementById("login-user").value'),login.username);assert.equal(await page.executeJavaScript('document.getElementById("login-password").value'),login.password);
|
||||
assert.equal(await page.executeJavaScript('typeof window.safetynet'),'undefined','Remote site obtained vault bridge');
|
||||
await page.executeJavaScript('document.querySelector("form").action="https://different-origin.test/capture"');
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultFill(${JSON.stringify(saved.data.id)})`)).ok,false,'Cross-origin form accepted password');
|
||||
}finally{await page.session.protocol.unhandle('https');await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url)})`);}
|
||||
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);
|
||||
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);
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultList()')).data.length,1);
|
||||
await win.webContents.executeJavaScript('document.getElementById("vault-close").click()');await wait(100);
|
||||
log('Real local vault: native ML-KEM, DPAPI, encrypted storage, masked metadata, isolated exact-HTTPS fill, hostile form denial, blur lock and reopen passed');
|
||||
const {dialog}=require('electron'),originalSave=dialog.showSaveDialog;let saveCalls=0;
|
||||
dialog.showSaveDialog=async(_win,options)=>{saveCalls++;return {canceled:false,filePath:path.join(process.env.SAFETYNET_SMOKE_USERDATA,path.basename(options.defaultPath))};};
|
||||
try{
|
||||
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 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');
|
||||
}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');
|
||||
if(fs.existsSync(runtime)){
|
||||
const create=require('node:child_process').spawnSync(runtime,['-c','import sys,zipfile; z=zipfile.ZipFile(sys.argv[1],"w"); z.writestr("word/document.xml",\'<document xmlns="urn:test"><t>Secret Roadmap</t></document>\'); z.close()',sample],{encoding:'utf8',windowsHide:true});assert.equal(create.status,0,create.stderr);
|
||||
const {dialog}=require('electron'),originalChooser=dialog.showOpenDialog;
|
||||
dialog.showOpenDialog=async()=>({canceled:false,filePaths:[sample]});
|
||||
try{const inspection=await win.webContents.executeJavaScript('window.safetynet.inspectFile()');assert.equal(inspection.ok,true,inspection.error);assert.equal(inspection.data.ruleId,'secret-roadmap');assert.equal(inspection.data.contentType,'office');}finally{dialog.showOpenDialog=originalChooser;}
|
||||
log('Bundled local document inspection passed');
|
||||
}
|
||||
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');
|
||||
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');
|
||||
const admin=new BrowserWindow({show:false,width:1440,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true}});
|
||||
await admin.loadURL(process.env.SAFETYNET_SMOKE_SERVER);
|
||||
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);
|
||||
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;
|
||||
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(secondary)}).then(r=>r.text())`),'secondary');
|
||||
const beforeDNS=(await (await fetch(url+'/count')).json()).secondaryRequests;
|
||||
const blocklistUpdate=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/security/blocklists`,{method:'PUT',headers,body:JSON.stringify({expectedVersion:1,blockedHosts:['blocked.test'],blockedIPs:['127.0.0.2']})});assert.equal(blocklistUpdate.status,200);
|
||||
for(let n=0;n<200;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===2)break;await wait(100);}
|
||||
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(secondary)}).then(()=>false).catch(()=>true)`),true);
|
||||
assert.equal((await (await fetch(url+'/count')).json()).secondaryRequests,beforeDNS,'Blocked IP receiver got the request');log('Live DNS/IP blocklist prevented a previously working destination');
|
||||
const overview=await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json();
|
||||
const managedPolicy=overview.policy;managedPolicy.version+=1;managedPolicy.security.dns.blockedIPs=[];managedPolicy.proxy={mode:'fixed_servers',rules:`http=127.0.0.1:${new URL(url).port}`,bypass:''};
|
||||
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);
|
||||
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);
|
||||
const draft=await draftResponse.json();assert.equal(draft.status,'draft');assert.ok(draft.reference.fragmentHashes.length>0);
|
||||
const publicationResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/teaching/${draft.id}/publish`,{method:'POST',headers,body:JSON.stringify({expectedVersion:3,approve:true})});assert.equal(publicationResponse.status,200);
|
||||
for(let n=0;n<200;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===4)break;await wait(100);}
|
||||
await page.executeJavaScript("fetch('/sink',{method:'POST',body:'Public text after teaching'}).then(r=>r.text())");
|
||||
const beforeExcerpt=(await (await fetch(url+'/count')).json()).posts;
|
||||
const excerpt='Public preface: engineering roadmap contains restricted architecture budgets and unreleased customer contracts. Public appendix.';
|
||||
assert.equal(await page.executeJavaScript(`fetch('/sink',{method:'POST',body:${JSON.stringify(excerpt)}}).then(()=>false).catch(()=>true)`),true);
|
||||
assert.equal((await (await fetch(url+'/count')).json()).posts,beforeExcerpt,'Registered confidential excerpt reached external receiver');
|
||||
log('Governed document teaching published signed fragment rules and blocked an altered excerpt before transmission');
|
||||
for(const [enabled,version] of [[false,5],[true,6]]){
|
||||
const toggle=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/devices/${result.data.deviceId}/inspection`,{method:'PATCH',headers,body:JSON.stringify({expectedVersion:version-1,documentAnalysisEnabled:enabled})});assert.equal(toggle.status,200);
|
||||
for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===version)break;await wait(100);}
|
||||
const scanState=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(scanState.data.documentScanEnabled,enabled);
|
||||
if(!enabled){const scan=await win.webContents.executeJavaScript('window.safetynet.inspectFile()');assert.equal(scan.ok,false);assert.match(scan.error,/disabled/);assert.equal(await page.executeJavaScript(`fetch('/sink',{method:'POST',body:'Secret Roadmap'}).then(()=>false).catch(()=>true)`),true);}
|
||||
}
|
||||
log('Signed per-browser document-analysis switch changed manual scanning without disabling mandatory DLP');
|
||||
const displayPolicy=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).policy;
|
||||
displayPolicy.version+=1;displayPolicy.security.content.blockedPhrases=['restricted-display-marker'];
|
||||
const displayPublication=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(displayPolicy)});assert.equal(displayPublication.status,200);
|
||||
for(let n=0;n<200;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===7)break;await wait(100);}
|
||||
await page.executeJavaScript("document.body.append('restricted-display-marker')");
|
||||
for(let n=0;n<50;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready)break;await wait(100);}
|
||||
const contentState=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(contentState.data.ready,false);assert.equal(contentState.data.message,'This page contains content blocked by company policy.');
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(page.getURL()+'#same-document')})`)).ok,false,'Hash-only navigation escaped content quarantine');
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.navigate("wss://example.test/")')).ok,false,'Unsupported navigation cleared quarantine');
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,false);
|
||||
log('Live signed content rule quarantined a changed document through isolated preload');
|
||||
displayPolicy.version+=1;displayPolicy.security.content.blockedPhrases=[];
|
||||
assert.equal((await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(displayPolicy)})).status,200);
|
||||
for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===8)break;await wait(100);}
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,true);
|
||||
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>';
|
||||
await page.session.protocol.handle('https',request=>{if(new URL(request.url).origin!=='https://identity.example.test')throw Error('Unexpected visual fixture URL');return new Response(visualLogin,{headers:{'content-type':'text/html'}});});
|
||||
log('Visual fixture: navigating approved origin');
|
||||
await Promise.race([win.webContents.executeJavaScript('window.safetynet.navigate("https://identity.example.test/login")'),wait(15000).then(()=>{throw Error('Visual fixture navigation timed out');})]);await wait(400);
|
||||
log('Visual fixture: capturing approved form');
|
||||
const formRect=await page.executeJavaScript('(()=>{const r=document.querySelector("form").getBoundingClientRect();return {x:Math.floor(r.x),y:Math.floor(r.y),width:Math.ceil(r.width),height:Math.ceil(r.height)}})()');
|
||||
const referencePNG=await require('../apps/browser/visual-capture.cjs').capturePNG(page,formRect);
|
||||
const referenceResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/security/visual-reference`,{method:'POST',headers,body:JSON.stringify({expectedVersion:8,brandId:'identity-lab',label:'Controlled Identity Lab',allowedOrigins:['https://identity.example.test'],data:referencePNG.toString('base64'),scope:'login-form'})});assert.equal(referenceResponse.status,200);const visualDraft=await referenceResponse.json();
|
||||
const visualPolicy=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).policy;visualPolicy.version++;visualDraft.brand.requiredText=['SafetyNet Identity Lab'];visualPolicy.security.visualPhishing={enabled:true,failClosed:true,brands:[visualDraft.brand]};
|
||||
assert.equal((await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(visualPolicy)})).status,200);
|
||||
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.');
|
||||
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);
|
||||
await wait(2500);assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,false,'Policy refresh cleared phishing quarantine');
|
||||
const safeNavigation=await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url)})`);assert.equal(safeNavigation.ok,true,safeNavigation.error);
|
||||
await page.session.protocol.unhandle('https');
|
||||
log('Real screenshot-derived pHash: trusted origin allowed, identical login at unlisted origin blocked, no payload released, quarantine survived refresh and safe navigation recovered');
|
||||
const managedClipboardPolicy=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).policy;
|
||||
const liveClipboardOriginal=await clipboard.readText();
|
||||
try{
|
||||
for(const allowed of [false,true]){
|
||||
// A new policy intentionally reloads the managed document. Observe that
|
||||
// reload before selecting text; otherwise the guard correctly rejects a
|
||||
// selection whose document is replaced during inspection.
|
||||
let policyDocumentLoaded=false;const onPolicyLoad=()=>{policyDocumentLoaded=true;};
|
||||
page.once('did-finish-load',onPolicyLoad);
|
||||
managedClipboardPolicy.version++;managedClipboardPolicy.security.clipboard.allowCopy=allowed;
|
||||
assert.equal((await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(managedClipboardPolicy)})).status,200);
|
||||
await clipboardWait(async()=>{const s=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;return s.ready&&s.policyVersion===managedClipboardPolicy.version;});
|
||||
await clipboardWait(async()=>policyDocumentLoaded&&!page.isLoading());page.removeListener('did-finish-load',onPolicyLoad);
|
||||
await clipboard.writeText(clipboardSentinel);await selectClipboardText('Public live-policy sample');
|
||||
// Adding the synthetic selection node triggers the preload's 150 ms
|
||||
// visual-mutation debounce. Let that new document revision be checked
|
||||
// before starting the deliberately slower isolated YARA scan.
|
||||
await page.executeJavaScript('new Promise(resolve=>setTimeout(resolve,250))');
|
||||
await clipboardWait(async()=>{const s=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;return s.ready&&!s.loginCheckPending;});
|
||||
await clipboardKey('C');
|
||||
if(allowed&&await clipboard.readText()!=='Public live-policy sample'){const s=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;log('Live copy gate state: '+JSON.stringify({ready:s.ready,policyVersion:s.policyVersion,message:s.message,clipboardPending:s.clipboardPending,loginCheckPending:s.loginCheckPending}));}
|
||||
assert.equal(await clipboard.readText(),allowed?'Public live-policy sample':clipboardSentinel,'Native copy ignored centrally published permission');
|
||||
await clipboard.writeText(clipboardSentinel);const share=await win.webContents.executeJavaScript('window.safetynet.shareURL()');
|
||||
assert.equal(share.ok,allowed,'Share ignored centrally published copy permission');
|
||||
assert.equal(await clipboard.readText(),allowed?new URL(url).toString():clipboardSentinel,'Share clipboard effect did not match signed copy policy');
|
||||
}
|
||||
log('Live signed clipboard permission blocked and re-enabled native Copy and Share without restart');
|
||||
}finally{await clipboard.writeText(liveClipboardOriginal);await page.executeJavaScript("document.getElementById('clipboard-source')?.remove()");}
|
||||
await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/devices/${result.data.deviceId}/revoke`,{method:'POST',headers,body:'{}'});
|
||||
for(let n=0;n<180;n++){const state=await win.webContents.executeJavaScript('window.safetynet.state()');if(!state.data.ready)break;await wait(100);}
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,false,'Revocation did not quarantine browser');
|
||||
console.log('Electron smoke passed: enrollment, clipboard DLP/ClickFix and live permissions, sandboxed frame bypass tests, agents, YARA pre-save downloads, local documents, live DNS/content, proxy, governed teaching and revocation.');
|
||||
require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);
|
||||
app.exit(0);
|
||||
}catch(e){fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),e.stack+'\n');console.error(e.stack);app.exit(1);}
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1');
|
||||
let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000);
|
||||
async function evaluate(source){return window.webContents.executeJavaScript(source,true);}
|
||||
async function waitFor(source){const until=Date.now()+12000;while(Date.now()<until){if(await evaluate(source))return;await new Promise(r=>setTimeout(r,75));}throw Error('Administrator UI state did not arrive');}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);
|
||||
await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`);
|
||||
assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'});
|
||||
const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff');
|
||||
await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);
|
||||
assert.equal(await evaluate(`document.querySelectorAll('nav a').length>=12`),true);assert.equal(await evaluate(`document.getElementById('draft-state').textContent`),'Published policy v1');assert.equal(await evaluate(`document.getElementById('token').value`),'');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');
|
||||
await evaluate(`document.getElementById('preset-id').value='baseline';document.getElementById('preset-id').dispatchEvent(new Event('change'));document.getElementById('preset-preview').click()`);await waitFor(`!document.getElementById('preset-apply').disabled`);
|
||||
assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');assert.ok((await evaluate(`document.getElementById('preset-changes').textContent`)).includes('browser.allowDevTools'));
|
||||
await evaluate(`document.getElementById('preset-apply').click()`);await waitFor(`document.getElementById('status').textContent.includes('Template added')`);assert.equal(await evaluate(`document.getElementById('devtools').checked`),false);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');
|
||||
await evaluate(`document.getElementById('review-changes').click()`);assert.equal(await evaluate(`document.getElementById('change-review').open`),true);assert.ok((await evaluate(`document.getElementById('change-review-list').textContent`)).includes('browser.allowDevTools'));assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');await evaluate(`document.getElementById('review-close').click()`);
|
||||
await evaluate(`document.getElementById('publish').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`);
|
||||
await evaluate(`const values={'enterprise-dns-mode':'secure','enterprise-dns-templates':'https://dns.synthetic.example/dns-query','enterprise-vpn-interfaces':'Synthetic Corporate VPN','enterprise-vpn-dns':'10.77.0.53','enterprise-icap-url':${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_ICAP)},'enterprise-icap-method':'RESPMOD','enterprise-icap-timeout':'10000','enterprise-icap-max':'1048576'};for(const [id,value]of Object.entries(values))document.getElementById(id).value=value;for(const id of ['enterprise-vpn-required','enterprise-icap-enabled'])document.getElementById(id).checked=true;document.getElementById('enterprise-icap-probe').click()`);
|
||||
await waitFor(`document.getElementById('enterprise-icap-status').textContent.includes('passed')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');
|
||||
await evaluate(`document.getElementById('diagnostics-enabled').checked=true;document.getElementById('diagnostics-mode').value='debug';document.getElementById('diagnostics-endpoint').value='https://collector.synthetic.example';document.getElementById('diagnostics-debug-sample').value='1';document.getElementById('agent-scope-bookmarks:mutate').checked=true;`);
|
||||
await evaluate(`document.getElementById('publish').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('published')`);
|
||||
const fields=await evaluate(`({dns:document.getElementById('enterprise-dns-mode').value,vpn:document.getElementById('enterprise-vpn-required').checked,icap:document.getElementById('enterprise-icap-enabled').checked,method:document.getElementById('enterprise-icap-method').value,readiness:document.getElementById('integration-readiness').textContent,errors:globalThis.__proofErrors})`);assert.equal(fields.dns,'secure');assert.equal(fields.vpn,true);assert.equal(fields.icap,true);assert.equal(fields.method,'RESPMOD');assert.ok(fields.readiness.includes('configured'));assert.equal(fields.errors,0);
|
||||
assert.equal(await evaluate(`document.getElementById('preset-changes').childElementCount`),0);assert.equal(await evaluate(`document.getElementById('preset-description').textContent`),'');
|
||||
await evaluate(`scrollTo(0,0)`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/enterprise-admin.png'),(await window.webContents.capturePage()).toPNG());
|
||||
await evaluate(`document.getElementById('event-filter').value='no-such-synthetic-event';document.getElementById('event-filter').dispatchEvent(new Event('input'));`);assert.ok((await evaluate(`document.getElementById('event-filter-count').textContent`)).startsWith('0 of '));await evaluate(`document.getElementById('event-filter').value='';document.getElementById('event-filter').dispatchEvent(new Event('input'));document.getElementById('device-filter').value='synthetic';document.getElementById('device-filter').dispatchEvent(new Event('input'));`);assert.equal(await evaluate(`document.getElementById('device-filter-count').textContent`),'0 of 0 devices');
|
||||
for(const theme of ['light','dark']){await evaluate(`document.getElementById('console-theme').value=${JSON.stringify(theme)};document.getElementById('console-theme').dispatchEvent(new Event('change'));`);assert.equal(await evaluate(`document.documentElement.dataset.theme`),theme);assert.equal(await evaluate(`localStorage.getItem('safetynet.admin.theme')`),theme);window.webContents.invalidate();await new Promise(r=>setTimeout(r,150));fs.writeFileSync(path.resolve(`artifacts/admin-console-${theme}.png`),(await window.webContents.capturePage()).toPNG());}
|
||||
window.setSize(600,1000);await evaluate(`scrollTo(0,0)`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,150));assert.equal(await evaluate(`document.documentElement.scrollWidth<=innerWidth`),true);fs.writeFileSync(path.resolve('artifacts/admin-console-mobile.png'),(await window.webContents.capturePage()).toPNG());assert.equal(await evaluate(`globalThis.__proofErrors`),0);
|
||||
await evaluate(`document.getElementById('sessions-load').click()`);await waitFor(`document.getElementById('sessions-status').textContent.includes('0 active management sessions')`);
|
||||
await evaluate(`globalThis.__adminPrincipal=currentPrincipal;applyOperatorPermissions({sub:'synthetic-auditor',role:'auditor',permissions:['overview.read','audit.read','policy.read','teaching.read']});`);assert.equal(await evaluate(`document.getElementById('publish').disabled&&document.getElementById('publish-shortcut').disabled&&document.getElementById('enroll').disabled&&document.getElementById('bookmark-device-load').disabled&&document.getElementById('access-sessions').hidden`),true);await evaluate(`applyOperatorPermissions(globalThis.__adminPrincipal);delete globalThis.__adminPrincipal;`);
|
||||
assert.equal(await evaluate(`document.getElementById('diagnostics-enabled').checked&&document.getElementById('diagnostics-mode').value==='debug'&&document.getElementById('agent-scope-bookmarks:mutate').checked`),true);
|
||||
await evaluate(`applyOperatorPermissions({sub:'synthetic-auditor',role:'auditor',permissions:['overview.read','audit.read','policy.read']});`);
|
||||
assert.equal(await evaluate(`document.getElementById('diagnostics-enabled').disabled&&document.getElementById('agent-scope-bookmarks:mutate').disabled`),true);
|
||||
console.log('ADMIN_UI_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('ADMIN_UI_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-enterprise-admin-smoke-'));
|
||||
let store,server,icap,child;const exchanges=[];
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const original=store.get('policy');original.browser.allowDevTools=true;original.browser.allowWebSockets=true;store.put('policy',original);
|
||||
const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false};
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
icap=createServer(socket=>{let received=Buffer.alloc(0);socket.on('data',chunk=>{received=Buffer.concat([received,chunk]);if(received.length>16384){socket.destroy();return;}if(received.includes('\r\n\r\n')){exchanges.push(received.toString('ascii'));socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nISTag: "synthetic-test-service"\r\nAllow: 204\r\nEncapsulated: null-body=0\r\n\r\n');}});}).listen(0,'127.0.0.1');await new Promise(r=>icap.once('listening',r));
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_ADMIN_SMOKE_ICAP:`icap://127.0.0.1:${icap.address().port}/scan`};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/enterprise-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('ADMIN_UI_PROOF_OK'))console.log('Administrator interface workflow passed.');});child.stderr.on('data',data=>{const message=String(data);if(message.includes('ADMIN_UI_PROOF_'))console.error(message.trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Administrator interface proof failed');
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(policy.browser.allowDevTools,false);assert.equal(policy.browser.allowWebSockets,false);assert.deepEqual(policy.enterprise.dns,{mode:'secure',templates:['https://dns.synthetic.example/dns-query']});assert.deepEqual(policy.enterprise.vpn,{required:true,interfaceNames:['Synthetic Corporate VPN'],requiredDnsServers:['10.77.0.53']});assert.equal(policy.enterprise.icap.enabled,true);assert.equal(policy.enterprise.icap.url,env.SAFETYNET_ADMIN_SMOKE_ICAP);assert.equal(exchanges.length,1);assert.ok(exchanges[0].startsWith('OPTIONS '));assert.equal(exchanges[0].split('\r\n\r\n')[1],'');
|
||||
assert.equal(policy.diagnostics.enabled,true);assert.equal(policy.diagnostics.mode,'debug');assert.equal(policy.diagnostics.endpoint,'https://collector.synthetic.example');assert.ok(policy.agents.scopes.includes('bookmarks:mutate'));
|
||||
const report={signedDiagnosticsUi:true,newAgentScopesUi:true,adminUi:true,isolatedControlPlane:true,previewDidNotPublish:true,explicitSignedPublication:true,integrationFormRoundtrip:true,optionsOnlyProbe:true,probeContentSent:false,cspPreserved:true,nodeUnavailable:true,hiddenCpuWindow:true,policyVersion:policy.version,groupedNavigation:true,draftChangeReview:true,reviewDoesNotPublish:true,fleetAndAuditFilters:true,consoleThemePersistence:true,mobileNoHorizontalOverflow:true,readOnlyRoleControls:true,sessionInventoryUi:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/enterprise-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();for(const service of [server,icap])if(service)await new Promise(r=>{service.closeAllConnections?.();service.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-enterprise-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
const {app,BrowserWindow,dialog,session}=require('electron');
|
||||
const fs=require('node:fs'),assert=require('node:assert/strict');
|
||||
app.setPath('userData',process.env.SAFETYNET_ENTERPRISE_PROFILE);app.setPath('sessionData',process.env.SAFETYNET_ENTERPRISE_PROFILE);app.disableHardwareAcceleration();
|
||||
let dialogs=0;dialog.showSaveDialog=async()=>{dialogs++;return {canceled:false,filePath:process.env.SAFETYNET_ENTERPRISE_FILE};};
|
||||
require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms)),deadline=setTimeout(()=>app.exit(1),110000);
|
||||
app.whenReady().then(async()=>{try{
|
||||
let win;for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}assert.ok(win);
|
||||
const shell=code=>win.webContents.executeJavaScript(code),state=async()=>(await shell('window.safetynet.state()')).data;
|
||||
const result=await shell(`window.safetynet.enroll(${JSON.stringify({server:process.env.SAFETYNET_ENTERPRISE_SERVER,code:process.env.SAFETYNET_ENTERPRISE_CODE})})`);assert.equal(result.ok,true);assert.equal(result.data.ready,true);
|
||||
const page=win.contentView.children.find(c=>c.webContents!==win.webContents).webContents;for(let i=0;i<100&&page.isLoading();i++)await wait(100);
|
||||
const origin=process.env.SAFETYNET_ENTERPRISE_SINK,headers={authorization:`Bearer ${process.env.SAFETYNET_ENTERPRISE_ADMIN}`,'content-type':'application/json'};
|
||||
async function overview(){return (await fetch(process.env.SAFETYNET_ENTERPRISE_SERVER+'/api/admin/overview',{headers})).json();}
|
||||
async function publish(mutator){const p=(await overview()).policy;mutator(p);p.version++;const r=await fetch(process.env.SAFETYNET_ENTERPRISE_SERVER+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(p)});assert.equal(r.status,200);return p.version;}
|
||||
async function until(fn,ms=20000){const end=Date.now()+ms;while(Date.now()<end){if(await fn())return;await wait(100);}throw Error('Enterprise fixture deadline exceeded');}
|
||||
async function post(text){return page.executeJavaScript(`fetch('/receiver',{method:'POST',body:${JSON.stringify(text)}}).then(r=>r.ok).catch(()=>false)`);}
|
||||
assert.equal(await post('public ordinary upload'),true);assert.equal(await post('PUBLIC_MALWARE_FIXTURE'),false);
|
||||
assert.equal((await fetch(origin+'/proof-stats').then(r=>r.json())).posts,1);
|
||||
await page.executeJavaScript("(()=>{const a=document.createElement('a');a.href='/download/blocked';document.body.append(a);a.click();})()");
|
||||
await until(async()=> (await overview()).events.some(e=>e.kind==='download.block'&&e.metadata.ruleId?.includes('icap')));assert.equal(dialogs,0);assert.equal(fs.existsSync(process.env.SAFETYNET_ENTERPRISE_FILE),false);
|
||||
await page.executeJavaScript("(()=>{const a=document.createElement('a');a.href='/download/clean';document.body.append(a);a.click();})()");await until(()=>fs.existsSync(process.env.SAFETYNET_ENTERPRISE_FILE));assert.equal(dialogs,1);assert.deepEqual(fs.readFileSync(process.env.SAFETYNET_ENTERPRISE_FILE),Buffer.from([0,1,2,3,65,66,67,255]));
|
||||
const pausedVersion=await publish(p=>p.enterprise.vpn={required:true,interfaceNames:['SafetyNet nonexistent proof interface'],requiredDnsServers:[]});await until(async()=>{const s=await state();return s.policyVersion===pausedVersion&&!s.ready;});
|
||||
const baseline=(await fetch(origin+'/proof-stats').then(r=>r.json())).websiteRequests;assert.equal(await post('public blocked by VPN gate'),false);await wait(500);assert.equal((await fetch(origin+'/proof-stats').then(r=>r.json())).websiteRequests,baseline);
|
||||
const resumedVersion=await publish(p=>p.enterprise.vpn.required=false);await until(async()=>{const s=await state();return s.policyVersion===resumedVersion&&s.ready;});
|
||||
assert.equal(await session.fromPartition('persist:safetynet-management').resolveProxy(origin),await session.fromPartition('persist:safetynet-managed').resolveProxy(origin));
|
||||
console.log(JSON.stringify({nativeEnterprise:true,icapUploadAllow:true,icapUploadBlock:true,blockedDownloadDialogAbsent:true,cleanDownloadBytesVerified:true,requiredVpnBlocksWebsiteRequests:true,remotePolicyRecovery:true,managementProxyParity:true}));clearTimeout(deadline);app.quit();
|
||||
}catch(error){console.error(JSON.stringify({nativeEnterprise:false,error:error.message}));clearTimeout(deadline);app.exit(1);}});
|
||||
@@ -0,0 +1,24 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {createRequire} from 'node:module';
|
||||
import {createServer} from 'node:http';
|
||||
import net from 'node:net';
|
||||
import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} 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';
|
||||
const electron=createRequire(import.meta.url)('electron'),temporary=mkdtempSync(join(tmpdir(),'safetynet-enterprise-proof-'));
|
||||
const marker='PUBLIC_MALWARE_FIXTURE',clean=Buffer.from([0,1,2,3,65,66,67,255]);
|
||||
const report={local:true,icapScans:0,icapBlocks:0,posts:0,websiteRequests:0};
|
||||
const sockets=new Set();let icap,sink,management,store,child;
|
||||
try{
|
||||
icap=net.createServer(socket=>{sockets.add(socket);socket.on('error',()=>{});socket.on('close',()=>sockets.delete(socket));let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options)return socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nISTag: "local-fixture"\r\nAllow: 204\r\nEncapsulated: null-body=0\r\n\r\n');report.icapScans++;if(input.includes(Buffer.from(marker))){report.icapBlocks++;socket.end('ICAP/1.0 200 OK\r\nISTag: "local-fixture"\r\nEncapsulated: res-hdr=0, res-body=99\r\n\r\nHTTP/1.1 403 Forbidden');}else socket.end('ICAP/1.0 204 No Content\r\nISTag: "local-fixture"\r\n\r\n');});}).listen(0,'127.0.0.1');await new Promise(r=>icap.once('listening',r));
|
||||
sink=createServer((req,res)=>{if(req.url==='/proof-stats'){res.setHeader('content-type','application/json');return res.end(JSON.stringify(report));}report.websiteRequests++;if(req.method==='POST'){const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{report.posts++;assert.equal(Buffer.concat(chunks).includes(Buffer.from(marker)),false,'Blocked fixture reached receiver');res.end('accepted');});return;}if(req.url.startsWith('/download/')){const bytes=req.url.endsWith('/blocked')?Buffer.from(marker):clean;res.setHeader('content-disposition','attachment; filename="fixture.bin"');res.setHeader('content-type','application/octet-stream');res.setHeader('content-length',bytes.length);return res.end(bytes);}res.setHeader('content-type','text/html');res.end('<!doctype html><title>Enterprise native proof</title><h1>Public local fixture</h1>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');store=openStore(join(temporary,'store'));const p=store.get('policy');p.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;p.browser.allowedHosts=['127.0.0.1'];p.browser.allowDownloads=true;p.dlp.rules=[];p.security.yara.enabled=false;p.security.yara.rules=[];p.proxy.mode='system';p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${icap.address().port}/av`};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-enterprise-user');
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,requirePQManagement:true};management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ENTERPRISE_PROFILE:join(temporary,'browser'),SAFETYNET_ENTERPRISE_SERVER:config.publicUrl,SAFETYNET_ENTERPRISE_CODE:code,SAFETYNET_ENTERPRISE_ADMIN:adminToken,SAFETYNET_ENTERPRISE_SINK:p.browser.homeUrl,SAFETYNET_ENTERPRISE_FILE:join(temporary,'released.bin'),SAFETYNET_SMOKE_USERDATA:join(temporary,'browser')};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(electron,['scripts/enterprise-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),120000);const status=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(status,0,'Native enterprise fixture failed');assert.equal(report.posts,1);assert.ok(report.icapBlocks>=2);Object.assign(report,{nativeBrowserPassed:true,icapOnlyUpload:true,icapBeforeDownloadDialog:true,requiredVpnPaused:true,remotePolicyRecovery:true,managementProtectedChannel:true,managementProxyParity:true});mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/enterprise-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{child?.kill();for(const socket of sockets)socket.destroy();for(const server of [management,sink,icap])if(server){server.closeAllConnections?.();await new Promise(r=>server.close(r));}store?.db.close();}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1');
|
||||
let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000);
|
||||
async function evaluate(source){return window.webContents.executeJavaScript(source,true);}
|
||||
async function waitFor(source){const until=Date.now()+12000;while(Date.now()<until){if(await evaluate(source))return;await new Promise(r=>setTimeout(r,75));}throw Error('Administrator UI state did not arrive: '+await evaluate(`document.getElementById('status').textContent`));}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);
|
||||
await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`);
|
||||
assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'});
|
||||
const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff');
|
||||
const reviewProof=process.env.SAFETYNET_GROUP_REVIEW_PROOF==='1';
|
||||
if(reviewProof){await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_GROUP_OPERATOR_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);}
|
||||
else {
|
||||
await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);}
|
||||
|
||||
await evaluate(`document.getElementById('group-id').value='engineering';document.getElementById('group-name').value='Engineering';for(const input of document.querySelectorAll('#group-members input'))input.checked=['pilot-device','member-device'].includes(input.value);document.getElementById('group-members').dispatchEvent(new Event('change'));for(const input of document.querySelectorAll('#group-cohort input'))input.checked=input.value==='pilot-device';document.getElementById('group-disable-ai').checked=true;document.getElementById('group-blocked-hosts').value='restricted.synthetic.example';document.getElementById('group-save').click()`);
|
||||
if(reviewProof){
|
||||
await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');assert.equal(await evaluate(`document.querySelectorAll('#groups-rows tr').length`),0);
|
||||
await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_GROUP_REVIEWER_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.querySelector('#reviews-rows button')`);
|
||||
await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);
|
||||
const operation=await evaluate(`document.getElementById('independent-review-operation').textContent`);for(const value of ['engineering','pilot-device','member-device','disableAI','restricted.synthetic.example'])assert.ok(operation.includes(value),`Exact proposal missing ${value}`);
|
||||
await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'`);assert.equal(await evaluate(`document.querySelector('#groups-rows tr td:nth-child(3)').textContent.startsWith('pilot')`),true);
|
||||
console.log('GROUP_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);return;
|
||||
}
|
||||
await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`);
|
||||
assert.equal(await evaluate(`document.querySelector('#groups-rows tr td:nth-child(3)').textContent.startsWith('pilot')`),true);
|
||||
await evaluate(`document.getElementById('group-preview-device').value='pilot-device';document.getElementById('group-preview').click()`);await waitFor(`document.getElementById('group-preview-summary').textContent.includes('engineering')`);
|
||||
assert.equal(await evaluate(`document.getElementById('group-preview-diff').textContent.includes('/browser/blockedHosts')`),true);
|
||||
await evaluate(`document.getElementById('group-preview-device').value='outside-device';document.getElementById('group-preview').click()`);await waitFor(`document.getElementById('group-preview-summary').textContent.includes('baseline only')`);
|
||||
assert.equal(await evaluate(`document.getElementById('group-preview-diff').textContent.includes('restricted.synthetic.example')`),false);
|
||||
await evaluate(`document.getElementById('group-stage').value='all';document.getElementById('group-stage').dispatchEvent(new Event('change'));document.getElementById('group-save').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('published')`);
|
||||
assert.equal(await evaluate(`document.querySelector('#groups-rows tr td:nth-child(3)').textContent`),'All members');assert.equal(await evaluate(`document.getElementById('group-device-rows').textContent.includes('Awaiting target version')`),true);assert.equal(await evaluate(`globalThis.__proofErrors`),0);
|
||||
await evaluate(`document.getElementById('policy-groups').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/groups-admin.png'),(await window.webContents.capturePage()).toPNG());
|
||||
console.log('GROUP_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('GROUP_ADMIN_PROOF_FAILED',error.stack);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { digest } from '../apps/server/store.mjs';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-groups-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:process.env.SAFETYNET_GROUP_REVIEW_PROOF==='1'};
|
||||
for(const id of ['pilot-device','member-device','outside-device'])store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run(id,id,'win32','synthetic-user',`synthetic-${id}`,new Date().toISOString());
|
||||
const operatorToken=randomBytes(32).toString('base64url'),reviewerToken=randomBytes(32).toString('base64url');for(const [value,sub,role]of [[operatorToken,'group-operator','operator'],[reviewerToken,'group-reviewer','admin']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(value),Date.now()+60000,JSON.stringify({sub,role}));
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_GROUP_OPERATOR_TOKEN:operatorToken,SAFETYNET_GROUP_REVIEWER_TOKEN:reviewerToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/groups-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('GROUP_ADMIN_PROOF_OK'))console.log('Group administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('GROUP_ADMIN_PROOF_'))console.error(text.trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Group administrator interface proof failed');
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,config.requireIndependentPolicyApproval?2:3);const group=store.getRaw('policyGroups').find(group=>group.id==='engineering');assert.equal(group.rollout.stage,config.requireIndependentPolicyApproval?'pilot':'all');assert.deepEqual([...group.memberDeviceIds].sort(),['member-device','pilot-device']);assert.equal(group.restrictions.disableAI,true);
|
||||
const report={actualAdministratorUi:true,explicitGroupMembership:true,pilotCohortPublished:true,noNode:true,cspPreserved:true,...(config.requireIndependentPolicyApproval?{operatorProposalLeftPolicyUnchanged:true,exactMembersCohortRestrictionsReviewed:true,distinctAdministratorApproval:true}:{effectivePreview:true,explicitExpansion:true,reportedVersionDisplayed:true})};mkdirSync('artifacts',{recursive:true});writeFileSync(config.requireIndependentPolicyApproval?'artifacts/groups-review-admin-proof.json':'artifacts/groups-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-groups-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
// Isolated feasibility harness: never loaded by the managed browser.
|
||||
const fs=require('node:fs'),os=require('node:os'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto');
|
||||
if(!process.versions.electron){
|
||||
const {spawn}=require('node:child_process');const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;
|
||||
const child=spawn(require('electron'),[__filename],{env,windowsHide:true,stdio:'inherit'});const timer=setTimeout(()=>child.kill(),60000);child.once('exit',code=>{clearTimeout(timer);process.exit(code??1);});
|
||||
}else{
|
||||
const {app,BrowserWindow,session}=require('electron');app.disableHardwareAcceleration();app.enableSandbox();
|
||||
const owned=fs.mkdtempSync(path.join(os.tmpdir(),'safetynet-upload-probe-'));app.setPath('userData',owned);
|
||||
const {createServer}=require('node:http');let server,win;const report={electron:process.versions.electron,productionEnabled:false};
|
||||
app.whenReady().then(async()=>{
|
||||
const original=Buffer.from('PUBLIC IMMUTABLE FILE CONTENT'),changed=Buffer.from('SECRET MUTATED FILE CONTENT!!');const file=path.join(owned,'fixture.txt');fs.writeFileSync(file,original);
|
||||
const received=[];server=createServer(async(req,res)=>{const chunks=[];for await(const chunk of req)chunks.push(chunk);received.push({url:req.url,headers:req.headers,bytes:Buffer.concat(chunks)});if(req.url==='/redirect'){res.writeHead(307,{location:'/redirect-target'});res.end();return;}res.setHeader('content-type','text/html');res.end('<input id="file" type="file"><script>async function upload(){let f=new FormData();f.append("document",document.querySelector("input").files[0]);return fetch("/upload",{method:"POST",body:f}).then(r=>r.text());}</script>');}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));
|
||||
const origin=`http://127.0.0.1:${server.address().port}`,ses=session.fromPartition('upload-proof');let snapshot;
|
||||
ses.protocol.handle('http',async request=>{
|
||||
if(new URL(request.url).origin!==origin)return new Response('Denied',{status:403});
|
||||
const body=request.body?Buffer.from(await request.arrayBuffer()):undefined;
|
||||
if(request.url.includes('/cookie-')){report.requestCredentials??={};report.requestCredentials[new URL(request.url).pathname]=request.credentials;report.requestCookiePresence??={};report.requestCookiePresence[new URL(request.url).pathname]=request.headers.has('cookie');}
|
||||
if(request.method==='POST'){snapshot=body;report.requestBodyAvailable=Boolean(body?.length);report.initiatorOrigin=request.initiatorOrigin||null;fs.writeFileSync(file,changed);report.mutatedAfterSnapshot=true;}
|
||||
try{return await ses.fetch(request.url,{method:request.method,headers:request.headers,body:body?new Blob([body]):undefined,redirect:'error',bypassCustomProtocolHandlers:true});}catch{return Response.error();}
|
||||
});
|
||||
win=new BrowserWindow({show:false,webPreferences:{session:ses,sandbox:true,contextIsolation:true,nodeIntegration:false}});await win.loadURL(origin);
|
||||
win.webContents.debugger.attach('1.3');const {root}=await win.webContents.debugger.sendCommand('DOM.getDocument');const {nodeId}=await win.webContents.debugger.sendCommand('DOM.querySelector',{nodeId:root.nodeId,selector:'#file'});await win.webContents.debugger.sendCommand('DOM.setFileInputFiles',{nodeId,files:[file]});
|
||||
await win.webContents.executeJavaScript('upload()');const upload=received.find(r=>r.url==='/upload');assert(upload);assert(snapshot);assert.deepEqual(upload.bytes,snapshot);assert(upload.bytes.includes(original));assert(!upload.bytes.includes(changed));report.receiverSha256=crypto.createHash('sha256').update(upload.bytes).digest('hex');report.snapshotSha256=crypto.createHash('sha256').update(snapshot).digest('hex');report.exactBytes=true;
|
||||
await ses.cookies.set({url:origin,name:'synthetic_session',value:'fixture',sameSite:'strict'});
|
||||
await win.webContents.executeJavaScript('fetch("/cookie-include",{credentials:"include"}).then(r=>r.text())');
|
||||
await win.webContents.executeJavaScript('fetch("/cookie-omit",{credentials:"omit"}).then(r=>r.text())');
|
||||
report.includeCookie=Boolean(received.find(r=>r.url==='/cookie-include').headers.cookie);
|
||||
report.omitCookie=Boolean(received.find(r=>r.url==='/cookie-omit').headers.cookie);
|
||||
report.credentialsPreserved=report.includeCookie&&!report.omitCookie;
|
||||
// A known unsafe expansion is asserted here so the evidence cannot be mistaken
|
||||
// for approval to ship the transparent bridge. No real credentials are used.
|
||||
assert.equal(report.credentialsPreserved,false,'Reassess Electron credential behavior before integration');
|
||||
report.redirectDenied=await win.webContents.executeJavaScript('fetch("/redirect").then(()=>false,()=>true)');
|
||||
assert.equal(report.redirectDenied,true);assert.equal(received.some(r=>r.url==='/redirect-target'),false);
|
||||
fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync('artifacts/immutable-upload-probe.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}).catch(error=>{console.error(error);process.exitCode=1;}).finally(async()=>{win?.destroy();if(server){server.closeAllConnections();await new Promise(r=>server.close(r));}app.exit(process.exitCode||0);});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);let window;const deadline=setTimeout(()=>app.exit(1),50000);
|
||||
const evaluate=source=>window.webContents.executeJavaScript(source,true);
|
||||
async function waitFor(source){const until=Date.now()+12000;while(Date.now()<until){if(await evaluate(source))return;await new Promise(r=>setTimeout(r,75));}throw Error('Incident UI state missing: '+await evaluate(`document.getElementById('status').textContent`));}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);
|
||||
assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require})`),{node:'undefined',require:'undefined'});assert.ok((await evaluate(`fetch(location.href).then(r=>r.headers.get('content-security-policy'))`)).includes("script-src 'self'"));
|
||||
await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`document.getElementById('status').textContent.includes('Connected')`);
|
||||
await evaluate(`document.querySelector('[data-incident-create]').click()`);await waitFor(`!document.getElementById('incident-detail').hidden`);assert.equal(await evaluate(`document.getElementById('incident-event').textContent.includes('NEVER_SHOW_DOCUMENT_CONTENT')`),false);
|
||||
await evaluate(`document.getElementById('incident-state').value='investigating';document.getElementById('incident-severity').value='high';document.getElementById('incident-assignee').value='Synthetic SOC';document.getElementById('incident-comment').value='Synthetic investigation started. <img src=x onerror=alert(1)>';document.getElementById('incident-save').click()`);await waitFor(`document.getElementById('incident-revision').textContent.includes('revision 2')`);
|
||||
assert.ok(await evaluate(`document.getElementById('incident-history').textContent.includes('Synthetic investigation started. <img src=x onerror=alert(1)>')`));assert.equal(await evaluate(`document.querySelectorAll('#incident-history img').length`),0);
|
||||
const id=await evaluate(`document.querySelector('#incident-rows tr').dataset.incidentId`);
|
||||
await evaluate(`fetch('/api/admin/incidents/'+${JSON.stringify(id)},{method:'PATCH',headers:{authorization:'Bearer '+${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)},'content-type':'application/json'},body:JSON.stringify({expectedRevision:2,status:'resolved'})}).then(r=>{if(!r.ok)throw Error('Concurrent synthetic update failed')})`);
|
||||
await evaluate(`document.getElementById('incident-comment').value='Preserved concurrent edit';document.getElementById('incident-save').click()`);await waitFor(`document.getElementById('incident-detail-status').textContent.includes('revision conflict')`);assert.equal(await evaluate(`document.getElementById('incident-comment').value`),'Preserved concurrent edit');
|
||||
await evaluate(`document.getElementById('incident-detail-refresh').click()`);await waitFor(`document.getElementById('incident-revision').textContent.includes('revision 3')`);
|
||||
await evaluate(`document.getElementById('incident-status-filter').value='open';document.getElementById('incident-status-filter').dispatchEvent(new Event('change'))`);await waitFor(`document.querySelectorAll('#incident-rows tr').length===0`);
|
||||
await evaluate(`document.getElementById('incident-status-filter').value='resolved';document.getElementById('incident-status-filter').dispatchEvent(new Event('change'))`);await waitFor(`document.querySelectorAll('#incident-rows tr').length===1`);
|
||||
await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_INCIDENT_AUDITOR_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`document.getElementById('status').textContent.includes('Connected')`);await evaluate(`document.querySelector('#incident-rows button').click()`);await waitFor(`!document.getElementById('incident-detail').hidden`);assert.equal(await evaluate(`document.getElementById('incident-save').disabled`),true);assert.equal(await evaluate(`document.querySelector('[data-incident-create]').disabled`),true);
|
||||
const forbidden=await evaluate(`fetch('/api/admin/incidents/'+${JSON.stringify(id)},{method:'PATCH',headers:{'content-type':'application/json'},body:JSON.stringify({expectedRevision:3,status:'open'})}).then(r=>r.status)`);assert.equal(forbidden,403);
|
||||
await evaluate(`document.getElementById('incidents').scrollIntoView()`);await new Promise(r=>setTimeout(r,250));fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync(path.resolve('artifacts/incident-admin.png'),(await window.webContents.capturePage()).toPNG());console.log('INCIDENT_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('INCIDENT_ADMIN_PROOF_FAILED',error.stack);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { digest } from '../apps/server/store.mjs';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-incident-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false};
|
||||
store.event('synthetic-device','dlp.blocked',{decision:'block',ruleId:'synthetic-rule',policyVersion:1,prompt:'NEVER_SHOW_DOCUMENT_CONTENT'});
|
||||
const auditorToken=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(auditorToken),Date.now()+60000,JSON.stringify({sub:'incident-auditor',role:'auditor'}));
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_INCIDENT_AUDITOR_TOKEN:auditorToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/incident-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('INCIDENT_ADMIN_PROOF_OK'))console.log('Incident administrator interface workflow passed.');});child.stderr.on('data',data=>{if(String(data).includes('INCIDENT_ADMIN_PROOF_'))console.error(String(data).trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Incident administrator interface proof failed');
|
||||
const row=store.db.prepare('SELECT * FROM incident_cases').get();assert.equal(row.revision,3);assert.equal(row.status,'resolved');assert.equal(row.assignee,'Synthetic SOC');
|
||||
const report={actualAdministratorUi:true,createFromSecurityEvent:true,statusSeverityAssignment:true,revisionConflictPreservesEdits:true,historyShown:true,adversarialNotesRenderedAsText:true,filters:true,auditorReadOnly:true,rawContentExcluded:true,noNode:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/incident-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-incident-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow,session}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);
|
||||
let window;const deadline=setTimeout(()=>app.exit(1),55000);const evaluate=s=>window.webContents.executeJavaScript(s,true);
|
||||
async function waitFor(s){for(let n=0;n<200;n++){if(await evaluate(s))return;await new Promise(r=>setTimeout(r,60));}throw Error('UI state timeout: '+s+' | '+await evaluate(`document.getElementById('status').textContent`));}
|
||||
async function identity(value){await session.defaultSession.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value,httpOnly:true,sameSite:'strict'});await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('reviews-status').textContent.includes('loaded')`);}
|
||||
async function prepare(name){await evaluate(`document.getElementById('status').textContent='Preparing';document.getElementById('brand-name').value=${JSON.stringify(name)};document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
await identity('synthetic-review-author');assert.equal(await evaluate('typeof require'),'undefined');
|
||||
assert.equal(await evaluate(`document.getElementById('publish').textContent`),'Prepare for independent approval');
|
||||
await prepare('Independently approved');await prepare('Stale candidate');await prepare('Rejected candidate');
|
||||
assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');
|
||||
await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);
|
||||
assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);
|
||||
const self=await evaluate(`fetch('/api/admin/policy-reviews/'+selectedIndependentReview.id+'/approve',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({payloadHash:selectedIndependentReview.payloadHash,expectedVersion:selectedIndependentReview.expectedVersion,approve:true})}).then(r=>r.status)`);assert.equal(self,403);
|
||||
await identity('synthetic-review-admin');
|
||||
await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);
|
||||
assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('Rejected candidate'));
|
||||
await evaluate(`document.getElementById('independent-review-reject').click()`);await waitFor(`document.getElementById('reviews-rows').textContent.includes('rejected')`);
|
||||
await evaluate(`document.querySelectorAll('#reviews-rows button')[2].click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);
|
||||
assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('Independently approved'));
|
||||
await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'`);
|
||||
assert.ok((await evaluate(`document.getElementById('reviews-rows').textContent`)).includes('stale'));
|
||||
await evaluate(`document.querySelectorAll('#reviews-rows button')[1].click()`);await waitFor(`document.getElementById('independent-review-title').textContent.includes('stale')`);assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);
|
||||
await evaluate(`document.getElementById('policy-reviews').scrollIntoView();document.getElementById('console-theme').value='dark';document.getElementById('console-theme').dispatchEvent(new Event('change'))`);await new Promise(r=>setTimeout(r,200));fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync(path.resolve('artifacts/independent-review-admin.png'),(await window.webContents.capturePage()).toPNG());
|
||||
await identity('synthetic-review-operator');assert.equal(await evaluate(`document.getElementById('publish').disabled`),false);await prepare('Operator proposed');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);
|
||||
|
||||
await evaluate(`document.getElementById('status').textContent='Preparing inspection';Array.from(document.querySelectorAll('#device-rows button')).find(button=>button.textContent==='Enable manual document scans').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);
|
||||
assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');assert.ok(await evaluate(`Array.from(document.querySelectorAll('#device-rows button')).some(button=>button.textContent==='Enable manual document scans')`));
|
||||
await identity('synthetic-review-admin');await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);
|
||||
const operation=await evaluate(`document.getElementById('independent-review-operation').textContent`);assert.ok(operation.includes('synthetic-bookmark-device'));assert.ok(operation.includes('Enable'));assert.ok(operation.includes('Mandatory upload DLP'));
|
||||
await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'`);assert.ok(await evaluate(`Array.from(document.querySelectorAll('#device-rows button')).some(button=>button.textContent==='Disable manual document scans')`));
|
||||
console.log('INDEPENDENT_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('INDEPENDENT_ADMIN_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -0,0 +1,28 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { openStore, digest } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-independent-review-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:true};
|
||||
for(const [credential,role,sub] of [['synthetic-review-author','admin','synthetic-author'],['synthetic-review-admin','admin','synthetic-reviewer'],['synthetic-review-operator','operator','synthetic-operator']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+3600000,JSON.stringify({sub,role}));
|
||||
store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('synthetic-bookmark-device','Synthetic bookmark device','win32','synthetic-user','synthetic-unused',new Date().toISOString());
|
||||
store.db.prepare('UPDATE devices SET document_analysis_enabled=0 WHERE id=?').run('synthetic-bookmark-device');
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/independent-review-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('INDEPENDENT_ADMIN_PROOF_OK'))console.log('Independent policy approval administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('INDEPENDENT_ADMIN_PROOF_'))console.error(text.trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Independent policy approval administrator interface proof failed');
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(store.db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get('synthetic-bookmark-device').document_analysis_enabled,1);assert.equal(policy.brand.name,'Independently approved');
|
||||
const report={actualAdministratorUi:true,mandatoryIndependentApproval:true,payloadDiff:true,selfApprovalHttpDenied:true,distinctReviewer:true,operatorPrepareOnly:true,inspectionOperationVisible:true,inspectionUnchangedUntilApproval:true,rejectedAndStaleVisible:true,publishedOnlyAfterApproval:true,nodeUnavailable:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/independent-review-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-independent-review-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
if(existsSync('.env'))console.log('.env already exists; preserved existing configuration.');
|
||||
else {const template=readFileSync('.env.example','utf8');writeFileSync('.env',template.replace('ADMIN_TOKEN=','ADMIN_TOKEN='+randomBytes(32).toString('base64url')),{mode:0o600});console.log('Created .env with a random bootstrap administrator token. Open .env locally to use it; never commit this file.');}
|
||||
let existing=readFileSync('.env','utf8');
|
||||
if(!/^INSPECTOR_URL=/m.test(existing)){appendFileSync('.env','\nINSPECTOR_URL=http://127.0.0.1:4311\n');existing=readFileSync('.env','utf8');}
|
||||
if(!/^INSPECTOR_TOKEN=.+/m.test(existing)){
|
||||
const secret=randomBytes(32).toString('base64url');
|
||||
if(/^INSPECTOR_TOKEN=/m.test(existing))writeFileSync('.env',existing.replace(/^INSPECTOR_TOKEN=.*$/m,'INSPECTOR_TOKEN='+secret),{mode:0o600});
|
||||
else appendFileSync('.env','INSPECTOR_TOKEN='+secret+'\n',{mode:0o600});
|
||||
console.log('Configured a private inspector credential.');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
import {createRequire} from 'node:module';
|
||||
const files=createRequire(import.meta.url)('../apps/browser/inspection.cjs').privateInspectorPaths(resolve('.'));
|
||||
if(!files)throw Error('Provision the private Windows runtime; external or linked inspector overrides are rejected');
|
||||
const {python,script}=files;
|
||||
const environment={...process.env,INSPECTOR_PARENT_PID:String(process.pid)};
|
||||
environment.EMBEDDING_MODEL_DIR ||= resolve('.runtime/windows/semantic/models');
|
||||
environment.EMBEDDING_WORKER_PATH ||= resolve('.runtime/windows/semantic/searchi-gemma-worker.exe');
|
||||
for(const key of ['ADMIN_TOKEN','OIDC_CLIENT_SECRET','AI_API_KEY','SIEM_HTTP_TOKEN','SIEM_KEY_FILE'])delete environment[key];
|
||||
const child=spawn(python,[script],{stdio:'inherit',env:environment,windowsHide:true});
|
||||
process.on('SIGINT',()=>child.kill());process.on('SIGTERM',()=>child.kill());child.on('exit',code=>process.exit(code??1));
|
||||
@@ -0,0 +1,26 @@
|
||||
const {app,BrowserWindow,safeStorage}=require('electron');
|
||||
const fs=require('node:fs'),path=require('node:path');
|
||||
const assert=require('node:assert/strict');
|
||||
app.setPath('userData',process.env.SAFETYNET_SETTINGS_PROFILE);app.setPath('sessionData',process.env.SAFETYNET_SETTINGS_PROFILE);app.disableHardwareAcceleration();
|
||||
process.env.SAFETYNET_SMOKE_USERDATA=process.env.SAFETYNET_SETTINGS_PROFILE;
|
||||
require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
const deadline=setTimeout(()=>app.exit(1),90000);
|
||||
app.whenReady().then(async()=>{try{
|
||||
let win;for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}assert.ok(win,'Trusted shell unavailable');
|
||||
const call=(name,input)=>win.webContents.executeJavaScript(`window.safetynet[${JSON.stringify(name)}](${JSON.stringify(input)??''})`);
|
||||
const enrolled=await call('enroll',{server:process.env.SAFETYNET_SETTINGS_SERVER,code:process.env.SAFETYNET_SETTINGS_CODE});assert.equal(enrolled.ok,true,'Enrollment failed');assert.equal(enrolled.data.ready,true,'Management did not become ready');
|
||||
const enrollment=JSON.parse(safeStorage.decryptString(Buffer.from(JSON.parse(fs.readFileSync(path.join(process.env.SAFETYNET_SETTINGS_PROFILE,'enrollment.json'),'utf8')).encryptedEnrollment,'base64')));
|
||||
assert.equal((await fetch(`${process.env.SAFETYNET_SETTINGS_SERVER}/api/device/policy`,{headers:{authorization:`Bearer ${enrollment.token}`}})).status,403,'Plaintext policy route was not rejected');
|
||||
async function state(){return (await call('state')).data;}
|
||||
async function rejected(name,input){assert.equal((await call(name,input)).ok,false,`Privileged ${name} bypassed administrator settings`);}
|
||||
assert.deepEqual((await state()).settings.appearance,{mode:'dark',accent:'violet',tabMode:'compact'});
|
||||
await rejected('updateSettings',{mode:'light'});await rejected('vaultStatus');await rejected('vaultCreate','synthetic-not-a-real-password');await rejected('vaultSave',{});await rejected('inspectFile');await rejected('agentConnection');assert.equal((await call('vaultLock')).ok,true);
|
||||
const headers={authorization:`Bearer ${process.env.SAFETYNET_SETTINGS_ADMIN}`,'content-type':'application/json'};
|
||||
async function publish(settings){const overview=await (await fetch(`${process.env.SAFETYNET_SETTINGS_SERVER}/api/admin/overview`,{headers})).json();const policy=overview.policy;assert.ok(policy,'Admin policy unavailable');policy.version++;policy.browser.settings=settings;const response=await fetch(`${process.env.SAFETYNET_SETTINGS_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(policy)});assert.equal(response.status,200,'Central policy publication failed');for(let i=0;i<100;i++){const current=await state();if(current.ready&¤t.policyVersion===policy.version)return current;await wait(100);}throw Error('Live central settings update timed out');}
|
||||
await publish({visible:false,hiddenSections:[],lockedSections:[],appearance:{mode:'light',accent:'teal',tabMode:'separate'}});
|
||||
await rejected('setChromeLayout',{height:104,sidebarOpen:false,startPageOpen:false,overviewOpen:false,toolsOpen:true});await rejected('updateSettings',{mode:'dark'});assert.equal((await call('toggleAI',true)).ok,true,'Hiding settings disabled approved AI use');await call('toggleAI',false);assert.equal((await call('vaultLock')).ok,true);
|
||||
const opened=await publish({visible:true,hiddenSections:[],lockedSections:[],appearance:{mode:'system',accent:'blue',tabMode:'separate'}});assert.equal(opened.settings.visible,true);const change=await call('updateSettings',{mode:'dark',accent:'teal',tabMode:'compact'});assert.equal(change.ok,true);assert.deepEqual(change.data.appearance,{mode:'dark',accent:'teal',tabMode:'compact'});
|
||||
const ui=await win.webContents.executeJavaScript('({theme:document.documentElement.dataset.theme,accent:document.documentElement.dataset.accent,tabMode:document.documentElement.dataset.tabMode})');assert.deepEqual(ui,{theme:'dark',accent:'teal',tabMode:'compact'});
|
||||
console.log(JSON.stringify({nativeSettingsEnforcement:true,liveCentralUpdate:true,directIPCBypassRejected:true}));clearTimeout(deadline);app.quit();
|
||||
}catch(error){console.error(JSON.stringify({nativeSettingsEnforcement:false,error:error.message}));clearTimeout(deadline);app.exit(1);}});
|
||||
@@ -0,0 +1,31 @@
|
||||
// Actual signed central publication and native browser IPC, isolated synthetic device.
|
||||
import assert from 'node:assert/strict';
|
||||
import {createRequire} from 'node:module';
|
||||
import {createServer} from 'node:http';
|
||||
import {mkdtempSync,mkdirSync,writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} 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';
|
||||
const electron=createRequire(import.meta.url)('electron');
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-managed-settings-'));
|
||||
let store,management,sink,child;
|
||||
try{
|
||||
const adminToken=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url');
|
||||
sink=createServer((_req,res)=>{res.setHeader('content-type','text/html');res.end('<!doctype html><title>Managed settings fixture</title><h1>Public local test page</h1>');}).listen(0,'127.0.0.1');
|
||||
await new Promise(resolve=>sink.once('listening',resolve));
|
||||
store=openStore(join(temporary,'management.sqlite'));
|
||||
const initial=store.get('policy');initial.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;initial.browser.allowedHosts=['127.0.0.1'];initial.browser.settings={visible:true,hiddenSections:['passwords','documentScan','agents'],lockedSections:['appearance'],appearance:{mode:'dark',accent:'violet',tabMode:'compact'}};store.put('policy',initial);
|
||||
store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-settings-user');
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,requirePQManagement:true};
|
||||
management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>management.once('listening',resolve));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
const env={...process.env,SAFETYNET_SETTINGS_PROFILE:join(temporary,'browser'),SAFETYNET_SETTINGS_SERVER:config.publicUrl,SAFETYNET_SETTINGS_CODE:code,SAFETYNET_SETTINGS_ADMIN:adminToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(electron,['scripts/managed-settings-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),100000);
|
||||
const status=await new Promise(resolve=>{child.once('exit',code=>resolve(code??1));child.once('error',()=>resolve(1));});clearTimeout(timer);assert.equal(status,0,'Native managed settings proof failed');
|
||||
const report={native:true,centralSignedPublication:true,plaintextPolicyRejected:true,lockedAppearanceDirectIPCRejected:true,hiddenSectionsDirectIPCRejected:true,globalSettingsHiddenRejected:true,liveUnlockApplied:true,vaultLockAlwaysAvailable:true,contentPersisted:false};
|
||||
mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/managed-settings-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}store?.db.close();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
const {app,BrowserWindow,webContents}=require('electron');const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path');
|
||||
app.setPath('userData',process.env.SAFETYNET_TABS_USERDATA);app.disableHardwareAcceleration();require('../apps/browser/main.cjs');
|
||||
function finish(code){for(const contents of webContents.getAllWebContents()){if(contents.getURL().endsWith('index.html'))continue;require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(contents);}app.exit(code);}
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn,label){for(let i=0;i<150;i++){if(await fn())return;await wait(100);}throw Error(label+' timed out');}
|
||||
app.whenReady().then(async()=>{try{
|
||||
let win;await until(()=>{win=BrowserWindow.getAllWindows()[0];return win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html');},'Shell');
|
||||
const run=code=>win.webContents.executeJavaScript(code),state=async()=>{const r=await run('window.safetynet.state()');assert.equal(r.ok,true,r.error);return r.data;};
|
||||
const invoke=async(method,value)=>{const r=await run(`window.safetynet[${JSON.stringify(method)}](${JSON.stringify(value)})`);assert.equal(r.ok,true,r.error);return r.data;};
|
||||
await invoke('enroll',{server:process.env.SAFETYNET_TABS_SERVER,code:process.env.SAFETYNET_TABS_CODE});const base=process.env.SAFETYNET_TABS_SINK;
|
||||
await until(async()=>{const s=await state();return s.tabs?.length===1&&s.tabs[0].url===base+'/';},'Initial managed native tab');
|
||||
const aId=(await state()).activeTabId;const a=webContents.getAllWebContents().find(w=>w.getURL()===base+'/');assert.ok(a);await until(()=>!a.isLoading(),'Initial page');
|
||||
assert.deepEqual(await a.executeJavaScript('({node:typeof require,bridge:typeof window.safetynet})'),{node:'undefined',bridge:'undefined'});
|
||||
await a.executeJavaScript("document.getElementById('remember').value='A survives';window.scrollTo(0,800)");
|
||||
const before=(await (await fetch(base+'/counts')).json()).requests['/'];
|
||||
await run("document.getElementById('new-tab').click()");await until(async()=>{const s=await state();return s.tabs.length===2&&s.activeTabId!==aId;},'New tab UI');
|
||||
const bId=(await state()).activeTabId;assert.equal((await state()).tabs.find(t=>t.id===bId).startPage,true);
|
||||
await run(`document.getElementById('address').value=${JSON.stringify(base+'/b')};document.getElementById('address-form').requestSubmit()`);
|
||||
await until(async()=>{const s=await state();return s.tabs.find(t=>t.id===bId)?.url===base+'/b';},'Second native page');const b=webContents.getAllWebContents().find(w=>w.getURL()===base+'/b');assert.ok(b);await until(()=>!b.isLoading(),'Second page');assert.notEqual(a.id,b.id);
|
||||
await b.executeJavaScript("document.getElementById('remember').value='B survives'");
|
||||
const clickTab=id=>run(`(()=>{const element=[...document.querySelectorAll('#tab-strip .browser-tab')].find(el=>el.dataset.tabId===${JSON.stringify(id)});if(!element)throw Error('Tab UI missing');element.querySelector('.tab-label').click()})()`);
|
||||
await clickTab(aId);await until(async()=>(await state()).activeTabId===aId,'Switch A');assert.equal(await a.executeJavaScript("document.getElementById('remember').value"),'A survives');assert.equal(await a.executeJavaScript('Math.round(scrollY)'),800);assert.equal((await (await fetch(base+'/counts')).json()).requests['/'],before,'Selecting a tab reloaded it');
|
||||
await a.executeJavaScript("document.getElementById('next').click()",true);await until(()=>a.getURL()===base+'/a-two'&&!a.isLoading(),'A history');await invoke('back');await until(()=>a.getURL()===base+'/'&&!a.isLoading(),'A back');
|
||||
await clickTab(bId);await until(async()=>(await state()).activeTabId===bId,'Switch B');assert.equal(await b.executeJavaScript("document.getElementById('remember').value"),'B survives');await b.executeJavaScript("document.getElementById('next').click()",true);await until(()=>b.getURL()===base+'/b-two'&&!b.isLoading(),'B history');await invoke('back');await until(()=>b.getURL()===base+'/b'&&!b.isLoading(),'B back');assert.equal(a.getURL(),base+'/','B history changed A');
|
||||
for(const page of [a,b]){const blocked=await page.executeJavaScript(`fetch(${JSON.stringify(base+'/blocked')},{method:'POST',body:'TAB_FIXTURE_SECRET'}).then(()=>false).catch(()=>true)`);assert.equal(blocked,true,'Inactive/active tab DLP bypass');assert.deepEqual(await page.executeJavaScript('({node:typeof require,bridge:typeof window.safetynet})'),{node:'undefined',bridge:'undefined'});assert.equal(await page.executeJavaScript('navigator.clipboard.writeText("TAB_FIXTURE_SECRET").then(()=>false).catch(()=>true)'),true,'Site clipboard bypass');}
|
||||
fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync('artifacts/native-tabs.png',(await win.webContents.capturePage()).toPNG());
|
||||
await invoke('closeTab',aId);assert.equal(a.isDestroyed(),true);await invoke('closeTab',bId);assert.equal(b.isDestroyed(),true);const final=await state();assert.equal(final.tabs.length,1);assert.equal(final.tabs[0].startPage,true);assert.notEqual(final.activeTabId,bId);await until(async()=>await run("!document.getElementById('start-page').hidden"),'Fresh start page');
|
||||
const fresh=webContents.getAllWebContents().find(w=>w!==win.webContents&&w.getURL()==='about:blank');assert.ok(fresh,'Closing last tab did not create native replacement');assert.deepEqual(await fresh.executeJavaScript('({node:typeof require,bridge:typeof window.safetynet})'),{node:'undefined',bridge:'undefined'});
|
||||
const revokedA=final.activeTabId;await invoke('navigate',{url:base+'/revoke-a',tabId:revokedA});await until(()=>webContents.getAllWebContents().some(w=>w.getURL()===base+'/revoke-a'&&!w.isLoading()),'Revocation A');const revokePageA=webContents.getAllWebContents().find(w=>w.getURL()===base+'/revoke-a');
|
||||
await invoke('createTab',{});const revokedB=(await state()).activeTabId;await invoke('navigate',{url:base+'/revoke-b',tabId:revokedB});await until(()=>webContents.getAllWebContents().some(w=>w.getURL()===base+'/revoke-b'&&!w.isLoading()),'Revocation B');const revokePageB=webContents.getAllWebContents().find(w=>w.getURL()===base+'/revoke-b');
|
||||
const collectorStatus=async()=>await (await fetch(process.env.SAFETYNET_TABS_COLLECTOR+'/status')).json();await until(async()=>{const status=await collectorStatus();return ['browser.ipc','browser.navigation','browser.dns','browser.request'].every(name=>status.names.includes(name))&&status.logs>0&&status.parentChild&&status.correlatedLogs;},'Actual native diagnostics export');
|
||||
const enrolled=(await state()).deviceId;const response=await fetch(`${process.env.SAFETYNET_TABS_SERVER}/api/admin/devices/${enrolled}/revoke`,{method:'POST',headers:{Authorization:`Bearer ${process.env.SAFETYNET_TABS_ADMIN}`,'content-type':'application/json'},body:'{}'});assert.equal(response.status,200);await until(async()=>!(await state()).ready,'Central revocation');
|
||||
for(const page of [revokePageA,revokePageB]){assert.equal(page.isDestroyed(),false,'Revocation proof lost native view before request assertion');assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(base+'/revoked-public')},{method:'POST',body:'Public after revocation'}).then(()=>false).catch(()=>true)`),true,'Revoked background/foreground page sent request');}
|
||||
await wait(500);const stoppedExports=(await collectorStatus()).exports;for(let i=0;i<5;i++)await state();await wait(600);assert.equal((await collectorStatus()).exports,stoppedExports,'Revoked browser continued diagnostic export');
|
||||
const report={actualNativeWebContents:true,actualChromeUi:true,independentDomAndScroll:true,noReloadOnSwitch:true,independentHistory:true,allTabsDlp:true,siteClipboardDenied:true,remoteNodeUnavailable:true,closedContentsDestroyed:true,lastTabFreshGuard:true,centralRevocationAllTabs:true,actualBrowserDiagnostics:true,diagnosticsStoppedOnRevocation:true};fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync('artifacts/native-tabs-proof.json',JSON.stringify(report,null,2));console.log('NATIVE_TABS_PROOF_OK '+JSON.stringify(report));finish(0);
|
||||
}catch(error){console.error('NATIVE_TABS_PROOF_FAILED '+error.stack);finish(1);}});
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {createServer} from 'node:http';
|
||||
import {createRequire} from 'node:module';
|
||||
import {mkdtempSync,rmSync,mkdirSync,writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
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 {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))};};
|
||||
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}`;
|
||||
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;}
|
||||
res.setHeader('content-type','text/html');res.end(`<!doctype html><title>${path}</title><h1>Native tab ${path}</h1><input id="remember"><a id="next" href="${path==='/'?'/a-two':'/b-two'}">Next page</a><div style="height:4000px">Unstyled fixture content</div>`);
|
||||
}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));const url=`http://127.0.0.1:${sink.address().port}`;
|
||||
store=openStore(join(directory,'management'));const policy=store.get('policy');policy.diagnostics={...policy.diagnostics,enabled:true,mode:'debug',endpoint:collectorUrl,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};policy.browser.homeUrl=url;policy.browser.allowedHosts=['127.0.0.1'];policy.dlp.rules=[{id:'fixture-secret',label:'Fixture secret',kind:'literal',value:'TAB_FIXTURE_SECRET',action:'block'}];store.put('policy',policy);
|
||||
const code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');privateSentinels=[code,adminToken,'TAB_FIXTURE_SECRET'];store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-native-tabs-user');
|
||||
const config={adminToken,publicUrl:'http://127.0.0.1',secure:false};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 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});}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {readFileSync,readdirSync} from 'node:fs';
|
||||
import {createHash} from 'node:crypto';
|
||||
import {createRequire} from 'node:module';
|
||||
import path from 'node:path';
|
||||
const platform=process.argv[2];if(platform!=='win'||process.platform!=='win32')throw Error('Windows is the active build target; other platforms are deferred');
|
||||
const runtime=spawnSync(process.env.SAFETYNET_BUILD_PYTHON||'python',['scripts/provision-windows-runtime.py'],{stdio:'inherit'});if(runtime.status!==0)process.exit(runtime.status??1);
|
||||
const semantic=spawnSync(process.env.SAFETYNET_BUILD_PYTHON||'python',['scripts/provision-semantic-runtime.py','--verify'],{stdio:'inherit'});if(semantic.status!==0)process.exit(semantic.status??1);
|
||||
const name=process.env.BRAND_NAME||'SafetyNet',id=process.env.BRAND_APP_ID||'enterprise.safetynet.browser';
|
||||
if(!/^[\w .-]{1,40}$/.test(name)||!/^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*){2,}$/.test(id))throw Error('Invalid white-label product name or app ID');
|
||||
const configuration=JSON.parse(readFileSync('package.json','utf8')),workspace=path.resolve('.'),output=path.resolve(configuration.build.directories.output);
|
||||
if(!output.startsWith(workspace+path.sep))throw Error('Build output must stay inside the workspace');
|
||||
// Reuse the pinned installed distribution already exercised by native tests.
|
||||
// This avoids a second archive extraction and Windows directory-rename locks.
|
||||
const electronDirectory=path.dirname(createRequire(import.meta.url)('electron'));
|
||||
if(readFileSync(path.join(electronDirectory,'version'),'utf8').trim().replace(/^v/,'')!==configuration.devDependencies.electron)throw Error('Installed Electron does not match the build pin');
|
||||
function sourceSnapshot(){const entries=[];function walk(directory){for(const item of readdirSync(directory,{withFileTypes:true})){const file=path.join(directory,item.name);if(item.isDirectory())walk(file);else if(item.isFile())entries.push([file,createHash('sha256').update(readFileSync(file)).digest('hex')]);}}for(const directory of ['apps/browser','packages/core'])walk(directory);entries.push(['package.json',createHash('sha256').update(readFileSync('package.json')).digest('hex')]);return JSON.stringify(entries.sort((a,b)=>a[0].localeCompare(b[0])));}
|
||||
const sourcesBefore=sourceSnapshot();
|
||||
const r=spawnSync(process.execPath,['node_modules/electron-builder/out/cli/cli.js',`--${platform}`,'--dir',`--config.electronDist=${electronDirectory}`,`--config.productName=${name}`,`--config.appId=${id}`],{stdio:'inherit',env:process.env});
|
||||
if(r.status!==0)process.exit(r.status??1);
|
||||
if(sourceSnapshot()!==sourcesBefore)throw Error('Browser sources changed during packaging; discard this artifact and rebuild from stable sources');
|
||||
const resources=path.join(output,'win-unpacked/resources');
|
||||
const acl=spawnSync(path.join(resources,'python/python.exe'),[path.join(resources,'inspector/provision_runtime_acl.py'),'--provision'],{stdio:'inherit',windowsHide:true});process.exit(acl.status??1);
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
if(!process.versions.electron){
|
||||
const {spawnSync}=require('node:child_process');const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;
|
||||
const result=spawnSync(require('electron'),[__filename],{env,stdio:'inherit',windowsHide:true,timeout:45000});process.exit(result.status??1);
|
||||
}else{
|
||||
const {app,BrowserWindow,WebContentsView,session}=require('electron');
|
||||
const fs=require('node:fs'),path=require('node:path'),https=require('node:https'),assert=require('node:assert/strict');
|
||||
const {X509Certificate}=require('node:crypto');const {fillScript}=require('../apps/browser/password-fill.cjs');
|
||||
app.enableSandbox();app.disableHardwareAcceleration();
|
||||
app.whenReady().then(async()=>{let server,win;const views=[];try{
|
||||
const cert=fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem')),key=fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/key.pem'));
|
||||
const fingerprint=new X509Certificate(cert).fingerprint256;
|
||||
server=https.createServer({cert,key},(_req,res)=>{res.setHeader('content-type','text/html');res.end('<!doctype html><form method="post" action="/signin"><input name="username" autocomplete="username"><input type="password" autocomplete="current-password"></form>');}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));
|
||||
const origin=`https://127.0.0.1:${server.address().port}`,ses=session.fromPartition('password-focus-proof');
|
||||
// Trust only this public synthetic certificate on loopback, in this fixture.
|
||||
ses.setCertificateVerifyProc((request,callback)=>callback(request.hostname==='127.0.0.1'&&new X509Certificate(request.certificate.data).fingerprint256===fingerprint?0:-2));
|
||||
win=new BrowserWindow({show:true,width:700,height:500});
|
||||
for(let i=0;i<2;i++){const v=new WebContentsView({webPreferences:{session:ses,nodeIntegration:false,contextIsolation:true,sandbox:true}});views.push(v);win.contentView.addChildView(v);v.setBounds({x:0,y:0,width:700,height:500});await v.webContents.loadURL(origin+'/login');}
|
||||
const a=views[0],b=views[1],credential={origin,username:'fixture-user',password:'synthetic-focus-proof-password'};
|
||||
const execute=()=>a.webContents.executeJavaScriptInIsolatedWorld(999,[{code:fillScript(origin+'/login',credential)}],false);
|
||||
b.setVisible(false);a.setVisible(true);win.focus();a.webContents.focus();
|
||||
await new Promise(r=>setTimeout(r,200));assert.equal((await execute()).completed,true);
|
||||
await a.webContents.executeJavaScript('document.querySelectorAll("input").forEach(e=>e.value="")');
|
||||
a.setVisible(false);b.setVisible(true);b.webContents.focus();await new Promise(r=>setTimeout(r,200));
|
||||
await assert.rejects(execute());
|
||||
assert.equal(await a.webContents.executeJavaScript('document.querySelector("input[type=password]").value'),'');
|
||||
fs.mkdirSync(path.join(__dirname,'../artifacts'),{recursive:true});fs.writeFileSync(path.join(__dirname,'../artifacts/password-focus-proof.json'),JSON.stringify({actualNativeViews:true,foregroundAllowed:true,hiddenBlurredFillDenied:true,noCredentialWriteToHiddenTab:true},null,2));
|
||||
console.log('Native password foreground proof passed.');
|
||||
}catch(error){console.error(error.message);process.exitCode=1;}finally{for(const v of views)if(!v.webContents.isDestroyed())v.webContents.close({waitForBeforeUnload:false});win?.destroy();if(server)await new Promise(r=>{server.closeAllConnections();server.close(r);});app.exit(process.exitCode||0);}});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1');
|
||||
let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000);
|
||||
async function evaluate(source){return window.webContents.executeJavaScript(source,true);}
|
||||
async function waitFor(source){const until=Date.now()+12000;while(Date.now()<until){if(await evaluate(source))return;await new Promise(r=>setTimeout(r,75));}throw Error('Administrator UI state did not arrive');}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);
|
||||
await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`);
|
||||
assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'});
|
||||
const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff');
|
||||
await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);
|
||||
await evaluate(`document.getElementById('brand-name').value='History test brand';document.getElementById('security-dns-hosts').value='blocked.synthetic.example';document.getElementById('security-dns-ips').value='203.0.113.10';document.getElementById('security-content-phrases').value='synthetic forbidden project';document.getElementById('agent-enabled').checked=true;document.getElementById('agent-hosts').value='*.synthetic.example';document.getElementById('agent-scope-read').checked=true;document.getElementById('publish').click()`);
|
||||
await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`);
|
||||
assert.equal(await evaluate(`document.getElementById('security-dns-hosts').value`),'blocked.synthetic.example');assert.equal(await evaluate(`document.getElementById('agent-enabled').checked`),true);assert.equal(await evaluate(`document.getElementById('agent-scope-read').checked`),true);
|
||||
await evaluate(`document.getElementById('security-controls').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,200));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/admin-security-controls.png'),(await window.webContents.capturePage()).toPNG());
|
||||
await evaluate(`document.getElementById('brand-name').value='Unpublished draft';document.getElementById('history-load').click()`);await waitFor(`document.querySelectorAll('#history-rows tr').length===2`);
|
||||
assert.equal(await evaluate(`document.getElementById('brand-name').value`),'Unpublished draft');
|
||||
await evaluate(`document.querySelectorAll('#history-rows button')[1].click()`);await waitFor(`!document.getElementById('history-review').hidden`);
|
||||
assert.equal(await evaluate(`document.getElementById('history-restore').disabled`),true);assert.equal(await evaluate(`document.getElementById('brand-name').value`),'Unpublished draft');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');assert.ok((await evaluate(`document.getElementById('history-diff-list').textContent`)).includes('/brand/name'));
|
||||
await evaluate(`document.getElementById('history-review').scrollIntoView();document.getElementById('console-theme').value='dark';document.getElementById('console-theme').dispatchEvent(new Event('change'))`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/policy-history-admin.png'),(await window.webContents.capturePage()).toPNG());
|
||||
await evaluate(`globalThis.__originalPrincipal=currentPrincipal;applyOperatorPermissions({sub:'synthetic-auditor',role:'auditor',permissions:['overview.read','audit.read','policy.read','teaching.read']});`);assert.equal(await evaluate(`document.getElementById('history-approve').disabled&&document.getElementById('history-restore').disabled&&document.getElementById('agent-enabled').disabled`),true);await evaluate(`applyOperatorPermissions(globalThis.__originalPrincipal);delete globalThis.__originalPrincipal`);
|
||||
await evaluate(`document.getElementById('history-approve').checked=true;document.getElementById('history-approve').dispatchEvent(new Event('change'));document.getElementById('history-restore').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v3'&&document.getElementById('status').textContent.includes('restored')`);
|
||||
assert.equal(await evaluate(`document.getElementById('brand-name').value`),'SafetyNet');assert.equal(await evaluate(`document.getElementById('agent-enabled').checked`),false);assert.equal(await evaluate(`document.getElementById('security-dns-hosts').value`),'');assert.equal(await evaluate(`document.querySelectorAll('#history-rows tr').length`),3);assert.equal(await evaluate(`globalThis.__proofErrors`),0);
|
||||
console.log('HISTORY_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('HISTORY_ADMIN_PROOF_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-policy-history-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false};
|
||||
store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run('synthetic-bookmark-device','Synthetic bookmark device','win32','synthetic-user','synthetic-unused',new Date().toISOString());
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/policy-history-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('HISTORY_ADMIN_PROOF_OK'))console.log('Policy history administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('HISTORY_ADMIN_PROOF_'))console.error(text.trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Policy history administrator interface proof failed');
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,3);assert.equal(policy.brand.name,'SafetyNet');assert.equal(policy.agents.enabled,false);assert.deepEqual(policy.security.dns.blockedHosts,[]);assert.deepEqual(policy.security.content.blockedPhrases,[]);
|
||||
const report={actualAdministratorUi:true,persistedHistory:true,serverDiff:true,explicitRestoreApproval:true,monotonicRestore:true,draftPreservedDuringReview:true,structuredDnsContentAgentControls:true,nodeUnavailable:true,cspPreserved:true};mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/policy-history-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-policy-history-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow}=require('electron');const assert=require('node:assert/strict');const fs=require('node:fs');const path=require('node:path');
|
||||
app.disableHardwareAcceleration();app.enableSandbox();app.setPath('userData',process.env.SAFETYNET_ADMIN_SMOKE_PROFILE);app.commandLine.appendSwitch('force-device-scale-factor','1');
|
||||
let window;const deadline=setTimeout(()=>{console.error('ADMIN_UI_PROOF_TIMEOUT');app.exit(1);},50000);
|
||||
async function evaluate(source){return window.webContents.executeJavaScript(source,true);}
|
||||
async function waitFor(source){const until=Date.now()+12000;while(Date.now()<until){if(await evaluate(source))return;await new Promise(r=>setTimeout(r,75));}throw Error('Administrator UI state did not arrive: '+await evaluate(`document.getElementById('status').textContent`));}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
window.webContents.on('did-finish-load',()=>evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`).catch(()=>{}));
|
||||
await window.loadURL(process.env.SAFETYNET_ADMIN_SMOKE_URL);
|
||||
await evaluate(`globalThis.__proofErrors=0;addEventListener('error',()=>globalThis.__proofErrors++);addEventListener('unhandledrejection',()=>globalThis.__proofErrors++);`);
|
||||
assert.deepEqual(await evaluate(`({node:typeof process,require:typeof require,bridge:typeof safetynet})`),{node:'undefined',require:'undefined',bridge:'undefined'});
|
||||
const headers=await evaluate(`fetch(location.href).then(r=>({csp:r.headers.get('content-security-policy'),type:r.headers.get('x-content-type-options')}))`);assert.ok(headers.csp.includes("script-src 'self'"));assert.ok(headers.csp.includes("frame-ancestors 'none'"));assert.equal(headers.type,'nosniff');
|
||||
const reviewProof=process.env.SAFETYNET_PROFILE_REVIEW_PROOF==='1';
|
||||
if(reviewProof){await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_PROFILE_OPERATOR_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);}
|
||||
else {
|
||||
await evaluate(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_ADMIN_SMOKE_TOKEN)};document.getElementById('connect').click()`);await waitFor(`!document.getElementById('workspace').hidden&&document.getElementById('status').textContent.includes('Connected')`);}
|
||||
|
||||
await evaluate(`document.getElementById('profile-template').value='employee';document.getElementById('profile-use-template').click();document.getElementById('profile-id').value='research-europe';document.getElementById('profile-name').value='Research Europe';for(const input of document.querySelectorAll('#profile-devices input'))input.checked=input.value==='pilot-device';document.getElementById('profile-blocked').value='restricted.synthetic.example';document.getElementById('profile-save').click()`);
|
||||
if(reviewProof){
|
||||
await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');assert.equal(await evaluate(`document.querySelectorAll('#profiles-rows tr').length`),0);
|
||||
await window.webContents.session.cookies.set({url:process.env.SAFETYNET_ADMIN_SMOKE_URL,name:'sn_session',value:process.env.SAFETYNET_PROFILE_REVIEWER_TOKEN,httpOnly:true,sameSite:'lax'});await window.reload();await waitFor(`!document.getElementById('workspace').hidden&&document.querySelector('#reviews-rows button')`);
|
||||
await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);
|
||||
const operation=await evaluate(`document.getElementById('independent-review-operation').textContent`);for(const value of ['research-europe','pilot-device','devTools','restricted.synthetic.example'])assert.ok(operation.includes(value),`Exact proposal missing ${value}`);
|
||||
await evaluate(`document.getElementById('independent-review-confirm').checked=true;document.getElementById('independent-review-confirm').dispatchEvent(new Event('change'));document.getElementById('independent-review-approve').click()`);await waitFor(`document.getElementById('policy-version').textContent==='v2'`);
|
||||
}else{await waitFor(`document.getElementById('policy-version').textContent==='v2'&&document.getElementById('status').textContent.includes('published')`);}
|
||||
assert.equal(await evaluate(`document.querySelector('#profiles-rows tr td:nth-child(3)').textContent`),'deny');
|
||||
await evaluate(`document.getElementById('profile-preview-device').value='pilot-device';document.getElementById('profile-preview').click()`);await waitFor(`document.getElementById('profile-preview-summary').textContent.includes('research-europe')`);
|
||||
assert.equal(await evaluate(`document.getElementById('profile-preview-diff').textContent.includes('restricted.synthetic.example')`),true);
|
||||
await evaluate(`document.getElementById('profile-preview-device').value='outside-device';document.getElementById('profile-preview').click()`);await waitFor(`document.getElementById('profile-preview-summary').textContent.includes('baseline only')`);
|
||||
assert.equal(await evaluate(`document.getElementById('profile-preview-diff').textContent.includes('restricted.synthetic.example')`),false);assert.equal(await evaluate(`globalThis.__proofErrors`),0);
|
||||
await evaluate(`document.getElementById('user-profiles').scrollIntoView()`);window.webContents.invalidate();await new Promise(r=>setTimeout(r,300));fs.mkdirSync(path.resolve('artifacts'),{recursive:true});fs.writeFileSync(path.resolve('artifacts/profiles-admin.png'),(await window.webContents.capturePage()).toPNG());
|
||||
console.log('PROFILE_ADMIN_PROOF_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('PROFILE_ADMIN_PROOF_FAILED',error.stack);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Finite, isolated administrator workflow proof using actual UI and HTTP routes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRequire } from 'node:module';
|
||||
import { createServer } from 'node:net';
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve, sep, basename } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { digest } from '../apps/server/store.mjs';
|
||||
import { openStore } from '../apps/server/store.mjs';
|
||||
import { createApp } from '../apps/server/app.mjs';
|
||||
import { verifyPolicy } from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-profiles-admin-smoke-'));
|
||||
let store,server,child;
|
||||
try{
|
||||
store=openStore(join(directory,'management'));const adminToken=randomBytes(32).toString('base64url'),config={adminToken,publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:process.env.SAFETYNET_PROFILE_REVIEW_PROOF==='1'};
|
||||
for(const id of ['pilot-device','member-device','outside-device'])store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash,last_seen) VALUES(?,?,?,?,?,?)').run(id,id,'win32','synthetic-user',`synthetic-${id}`,new Date().toISOString());
|
||||
const operatorToken=randomBytes(32).toString('base64url'),reviewerToken=randomBytes(32).toString('base64url');for(const [value,sub,role]of [[operatorToken,'group-operator','operator'],[reviewerToken,'group-reviewer','admin']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(value),Date.now()+60000,JSON.stringify({sub,role}));
|
||||
server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));config.publicUrl=`http://127.0.0.1:${server.address().port}`;
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl,SAFETYNET_ADMIN_SMOKE_TOKEN:adminToken,SAFETYNET_PROFILE_OPERATOR_TOKEN:operatorToken,SAFETYNET_PROFILE_REVIEWER_TOKEN:reviewerToken};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/profiles-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',data=>{if(String(data).includes('PROFILE_ADMIN_PROOF_OK'))console.log('Profile administrator interface workflow passed.');});child.stderr.on('data',data=>{const text=String(data);if(text.includes('PROFILE_ADMIN_PROOF_'))console.error(text.trim());});
|
||||
const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',code=>r(code??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0,'Profile administrator interface proof failed');
|
||||
const policy=verifyPolicy(store.envelope(),store.publicKey());assert.equal(policy.version,2);
|
||||
const report={actualAdministratorUi:true,explicitDeviceAssignments:true,employeeDeveloperFlags:true,websiteRestrictions:true,noNode:true,cspPreserved:true,...(config.requireIndependentPolicyApproval?{operatorProposalLeftPolicyUnchanged:true,exactProfileReviewed:true,distinctAdministratorApproval:true}:{effectivePreview:true})};mkdirSync('artifacts',{recursive:true});writeFileSync(config.requireIndependentPolicyApproval?'artifacts/profiles-review-admin-proof.json':'artifacts/profiles-admin-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-profiles-admin-smoke-'))throw Error('Unsafe proof cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
"""Compatibility entry point for verified offline semantic provisioning."""
|
||||
from runpy import run_path
|
||||
from pathlib import Path
|
||||
run_path(str(Path(__file__).with_name('provision-semantic-runtime.py')), run_name='__main__')
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Provision an offline, verified EmbeddingGemma 2 Windows distribution."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEST = ROOT / '.runtime/windows/semantic'
|
||||
PIN = 'bfcd298762cc34d0357ece5ebdd31791a3a374d8'
|
||||
MODELS = {
|
||||
'embeddinggemma-2-Q8_0.gguf': (309855456, '2188ac1deca4b77dffefd603c2776a9d76d9d74ec01841392982ebb840b09135'),
|
||||
'mmproj-embeddinggemma-2-Q8_0.gguf': (554821024, 'c4a8a52691ecef40618438928bdf9e68379b854e24166f292592353db0aab64f'),
|
||||
}
|
||||
|
||||
def verify(base, item):
|
||||
name = item['path']
|
||||
if Path(name).name != name or ':' in name or '\\' in name or '/' in name:
|
||||
raise ValueError('Invalid artifact name')
|
||||
file = base / name
|
||||
if file.is_symlink() or not file.is_file() or file.stat().st_size != item['bytes']:
|
||||
raise ValueError('Artifact missing or incorrect size: ' + name)
|
||||
digest = hashlib.sha256()
|
||||
with file.open('rb') as stream:
|
||||
while block := stream.read(1024 * 1024): digest.update(block)
|
||||
if digest.hexdigest() != item['sha256']: raise ValueError('Artifact digest mismatch: ' + name)
|
||||
|
||||
def validate(base):
|
||||
manifest = json.loads((base / 'models/model-manifest.json').read_text('utf-8'))
|
||||
if (manifest['model_id'], manifest['revision'], manifest['dimensions'], manifest['quantization'], manifest['runtime_tag']) != ('google/embeddinggemma-2', PIN, 768, 'Q8_0', 'b11461'):
|
||||
raise ValueError('Model identity mismatch')
|
||||
for name, (size, digest) in MODELS.items(): verify(base / 'models', {'path': name, 'bytes': size, 'sha256': digest})
|
||||
files = json.loads((base / 'runtime-manifest.json').read_text('utf-8'))['files']
|
||||
if not 1 <= len(files) <= 64 or len({item['path'].lower() for item in files}) != len(files): raise ValueError('Invalid runtime inventory')
|
||||
if not any(item['path'] == 'searchi-gemma-worker.exe' for item in files): raise ValueError('Worker absent')
|
||||
for item in files: verify(base, item)
|
||||
if {p.name.lower() for p in base.glob('*.dll')} != {i['path'].lower() for i in files if i['path'].lower().endswith('.dll')}: raise ValueError('Unverified DLL')
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--source-project', type=Path)
|
||||
parser.add_argument('--verify', action='store_true')
|
||||
args = parser.parse_args()
|
||||
if DEST.exists():
|
||||
validate(DEST)
|
||||
print('Verified offline EmbeddingGemma 2 model and Windows runtime.')
|
||||
return
|
||||
if args.verify or args.source_project is None: raise ValueError('Provision semantic runtime with --source-project before packaging')
|
||||
source = args.source_project.resolve()
|
||||
runtime = source / '.searchi/runtime/auto/Release'
|
||||
inventory = json.loads((runtime / 'runtime-libraries.json').read_text('utf-8'))
|
||||
worker = runtime / 'searchi-gemma-worker.exe'
|
||||
files = [{'path': worker.name, 'bytes': worker.stat().st_size, 'sha256': inventory['worker_sha256']}, *inventory['libraries']]
|
||||
for item in files: verify(runtime, item)
|
||||
DEST.parent.mkdir(parents=True, exist_ok=True)
|
||||
candidate = Path(tempfile.mkdtemp(prefix='semantic-stage-', dir=DEST.parent))
|
||||
# A failed stage is retained for diagnosis; never delete another project's assets.
|
||||
(candidate / 'models').mkdir()
|
||||
for item in files: shutil.copy2(runtime / item['path'], candidate / item['path'])
|
||||
(candidate / 'runtime-manifest.json').write_text(json.dumps({'files': files}, indent=2), 'utf-8')
|
||||
shutil.copy2(source / 'tools/embeddinggemma2/model-manifest.json', candidate / 'models/model-manifest.json')
|
||||
for name in MODELS: shutil.copy2(source / '.searchi/models/embeddinggemma2' / name, candidate / 'models' / name)
|
||||
shutil.copy2(source / '.searchi/references/llama.cpp/LICENSE', candidate / 'LLAMA-LICENSE.txt')
|
||||
for name in ('MSVC-REDISTRIBUTION.txt', 'VULKAN-LICENSE.txt'): shutil.copy2(runtime / name, candidate / name)
|
||||
validate(candidate)
|
||||
candidate.rename(DEST)
|
||||
print('Provisioned verified offline EmbeddingGemma 2 model and Windows runtime.')
|
||||
|
||||
if __name__ == '__main__': main()
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Build a pinned private Windows x64 inspector runtime; no machine-wide install."""
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
if sys.platform!='win32': raise SystemExit('Windows runtime provisioning runs on Windows only')
|
||||
VERSION='3.13.16'
|
||||
CHECKSUM='97dae5274cc54867065e8d5a3226e48c35017ed332a0fdb0e27d5b5821961297'
|
||||
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()
|
||||
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:
|
||||
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))
|
||||
(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)
|
||||
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']:
|
||||
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)
|
||||
@@ -0,0 +1,34 @@
|
||||
// Finite native fixture: synthetic teaching data, real signed policy and CPU model.
|
||||
const {app,BrowserWindow,clipboard,ClipboardItem}=require('electron');
|
||||
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
|
||||
app.setPath('userData',process.env.SAFETYNET_SEMANTIC_PROFILE);
|
||||
app.setPath('sessionData',process.env.SAFETYNET_SEMANTIC_PROFILE);
|
||||
app.disableHardwareAcceleration();
|
||||
require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
const deadline=setTimeout(()=>app.exit(1),90000);
|
||||
app.whenReady().then(async()=>{let original;try{
|
||||
let win;
|
||||
for(let i=0;i<150;i++){win=BrowserWindow.getAllWindows()[0];if(win&&!win.webContents.isLoading()&&win.webContents.getURL().endsWith('index.html'))break;await wait(100);}
|
||||
assert.ok(win,'Trusted shell unavailable');
|
||||
const enrolled=await win.webContents.executeJavaScript(`window.safetynet.enroll(${JSON.stringify({server:process.env.SAFETYNET_SEMANTIC_SERVER,code:process.env.SAFETYNET_SEMANTIC_CODE})})`);
|
||||
assert.equal(enrolled.ok,true,'Enrollment failed');assert.equal(enrolled.data.ready,true);
|
||||
const page=win.contentView.children.find(child=>child.webContents!==win.webContents).webContents;
|
||||
for(let i=0;i<100&&page.isLoading();i++)await wait(100);
|
||||
const connection=JSON.parse(process.env.SAFETYNET_SEMANTIC_FIXTURES);
|
||||
original=[];for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);if(Object.keys(data).length)original.push(new ClipboardItem(data));}
|
||||
async function select(text){await page.executeJavaScript(`(()=>{let node=document.getElementById('source');node.textContent=${JSON.stringify(text)};const range=document.createRange();range.selectNodeContents(node);const selection=getSelection();selection.removeAllRanges();selection.addRange(range);})()`);}
|
||||
async function copy(){win.show();win.focus();page.focus();page.sendInputEvent({type:'keyDown',keyCode:'C',modifiers:['control']});page.sendInputEvent({type:'keyUp',keyCode:'C',modifiers:['control']});await wait(500);for(let i=0;i<150;i++){const state=await win.webContents.executeJavaScript('window.safetynet.state()');if(!state.data.clipboardPending)return;await wait(100);}throw Error('Native clipboard scan timed out');}
|
||||
const sentinel='Public semantic test clipboard marker';
|
||||
await clipboard.writeText(sentinel);await select(connection.paraphrase);await copy();
|
||||
assert.ok((await clipboard.readText())===sentinel,'Semantic confidential paraphrase reached OS clipboard');
|
||||
const overview=await (await fetch(`${process.env.SAFETYNET_SEMANTIC_SERVER}/api/admin/overview`,{headers:{authorization:`Bearer ${process.env.SAFETYNET_SEMANTIC_ADMIN}`}})).json();
|
||||
assert.ok(overview.events.some(event=>event.kind==='clipboard.block'&&event.metadata.ruleId==='synthetic-restructuring'),'Semantic rule did not cause centrally audited block');
|
||||
await select(connection.publicText);await copy();
|
||||
for(let i=0;i<30&&await clipboard.readText()!==connection.publicText;i++)await wait(100);
|
||||
assert.equal(await clipboard.readText(),connection.publicText,'Public text was not copied through real local semantic inference');
|
||||
assert.equal(JSON.stringify(overview.events).includes(connection.paraphrase),false,'Audit contains raw clipboard text');
|
||||
console.log(JSON.stringify({nativeSemanticClipboard:true,confidentialParaphraseBlocked:true,publicTextAllowed:true,centralAuditMetadataOnly:true}));
|
||||
if(original.length)await clipboard.write(original);else await clipboard.clear();original=null;clearTimeout(deadline);app.quit();
|
||||
}catch(error){console.error(JSON.stringify({nativeSemanticClipboard:false,error:error.message}));if(original){try{if(original.length)await clipboard.write(original);else await clipboard.clear();}catch{}}clearTimeout(deadline);app.exit(1);}});
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Independent localhost control plane and real Windows browser. No cloud/model downloads.
|
||||
import assert from 'node:assert/strict';
|
||||
import {createRequire} from 'node:module';
|
||||
import {createServer} from 'node:http';
|
||||
import {mkdtempSync,mkdirSync,existsSync,writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join,resolve} from 'node:path';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import {createApp} from '../apps/server/app.mjs';
|
||||
import {openStore,digest} from '../apps/server/store.mjs';
|
||||
import {inspect,verifyPolicy} from '../packages/core/policy.mjs';
|
||||
const require=createRequire(import.meta.url),electron=require('electron');
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-semantic-browser-'));
|
||||
const runtime=resolve('.runtime/windows/semantic'),python=resolve('.runtime/windows/python/python.exe');
|
||||
assert.ok(existsSync(join(runtime,'models/model-manifest.json')),'Stage verified local model artifacts before running');
|
||||
const fixtures={reference:'Confidential corporate restructuring will close the Lisbon office next autumn and eliminate two hundred positions before the public announcement. Leadership is negotiating a six-million-euro acquisition of a rival. These undisclosed staffing reductions and acquisition plans are restricted internal company information.',paraphrase:'Private workforce planning calls for shutting the Portugal branch after summer and cutting roughly two hundred jobs. Executives are discussing a six-million-euro purchase of a competitor; the timetable and personnel reductions remain undisclosed.',publicText:'A simple tomato pasta recipe combines ripe tomatoes, olive oil, garlic and fresh basil. Boil the pasta in salted water and serve it warm.'};
|
||||
let inspector,management,sink,store,child;
|
||||
const report={model:'google/embeddinggemma-2',revision:'bfcd298762cc34d0357ece5ebdd31791a3a374d8',dimensions:768,profile:'document-v1',artifactFormat:'gguf',quantization:'Q8_0',runtimeRevision:'b11461',local:true};
|
||||
try{
|
||||
const inspectorToken=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url'),code=randomBytes(32).toString('base64url');
|
||||
inspector=spawn(python,['apps/inspector/server.py'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,EMBEDDING_MODEL_DIR:join(runtime,'models'),EMBEDDING_WORKER_PATH:join(runtime,'searchi-gemma-worker.exe'),INSPECTOR_TOKEN:inspectorToken,INSPECTOR_PORT:'0',INSPECTOR_PARENT_PID:String(process.pid)}});
|
||||
inspector.stderr.on('data',()=>{});
|
||||
const inspectorPort=await new Promise((accept,reject)=>{let output='';const timeout=setTimeout(()=>{inspector.kill();reject(Error('Inspector startup timed out'));},40000);inspector.once('error',reject);inspector.once('exit',()=>{clearTimeout(timeout);reject(Error('Inspector exited before startup'));});inspector.stdout.on('data',chunk=>{output+=chunk;if(output.includes('\n')){clearTimeout(timeout);try{accept(JSON.parse(output.split('\n')[0]).port);}catch{reject(Error('Invalid inspector startup'));}}});});
|
||||
sink=createServer((_req,res)=>{res.setHeader('Content-Type','text/html');res.end('<!doctype html><title>Local semantic enforcement fixture</title><h1>Public browser fixture</h1><pre id="source"></pre>');}).listen(0,'127.0.0.1');await new Promise(resolve=>sink.once('listening',resolve));
|
||||
store=openStore(join(temporary,'management.sqlite'));
|
||||
const initial=store.get('policy');initial.browser.homeUrl=`http://127.0.0.1:${sink.address().port}`;initial.browser.allowedHosts=['127.0.0.1'];initial.dlp.rules=[];store.put('policy',initial);
|
||||
store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'synthetic-semantic-user');
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,adminToken,inspectorUrl:`http://127.0.0.1:${inspectorPort}`,inspectorToken};
|
||||
management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(resolve=>management.once('listening',resolve));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
async function admin(route,body,method='POST'){const result=await fetch(config.publicUrl+route,{method,headers:{authorization:`Bearer ${adminToken}`,'content-type':'application/json'},body:JSON.stringify(body),signal:AbortSignal.timeout(90000)});assert.equal(result.status,200,`Central route ${route} failed`);return result.json();}
|
||||
const teaching=await admin('/api/admin/teaching',{label:'Synthetic confidential workforce plan',filename:'synthetic-reference.txt',data:Buffer.from(fixtures.reference).toString('base64')});
|
||||
await admin(`/api/admin/teaching/${teaching.id}/publish`,{expectedVersion:initial.version,approve:true});
|
||||
const reference=await admin('/api/admin/semantic/reference',{description:fixtures.reference});
|
||||
for(const key of ['model','revision','dimensions','profile','artifactFormat','quantization','runtimeRevision','local'])assert.equal(reference[key],report[key],`Unexpected local model ${key}`);
|
||||
const policy=store.get('policy');policy.version++;policy.dlp.semantic={enabled:true,model:reference.model,revision:reference.revision,dimensions:reference.dimensions,profile:reference.profile,artifactFormat:reference.artifactFormat,quantization:reference.quantization,runtimeRevision:reference.runtimeRevision,categories:[{id:'synthetic-restructuring',label:'Synthetic restricted workforce plan',threshold:0.65,prototype:reference.vector,prototypes:reference.vectors}]};
|
||||
assert.equal(inspect(Buffer.from(fixtures.paraphrase),policy).decision,'allow','Paraphrase accidentally matches a deterministic rule');
|
||||
const secret=await admin('/api/admin/policy/test',{text:fixtures.paraphrase,policy}),ordinary=await admin('/api/admin/policy/test',{text:fixtures.publicText,policy});
|
||||
assert.equal(secret.ruleId,'synthetic-restructuring','Semantic paraphrase did not match calibrated category');assert.equal(ordinary.decision,'allow','Unrelated public text failed semantic calibration');
|
||||
await admin('/api/admin/policy',policy,'PUT');const signed=verifyPolicy(store.envelope(),store.publicKey());assert.equal(signed.dlp.semantic.revision,report.revision);assert.equal(signed.dlp.semantic.profile,report.profile);
|
||||
const env={...process.env,SAFETYNET_SEMANTIC_PROFILE:join(temporary,'browser'),SAFETYNET_SEMANTIC_SERVER:config.publicUrl,SAFETYNET_SEMANTIC_CODE:code,SAFETYNET_SEMANTIC_ADMIN:adminToken,SAFETYNET_SEMANTIC_FIXTURES:JSON.stringify(fixtures)};
|
||||
// Prove the browser's automatic bundled-path wiring rather than the fixture inspector environment.
|
||||
delete env.EMBEDDING_MODEL_DIR;delete env.EMBEDDING_WORKER_PATH;delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(electron,['scripts/semantic-browser-smoke.cjs'],{windowsHide:true,stdio:'inherit',env});const timer=setTimeout(()=>child.kill(),100000);
|
||||
const status=await new Promise(resolve=>{child.once('exit',code=>resolve(code??1));child.once('error',()=>resolve(1));});clearTimeout(timer);assert.equal(status,0,'Native semantic browser fixture failed');
|
||||
Object.assign(report,{centralTeaching:true,signedPolicyVerified:true,deterministicParaphraseAllow:true,semanticParaphraseBlock:true,publicTextAllow:true,paraphraseSimilarity:secret.similarity?.[0]?.score,publicSimilarity:ordinary.similarity?.[0]?.score,nativeProofPassed:true});
|
||||
mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/semantic-browser-proof.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report));
|
||||
}finally{
|
||||
child?.kill();inspector?.kill();for(const server of [management,sink])if(server){server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}store?.db.close();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createServer } from 'node:http';
|
||||
import { createRequire } from 'node:module';
|
||||
import { mkdtempSync,rmSync,mkdirSync } 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';
|
||||
const require=createRequire(import.meta.url),electron=require('electron');
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-smoke-'));mkdirSync('artifacts',{recursive:true});
|
||||
let posts=0,proxyRequests=0,secondaryRequests=0;
|
||||
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==='/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'}"`);
|
||||
res.end(req.url==='/download-safe'?Buffer.from([0,1,2,3,255]):Buffer.from('SAFETYNET_BLOCK_TEST'));return;
|
||||
}
|
||||
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>');
|
||||
};
|
||||
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));
|
||||
const store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');
|
||||
const inspectorToken=randomBytes(32).toString('base64url');
|
||||
const inspectorProcess=spawn(resolve('.runtime/windows/python/python.exe'),['apps/inspector/server.py'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,INSPECTOR_TOKEN:inspectorToken,INSPECTOR_PORT:'0',INSPECTOR_PARENT_PID:String(process.pid)}});
|
||||
const inspectorPort=await new Promise((resolve,reject)=>{
|
||||
let output='';const deadline=setTimeout(()=>{inspectorProcess.kill();reject(Error('Fixture inspector did not start'));},40000);
|
||||
inspectorProcess.once('exit',()=>{clearTimeout(deadline);reject(Error('Fixture inspector exited before startup'));});
|
||||
inspectorProcess.on('error',reject);
|
||||
inspectorProcess.stdout.on('data',chunk=>{
|
||||
output+=chunk;
|
||||
if(output.includes('\n')){
|
||||
clearTimeout(deadline);
|
||||
try{resolve(JSON.parse(output.split('\n')[0]).port);}catch{reject(Error('Fixture inspector startup rejected'));}
|
||||
}
|
||||
});
|
||||
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 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();
|
||||
inspectorProcess.kill();
|
||||
// Temporary paths originate from mkdtemp under the OS temp directory.
|
||||
rmSync(temporary,{recursive:true,force:true});process.exit(exitCode);
|
||||
@@ -0,0 +1,59 @@
|
||||
// Deterministic UI fixture: public invented metadata only; no production main process.
|
||||
const {app,BrowserWindow,ipcMain}=require('electron');
|
||||
const fs=require('node:fs'),path=require('node:path'),os=require('node:os');
|
||||
const fixtureDirectory=fs.mkdtempSync(path.join(os.tmpdir(),'safetynet-ui-fixture-'));
|
||||
function cleanup(){
|
||||
const target=path.resolve(fixtureDirectory),parent=path.resolve(os.tmpdir());
|
||||
if(path.dirname(target)===parent&&path.basename(target).startsWith('safetynet-ui-fixture-')){
|
||||
try{fs.rmSync(target,{recursive:true,force:true});}catch{}
|
||||
}
|
||||
}
|
||||
app.setPath('userData',path.join(fixtureDirectory,'profile'));
|
||||
app.disableHardwareAcceleration();
|
||||
app.commandLine.appendSwitch('force-device-scale-factor','1');
|
||||
const watchdog=setTimeout(()=>app.exit(1),20000);
|
||||
let locked=false;
|
||||
const state=()=>({ready:true,enrolled:true,brand:{name:'SafetyNet'},policyVersion:1,url:'https://example.test/',message:'UI fixture · public example metadata',aiEnabled:true,agentsEnabled:true,documentScanEnabled:true,passwordManagerEnabled:true,vaultExists:true,vaultLocked:locked});
|
||||
const vaultStatus=()=>({exists:true,locked,enabled:true,idleLockSeconds:120,allowSaving:true,allowAutofill:true});
|
||||
ipcMain.handle('ui-fixture',(_event,method)=>{
|
||||
if(method==='state')return {ok:true,data:state()};
|
||||
if(method==='vaultStatus')return {ok:true,data:vaultStatus()};
|
||||
if(method==='vaultList')return {ok:true,data:locked?[]:[{id:'fixture-1',origin:'https://example.test',label:'Public example account',username:'[email protected]'}]};
|
||||
if(method==='vaultLock'){locked=true;return {ok:true,data:vaultStatus()};}
|
||||
if(method==='vaultUnlock'){locked=false;return {ok:true,data:vaultStatus()};}
|
||||
if(['toggleVault','toggleAI'].includes(method))return {ok:true,data:null};
|
||||
return {ok:false,error:'This preview does not perform real browser or credential operations.'};
|
||||
});
|
||||
const preload=path.join(fixtureDirectory,'fixture-preload.cjs');
|
||||
fs.writeFileSync(preload,`const {contextBridge,ipcRenderer}=require('electron');const api={onState:()=>()=>{}};for(const method of ['state','toggleVault','toggleAI','vaultStatus','vaultList','vaultLock','vaultUnlock','vaultCreate','vaultSave','vaultDelete','vaultFill','vaultGenerate'])api[method]=(...args)=>ipcRenderer.invoke('ui-fixture',method,...args);contextBridge.exposeInMainWorld('safetynet',api);`);
|
||||
app.whenReady().then(async()=>{
|
||||
let win;
|
||||
try{
|
||||
win=new BrowserWindow({show:false,useContentSize:true,width:1320,height:900,webPreferences:{preload,contextIsolation:true,sandbox:true,nodeIntegration:false}});
|
||||
await win.loadFile(path.resolve('apps/browser/index.html'));
|
||||
await win.webContents.executeJavaScript(`document.getElementById('passwords').click()`);
|
||||
for(let n=0;n<40;n++){
|
||||
if(await win.webContents.executeJavaScript(`document.getElementById('vault-list').textContent.includes('Public example account')`))break;
|
||||
await new Promise(resolve=>setTimeout(resolve,50));
|
||||
}
|
||||
await win.webContents.executeJavaScript(`(()=>{const label=document.createElement('div');label.id='fixture-label';label.textContent='UI fixture preview · invented public metadata';label.style.cssText='position:fixed;left:30px;top:135px;color:#8994a3;font:12px Segoe UI';document.body.append(label);})()`);
|
||||
await win.webContents.executeJavaScript(`new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))`);
|
||||
fs.mkdirSync(path.resolve('artifacts'),{recursive:true});
|
||||
fs.writeFileSync(path.resolve('artifacts/ui-vault-preview.png'),(await win.webContents.capturePage()).toPNG());
|
||||
await win.webContents.executeJavaScript(`document.getElementById('vault-lock').click()`);
|
||||
for(let n=0;n<40;n++){
|
||||
if(await win.webContents.executeJavaScript(`!document.getElementById('vault-access').hidden`))break;
|
||||
await new Promise(resolve=>setTimeout(resolve,50));
|
||||
}
|
||||
fs.writeFileSync(path.resolve('artifacts/ui-vault-locked-preview.png'),(await win.webContents.capturePage()).toPNG());
|
||||
const bounds=await win.webContents.executeJavaScript(`JSON.stringify({header:document.querySelector('header').getBoundingClientRect().height,vaultWidth:document.getElementById('vault').getBoundingClientRect().width,vaultTop:document.getElementById('vault').getBoundingClientRect().top,missingIcons:[...document.images].filter(i=>!i.complete||!i.naturalWidth).length,locked:document.getElementById('vault-open').hidden,masked:document.getElementById('vault-master').type==='password'})`);
|
||||
fs.writeFileSync(path.resolve('artifacts/ui-vault-preview-layout.json'),bounds);
|
||||
win.setContentSize(800,650);
|
||||
await win.webContents.executeJavaScript(`new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))`);
|
||||
fs.writeFileSync(path.resolve('artifacts/ui-vault-compact-preview.png'),(await win.webContents.capturePage()).toPNG());
|
||||
console.log('UI fixture captured: unlocked and locked password panels.');
|
||||
win.destroy();win=null;clearTimeout(watchdog);
|
||||
cleanup();app.exit(0);
|
||||
}catch{win?.destroy();clearTimeout(watchdog);cleanup();app.exit(1);}
|
||||
}).catch(()=>app.exit(1));
|
||||
app.on('will-quit',cleanup);
|
||||
@@ -0,0 +1,201 @@
|
||||
'use strict';
|
||||
if (!process.versions.electron) {
|
||||
const fs = require('node:fs'), path = require('node:path'), os = require('node:os');
|
||||
const { spawn } = require('node:child_process');
|
||||
const owned = fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-ui-showcase-'));
|
||||
const env = { ...process.env, SAFETYNET_SHOWCASE_PROFILE: owned }; delete env.ELECTRON_RUN_AS_NODE;
|
||||
const child = spawn(require('electron'), [__filename], { stdio: 'inherit', windowsHide: true, env });
|
||||
const deadline = setTimeout(() => child.kill(), 90_000);
|
||||
const finish = code => {
|
||||
clearTimeout(deadline);
|
||||
const resolved = path.resolve(owned);
|
||||
if (!resolved.startsWith(path.resolve(os.tmpdir()) + path.sep) || !path.basename(resolved).startsWith('safetynet-ui-showcase-')) throw Error('Unsafe showcase cleanup');
|
||||
fs.rmSync(resolved, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
||||
process.exit(code ?? 1);
|
||||
};
|
||||
child.once('error', () => finish(1)); child.once('exit', finish);
|
||||
} else {
|
||||
// Finite test harness for the real trusted shell. No production browser startup,
|
||||
// site view, real credentials, network browsing or external application state.
|
||||
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const assert = require('node:assert/strict');
|
||||
const { capturePNG } = require('../apps/browser/visual-capture.cjs');
|
||||
app.disableHardwareAcceleration(); // Windows fixture uses the CPU compositor; production keeps its sandboxed defaults.
|
||||
app.commandLine.appendSwitch('force-device-scale-factor', '1');
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const profile = process.env.SAFETYNET_SHOWCASE_PROFILE || fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-ui-showcase-'));
|
||||
if (!profile.startsWith(path.resolve(os.tmpdir()) + path.sep)) throw Error('Unsafe showcase profile');
|
||||
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 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 = [
|
||||
{ title: 'Apple Documentation', url: 'https://developer.apple.com/' },
|
||||
{ title: 'GitHub', url: 'https://github.com/' },
|
||||
{ title: 'MDN', url: 'https://developer.mozilla.org/' },
|
||||
{ title: 'Wikipedia', url: 'https://wikipedia.org/' },
|
||||
{ title: 'Company Intranet', url: 'https://intranet.example.test/' },
|
||||
{ title: 'Design Library', url: 'https://design.example.test/' }
|
||||
];
|
||||
let state = { ready: true, enrolled: true, brand: { name: 'SafetyNet' }, url: 'https://example.test', pageTitle: 'Reference Workspace', pageColor: '#8aacb4', canNavigate: true, policyVersion: 12, message: 'Public fixture · centrally managed workspace', aiEnabled: true, agentsEnabled: true, documentScanEnabled: true, passwordManagerEnabled: true, vaultLocked: false };
|
||||
let nextTab=1;state.tabsRevision=0;state.tabs=[{id:'showcase-native-1',url:state.url,title:state.pageTitle,color:state.pageColor,group:'',startPage:false,canGoBack:false,canGoForward:false}];state.activeTabId=state.tabs[0].id;
|
||||
function snapshot(){const tab=state.tabs.find(tab=>tab.id===state.activeTabId);state={...state,url:tab.url,pageTitle:tab.title,pageColor:tab.color,tabsRevision:state.tabsRevision+1};return JSON.parse(JSON.stringify(state));}
|
||||
function publish(sender){const result=snapshot();sender.send('showcase:state',result);return result;}
|
||||
state.settings={available:true,visible:true,hiddenSections:[],lockedSections:[],appearance:{mode:'system',accent:'blue',tabMode:'separate'}};
|
||||
state.bookmarks={managed:[{...fixtures[4],id:'showcase-company',folder:'Company'}],personal:{revision:0,items:fixtures.filter((_,i)=>i!==4).map((item,i)=>({...item,id:`showcase-${i}`,folder:'Work'}))},allowUserAdd:true,allowUserEdit:true,allowUserDelete:true,adminReadUserBookmarks:true};
|
||||
let vaultLocked = false;
|
||||
const records = [], screenshots = [], consoleErrors = [];
|
||||
let window, finished = false;
|
||||
function cleanup() {
|
||||
if (path.resolve(profile).startsWith(path.resolve(os.tmpdir()) + path.sep) && path.basename(profile).startsWith('safetynet-ui-showcase-')) {
|
||||
try { fs.rmSync(profile, { recursive: true, force: true }); } catch {}
|
||||
}
|
||||
}
|
||||
if (!process.env.SAFETYNET_SHOWCASE_PROFILE) app.on('quit', cleanup);
|
||||
const watchdog = setTimeout(() => { console.error('UI showcase exceeded 80 seconds'); app.exit(1); }, 80_000);
|
||||
watchdog.unref();
|
||||
ipcMain.handle('showcase:call', (event, method, args) => {
|
||||
if (event.sender !== window?.webContents || !methods.includes(method)) return { ok: false, error: 'Showcase operation refused' };
|
||||
let data = null;
|
||||
const navigation=typeof args[0]==='object'?args[0]:{url:args[0],tabId:state.activeTabId};
|
||||
if(method==='navigate'&&navigation.url==='https://example.test/slow'){const id=navigation.tabId;return new Promise(resolve=>setTimeout(()=>{const tab=state.tabs.find(tab=>tab.id===id);if(tab){tab.url=navigation.url;tab.title='Delayed public fixture';tab.startPage=false;}resolve({ok:true,data:publish(event.sender)});},250));}
|
||||
if (method === 'state' || ['enroll', 'sso', 'back', 'forward', 'reload'].includes(method)) data = snapshot();
|
||||
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==='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;}
|
||||
else if(method==='authorizeSetting'){if(!state.settings.available||!state.settings.visible||state.settings.hiddenSections.includes(args[0])||state.settings.lockedSections.includes(args[0]))return{ok:false,error:'Managed setting refused'};data={allowed:true};}
|
||||
else if (method === 'navigate') { const url = new URL(navigation.url); assert.equal(url.protocol, 'https:');const tab=state.tabs.find(tab=>tab.id===navigation.tabId);assert.ok(tab);tab.url=url.href;tab.title=fixtures.find(item=>item.url===url.href)?.title||'Reference Workspace';tab.startPage=false;data=publish(event.sender); }
|
||||
else if (method === 'setChromeLayout') {
|
||||
const layout = args[0]; assert.ok([64, 104].includes(layout.height));
|
||||
for (const key of ['sidebarOpen', 'startPageOpen', 'overviewOpen', 'toolsOpen']) assert.equal(typeof layout[key], 'boolean');
|
||||
records.push({ method, ...layout, nativeHidden: layout.startPageOpen || layout.overviewOpen || layout.toolsOpen }); data = { applied: true };
|
||||
} else if (method === 'windowControls') { assert.ok(['minimize', 'maximize', 'close'].includes(args[0])); records.push({ method, action: args[0], nativeFlag: false }); data = { recorded: true }; }
|
||||
else if (method === 'shareURL') data = { shared: true };
|
||||
else if (method === 'toggleAI' || method === 'toggleVault') { records.push({ method, open: args[0] }); data = { applied: true }; }
|
||||
else if (method === 'vaultStatus') data = { enabled: true, exists: true, locked: vaultLocked, allowSaving: true, allowAutofill: true, idleLockSeconds: 300 };
|
||||
else if (method === 'vaultList') data = [{ id: 'public-fixture', origin: 'https://example.test', username: 'example-user', label: 'Public demonstration account' }];
|
||||
else if (method === 'vaultLock') { vaultLocked = true; data = { locked: true }; }
|
||||
else if (method === 'vaultCreate' || method === 'vaultUnlock') { vaultLocked = false; data = { locked: false }; }
|
||||
else if (method === 'vaultGenerate') data = 'PUBLIC-DEMONSTRATION-ONLY';
|
||||
else if (method === 'vaultSave' || method === 'vaultDelete' || method === 'vaultFill') data = { completed: true };
|
||||
else if (method === 'ai') data = { answer: 'Public local fixture response.' };
|
||||
else if (method === 'agentConnection') data = { address: 'http://127.0.0.1:0', credentialPath: 'Public showcase · no pairing credential' };
|
||||
return { ok: true, data };
|
||||
});
|
||||
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
async function execute(source) { return window.webContents.executeJavaScript(source, true); }
|
||||
async function reloadShell() {
|
||||
const loaded = new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(Error('Showcase shell reload timed out')), 5000);
|
||||
window.webContents.once('did-finish-load', () => { clearTimeout(timer); resolve(); });
|
||||
});
|
||||
window.webContents.reload(); await loaded; await delay(100);
|
||||
}
|
||||
async function waitFor(source, expected) {
|
||||
for (let attempt = 0; attempt < 40; attempt++) { if (await execute(source) === expected) return; await delay(70); }
|
||||
assert.equal(await execute(source), expected);
|
||||
}
|
||||
async function click(id) { await execute(`document.getElementById(${JSON.stringify(id)}).click()`); await delay(100); }
|
||||
async function appearance(theme, mode) {
|
||||
await execute(`for(const [id,value] of ${JSON.stringify([['theme-mode', theme], ['tab-mode', mode]])}){const element=document.getElementById(id);element.value=value;element.dispatchEvent(new Event('change',{bubbles:true}));}`); await delay(100);
|
||||
}
|
||||
async function accent(value) {
|
||||
await execute(`(()=>{const selector=document.getElementById('accent-mode');selector.value=${JSON.stringify(value)};selector.dispatchEvent(new Event('change',{bubbles:true}));})()`); await delay(90);
|
||||
}
|
||||
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)}})()`);
|
||||
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);
|
||||
for (const panel of ['ai', 'vault']) if (!measurements[panel].hidden) assert.equal(measurements[panel].width, 360);
|
||||
// This Windows host's native capturePage can return an earlier compositor frame.
|
||||
// Exercise the existing trusted screenshot-only CDP fallback for finite fixtures.
|
||||
const capturePage = window.webContents.capturePage;
|
||||
let png;
|
||||
try { window.webContents.capturePage = async () => { throw Error('Use finite showcase compositor fallback'); }; png = await capturePNG(window.webContents); }
|
||||
finally { window.webContents.capturePage = capturePage; }
|
||||
fs.writeFileSync(path.join(output, name + '.png'), png); screenshots.push({ name, ...measurements });
|
||||
}
|
||||
app.whenReady().then(async () => {
|
||||
window = new BrowserWindow({ width: 1320, height: 900, useContentSize: true, frame: false, show: true, backgroundColor: '#f7f8fa', webPreferences: { preload, contextIsolation: true, sandbox: true, nodeIntegration: false, zoomFactor: 1 } });
|
||||
window.webContents.setZoomFactor(1);
|
||||
window.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) consoleErrors.push(event.message); });
|
||||
window.webContents.on('will-navigate', event => event.preventDefault());
|
||||
window.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
||||
await window.loadFile(path.join(root, 'apps/browser/index.html'));
|
||||
await execute(`localStorage.clear();localStorage.setItem('sbrowser.bookmarks',${JSON.stringify(JSON.stringify(fixtures))});localStorage.setItem('sbrowser.theme','"light"');localStorage.setItem('sbrowser.tab-mode','"separate"');localStorage.setItem('sbrowser.accent','{"toString":0}');`);
|
||||
await reloadShell();
|
||||
assert.equal(await execute(`document.documentElement.dataset.accent`), 'blue');
|
||||
await appearance('auto', 'separate'); nativeTheme.themeSource = 'dark';
|
||||
await waitFor(`document.documentElement.dataset.effectiveTheme`, 'dark');
|
||||
nativeTheme.themeSource = 'light';
|
||||
await waitFor(`document.documentElement.dataset.effectiveTheme`, 'light');
|
||||
await appearance('dark', 'separate'); nativeTheme.themeSource = 'light'; await delay(90);
|
||||
assert.equal(await execute(`document.body.dataset.theme`), 'dark');
|
||||
await accent('teal'); await reloadShell();
|
||||
assert.equal(await execute(`document.documentElement.dataset.accent`), 'teal');
|
||||
assert.equal(await execute(`document.documentElement.dataset.theme`), 'dark');
|
||||
assert.equal(await execute(`document.documentElement.style.getPropertyValue('--accent')`), '#007d76');
|
||||
await accent('blue'); await appearance('light', 'separate');
|
||||
// Real DOM interactions verify tabs, bookmarks, themes and overlay signaling.
|
||||
await click('tools-toggle'); await click('bookmark');
|
||||
await execute(`document.getElementById('bookmark-form').requestSubmit()`);
|
||||
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');
|
||||
assert.equal(await execute(`document.querySelectorAll('#favorites-grid .favorite-card').length`), 7);
|
||||
await shot('sbrowser-light');
|
||||
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');
|
||||
await appearance('dark', 'separate'); await accent('violet'); await shot('sbrowser-theme-violet');
|
||||
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 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');
|
||||
assert.equal(await execute(`document.getElementById('address').value`), '');
|
||||
assert.equal(await execute(`document.getElementById('start-page').hidden`), false);
|
||||
const latestLayout=records.findLast(record=>record.method==='setChromeLayout');assert.ok(latestLayout.startPageOpen&&latestLayout.nativeHidden);
|
||||
for (const overlay of ['startPageOpen', 'overviewOpen', 'toolsOpen']) assert.ok(records.some(record => record.method === 'setChromeLayout' && record[overlay] && record.nativeHidden));
|
||||
// Policy updates override stale or hostile renderer preferences immediately.
|
||||
state={...state,settings:{...state.settings,lockedSections:['appearance','ai','passwords','documentScan','agents'],appearance:{mode:'dark',accent:'violet',tabMode:'compact'}}};
|
||||
window.webContents.send('showcase:state',state);await delay(120);
|
||||
await execute(`localStorage.setItem('sbrowser.theme','"light"');localStorage.setItem('sbrowser.accent','"blue"');window.dispatchEvent(new StorageEvent('storage',{key:'sbrowser.theme'}));`);
|
||||
assert.equal(await execute(`document.body.dataset.theme`),'dark');
|
||||
assert.equal(await execute(`document.documentElement.dataset.accent`),'violet');
|
||||
assert.equal(await execute(`['theme-mode','accent-mode','tab-mode','ai-toggle','passwords','inspect','agents'].every(id=>document.getElementById(id).disabled)`),true);
|
||||
assert.equal((await execute(`window.safetynet.updateSettings({mode:'light',accent:'blue',tabMode:'separate'})`)).ok,false);
|
||||
state={...state,settings:{...state.settings,lockedSections:[],hiddenSections:['appearance','ai','passwords','documentScan','agents'],visible:true}};
|
||||
window.webContents.send('showcase:state',state);await delay(100);
|
||||
assert.equal(await execute(`['appearance-section','ai-toggle','sidebar','passwords','inspect','agents'].every(id=>document.getElementById(id).hidden)`),true);
|
||||
state={...state,settings:{...state.settings,visible:false}};window.webContents.send('showcase:state',state);await delay(100);
|
||||
assert.equal(await execute(`document.getElementById('tools-toggle').hidden&&document.getElementById('tools-menu').hidden`),true);
|
||||
state={...state,settings:{...state.settings,hiddenSections:['appearance']}};window.webContents.send('showcase:state',state);await delay(100);
|
||||
assert.equal(await execute(`!document.getElementById('quick-ai').hidden&&!document.getElementById('quick-ai').disabled&&document.getElementById('appearance-section').hidden`),true);
|
||||
await click('quick-ai');assert.equal(await execute(`document.getElementById('ai').hidden`),false);await click('ai-close');
|
||||
await reloadShell();assert.equal(await execute(`document.getElementById('tools-toggle').hidden&&document.body.dataset.theme==='dark'`),true);
|
||||
await execute(`document.getElementById('error').textContent=''`);
|
||||
for (const control of ['window-minimize', 'window-maximize', 'window-close']) await click(control);
|
||||
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.');
|
||||
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); });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import {createRequire} from 'node:module';
|
||||
import {spawn} from 'node:child_process';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import assert from 'node:assert/strict';
|
||||
const require=createRequire(import.meta.url);
|
||||
const {startAgentControl}=require('../apps/browser/agent-control.cjs');
|
||||
const {startInspector}=require('../apps/browser/inspection.cjs');
|
||||
const root=path.resolve('dist/win-unpacked');
|
||||
if(process.platform!=='win32')throw Error('Windows package verification requires Windows');
|
||||
const {extractFile}=require('@electron/asar');
|
||||
for(const file of ['apps/browser/policy-assignment.cjs','packages/core/policy-groups.mjs','apps/browser/tab-registry.cjs','apps/browser/agent-adapters.cjs','apps/browser/agent-control.cjs','apps/browser/password-fill.cjs','apps/browser/diagnostics.cjs','packages/core/diagnostics.mjs','packages/core/security.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged device/session policy source is stale: ${file}`);
|
||||
for(const file of ['apps/browser/main.cjs','apps/browser/preload.cjs','apps/browser/site-preload.cjs','apps/browser/clipboard-deny.cjs','apps/browser/clipboard-guard.cjs','apps/browser/clipboard-dom.cjs','apps/browser/page-clipboard-guard.cjs','apps/browser/startup-security.cjs','apps/browser/visual-capture.cjs','apps/browser/renderer.js','apps/browser/index.html','apps/browser/style.css','packages/core/clipboard.mjs']){
|
||||
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged source is stale: ${file}`);
|
||||
}
|
||||
for(const file of ['apps/browser/inspection.cjs','apps/browser/managed-settings.cjs','apps/browser/management-channel.cjs','apps/browser/enterprise-inspection.cjs','apps/browser/icap-client.cjs','apps/browser/network-posture.cjs','apps/browser/download-gate.cjs','packages/core/enterprise.mjs','packages/core/semantic.mjs','packages/core/managed-settings.mjs','packages/core/policy-channel.cjs','packages/core/policy.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged source is stale: ${file}`);
|
||||
for(const file of ['semantic.py','engine.py','server.py','windows_containment.py','windows_appcontainer.py','sandbox_worker.py','provision_runtime_acl.py'])assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector',file)),await fs.readFile(path.join('apps/inspector',file)),`Packaged inspector is stale: ${file}`);
|
||||
const temporary=await fs.mkdtemp(path.join(os.tmpdir(),'safetynet-package-'));
|
||||
for(const file of ['apps/browser/bookmarks.cjs','packages/core/bookmarks.mjs'])assert.ok(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)).equals(await fs.readFile(file)),`Packaged bookmark source is stale: ${file}`);
|
||||
function run(executable,args,input,timeoutMs=15000){return new Promise((resolve,reject)=>{
|
||||
const child=spawn(executable,args,{stdio:['pipe','pipe','pipe'],windowsHide:true,env:{...process.env,PYTHONDONTWRITEBYTECODE:'1'}});let output='',errors='';
|
||||
const timer=setTimeout(()=>{child.kill();reject(Error('Package verification timed out'));},timeoutMs);
|
||||
child.on('error',reject);child.stdout.on('data',data=>output+=data);child.stderr.on('data',data=>errors+=data);
|
||||
child.on('close',code=>{clearTimeout(timer);resolve({code,output,errors});});child.stdin.end(input);
|
||||
});}
|
||||
let service;
|
||||
try{
|
||||
const inspectorBytecode=path.join(root,'resources/inspector/__pycache__');
|
||||
const bytecodeExists=()=>fs.stat(inspectorBytecode).then(()=>true,error=>{if(error.code==='ENOENT')return false;throw error;});
|
||||
assert.equal(await bytecodeExists(),false,'Package must start with no inspector bytecode cache');
|
||||
const packagedInspector=startInspector(path.join(root,'resources'),true);
|
||||
assert.ok(packagedInspector,'Packaged inspector runtime is missing');
|
||||
try{
|
||||
const verdict=await packagedInspector.inspect(Buffer.from('Public package verification text'),{dlp:{maxBytes:1048576,rules:[],semantic:{enabled:false}}});
|
||||
assert.equal(verdict.decision,'allow','Packaged contained inspector failed');
|
||||
assert.equal(verdict.coverage.complete,true);
|
||||
}finally{packagedInspector.stop();}
|
||||
const semanticPath=path.join(root,'resources/semantic');
|
||||
const actual=await run(path.join(root,'resources/python/python.exe'),['-c','import sys; sys.path.insert(0,sys.argv[1]); from semantic import NativeSemanticModel; m=NativeSemanticModel(sys.argv[2],sys.argv[3]); vectors=m.text_vectors("Public information about weather forecasting."); assert len(vectors)>=1 and len(vectors[0])==768; m.close(); print("Offline packaged semantic CPU inference verified.")',path.join(root,'resources/inspector'),path.join(semanticPath,'models'),path.join(semanticPath,'searchi-gemma-worker.exe')],undefined,45000);
|
||||
assert.equal(actual.code,0,'Packaged offline semantic inference failed');assert.ok(actual.output.includes('Offline packaged semantic CPU inference verified.'));
|
||||
service=await startAgentControl({authorize:async()=>({allowed:true,role:'agent',scopes:['sessions:list']}),audit:async()=>{},actions:{sessions:async()=>[{id:'managed',url:'https://example.test/'}]}});
|
||||
const encrypted=await run(path.join(root,'resources/python/python.exe'),[path.join(root,'resources/inspector/dpapi.py'),'protect'],JSON.stringify({address:service.address,token:service.token}));
|
||||
assert.equal(encrypted.code,0,'Packaged DPAPI failed');
|
||||
const connection=path.join(temporary,'connection.json'),request=path.join(temporary,'request.json');
|
||||
await fs.writeFile(connection,JSON.stringify({format:'windows-dpapi-v1',protectedConnection:encrypted.output}));
|
||||
await fs.writeFile(request,JSON.stringify({action:'sessions'}));
|
||||
const result=await run(path.join(root,'SafetyNet.exe'),[`--agent-connection=${connection}`,`--agent-action=${request}`]);
|
||||
assert.equal(result.code,0,'Packaged agent helper failed');assert.equal(JSON.parse(result.output).result[0].id,'managed');assert.equal(result.output.includes(service.token),false);
|
||||
assert.equal(await bytecodeExists(),false,'Cold packaged startup must not create inspector bytecode files');
|
||||
console.log('Packaged security sources, Windows-contained inspector, offline EmbeddingGemma 2 CPU inference, Windows agent helper and bundled DPAPI runtime passed.');
|
||||
}finally{
|
||||
await service?.close();
|
||||
if(!temporary.startsWith(path.join(os.tmpdir(),'safetynet-package-')))throw Error('Unexpected temporary directory');
|
||||
await fs.rm(temporary,{recursive:true,force:true});
|
||||
}
|
||||
Reference in new issue
Block a user