Implement managed browser security, individual agents and observability
This commit is contained in:
1 parent
ff482ebe23
commit
ea448de2d4
172 files changed
+5404
-347
No files matched your search
@@ -28,12 +28,46 @@ app.whenReady().then(async()=>{
|
||||
const blocked=await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:'Secret Roadmap'}).then(()=>false).catch(()=>true)`);assert.equal(blocked,true);
|
||||
const counts=await (await fetch(url+'/count')).json();assert.equal(counts.posts,1,'Secret payload reached the destination');
|
||||
log('Request blocking passed');
|
||||
// Use the already attached privileged page guard; no additional debugging port.
|
||||
const fileAuditHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};
|
||||
const filePriorIds=new Set((await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:fileAuditHeaders})).json()).events.map(e=>e.id));
|
||||
await page.executeJavaScript('(()=>{const input=document.createElement("input");input.type="file";input.id="native-upload-fixture";document.body.append(input);})()');
|
||||
const fileDocument=await page.debugger.sendCommand('DOM.getDocument');
|
||||
const fileNode=await page.debugger.sendCommand('DOM.querySelector',{nodeId:fileDocument.root.nodeId,selector:'#native-upload-fixture'});
|
||||
async function nativeFileUpload(file){await page.debugger.sendCommand('DOM.setFileInputFiles',{nodeId:fileNode.nodeId,files:[file]});return page.executeJavaScript(`(()=>{const data=new FormData();data.append('file',document.getElementById('native-upload-fixture').files[0]);return fetch(${JSON.stringify(url+'/file-upload-sink')},{method:'POST',body:data}).then(r=>r.text()).catch(()=>false);})()`);}
|
||||
const fileBefore=(await (await fetch(url+'/count')).json()).fileUploadRequests;
|
||||
assert.equal(await nativeFileUpload(process.env.SAFETYNET_SMOKE_PUBLIC_UPLOAD),'file received','Clean native file upload was blocked');
|
||||
const fileAfter=(await (await fetch(url+'/count')).json());assert.equal(fileAfter.fileUploadRequests,fileBefore+1);assert.equal(fileAfter.fileUploadExact,true,'Actual file bytes differed at receiver');
|
||||
assert.equal(await nativeFileUpload(process.env.SAFETYNET_SMOKE_SECRET_UPLOAD),false,'Confidential native file upload escaped');
|
||||
assert.equal((await (await fetch(url+'/count')).json()).fileUploadRequests,fileBefore+1,'Blocked native file upload reached receiver');
|
||||
let fileEvents;await clipboardWait(async()=>{fileEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:fileAuditHeaders})).json()).events.filter(e=>!filePriorIds.has(e.id));return fileEvents.some(e=>e.kind==='upload.authorized')&&fileEvents.some(e=>e.kind==='upload.block'&&e.metadata.ruleId==='secret-roadmap');});
|
||||
const fileAuditText=JSON.stringify(fileEvents),fileCrypto=require('node:crypto');
|
||||
for(const fixture of [process.env.SAFETYNET_SMOKE_PUBLIC_UPLOAD,process.env.SAFETYNET_SMOKE_SECRET_UPLOAD]){const bytes=fs.readFileSync(fixture);assert.equal(fileAuditText.includes(bytes.toString('utf8').trim()),false,'File contents entered ordinary audit');assert.equal(fileAuditText.includes(fileCrypto.createHash('sha256').update(bytes).digest('hex')),false,'File hash entered ordinary request audit');bytes.fill(0);}
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'native-file-upload-proof.json'),JSON.stringify({actualElectron:true,cleanFileReceivedExactlyOnce:true,exactFileBytesPreserved:true,secretFileNeverReachedReceiver:true,contentsAndHashesAbsentFromAudit:true,traceIds:fileEvents.map(e=>e.metadata.traceId).filter(Boolean)},null,2));
|
||||
await page.executeJavaScript('document.getElementById("native-upload-fixture").remove()');
|
||||
log('Managed native file upload receiver and confidentiality proof passed');
|
||||
|
||||
// Native Chromium header inspection must run before the dedicated receiver sees bytes.
|
||||
const headerAuditHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};
|
||||
const headerPriorIds=new Set((await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:headerAuditHeaders})).json()).events.map(e=>e.id));
|
||||
const headerBefore=(await (await fetch(url+'/count')).json()).headerRequests;
|
||||
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(url+'/header-sink')},{headers:{'X-SafetyNet-Test':'Secret Roadmap'}}).then(()=>false).catch(()=>true)`),true,'Secret custom header escaped inspection');
|
||||
assert.equal((await (await fetch(url+'/count')).json()).headerRequests,headerBefore,'Blocked header request reached receiver');
|
||||
assert.equal(await page.executeJavaScript(`fetch(${JSON.stringify(url+'/header-sink')},{headers:{'X-SafetyNet-Test':'Public header fixture'}}).then(r=>r.text())`),'header received','Clean custom header was blocked');
|
||||
assert.equal((await (await fetch(url+'/count')).json()).headerRequests,headerBefore+1,'Clean header request did not arrive exactly once');
|
||||
let headerEvents,headerDecision;
|
||||
await clipboardWait(async()=>{headerEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:headerAuditHeaders})).json()).events;headerDecision=headerEvents.find(e=>!headerPriorIds.has(e.id)&&e.kind==='dlp.block'&&e.metadata.ruleId==='secret-roadmap'&&(!process.env.SAFETYNET_TEST_OTLP_ENDPOINT||e.metadata.traceId));return !!headerDecision;});
|
||||
assert.equal(JSON.stringify(headerEvents).includes('Secret Roadmap'),false,'Header secret appeared in audit');
|
||||
assert.equal(JSON.stringify(headerEvents).includes('Public header fixture'),false,'Allowed header value appeared in audit');
|
||||
assert.equal(JSON.stringify(headerEvents).includes('X-SafetyNet-Test'),false,'Header name appeared in audit');
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'request-headers-native-proof.json'),JSON.stringify({actualElectron:true,blockedHeaderNeverReachedReceiver:true,cleanHeaderReceivedOnce:true,headerNamesAndValuesAbsentFromAudit:true,traceId:headerDecision.metadata.traceId},null,2));
|
||||
log('Native custom header DLP blocks before transmission and preserves clean requests');
|
||||
// Real Chromium gestures and the Windows clipboard: never execute fixture commands.
|
||||
const {clipboard}=require('electron');
|
||||
const originalClipboardText=await clipboard.readText();
|
||||
const clipboardSentinel='SafetyNet public clipboard fixture';
|
||||
async function clipboardKey(key,shift=false){page.focus();page.sendInputEvent({type:'keyDown',keyCode:key,modifiers:shift?['shift']:['control']});page.sendInputEvent({type:'keyUp',keyCode:key,modifiers:shift?['shift']:['control']});await wait(800);await clipboardWait(async()=>!(await win.webContents.executeJavaScript('window.safetynet.state()')).data.clipboardPending);}
|
||||
async function clipboardWait(predicate){for(let attempt=0;attempt<200;attempt++){if(await predicate())return;await wait(100);}throw Error('Clipboard fixture operation timed out');}
|
||||
async function clipboardWait(predicate,stage='Native fixture'){for(let attempt=0;attempt<200;attempt++){if(await predicate())return;await wait(100);}throw Error(`${stage} operation timed out`);}
|
||||
async function selectClipboardText(text){await page.executeJavaScript(`(()=>{let node=document.getElementById('clipboard-source');if(!node){node=document.createElement('pre');node.id='clipboard-source';document.body.append(node)}node.textContent=${JSON.stringify(text)};const range=document.createRange();range.selectNodeContents(node);const selection=getSelection();selection.removeAllRanges();selection.addRange(range);})()`);}
|
||||
try{
|
||||
await clipboard.writeText(clipboardSentinel);
|
||||
@@ -73,7 +107,9 @@ app.whenReady().then(async()=>{
|
||||
}finally{await clipboard.writeText(originalClipboardText);await page.executeJavaScript("document.getElementById('clipboard-source')?.remove();document.getElementById('public-input').value=''");}
|
||||
const connection=await win.webContents.executeJavaScript('window.safetynet.agentConnection()');assert.equal(connection.ok,true,connection.error);
|
||||
const agentRequest=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'agent-request.json');
|
||||
let agentWindowStarted=Date.now(),agentCalls=0;
|
||||
async function localAgent(body){
|
||||
if(++agentCalls>20){const remaining=Math.max(0,61000-(Date.now()-agentWindowStarted));await wait(Math.min(remaining,30000));if(remaining>30000)await wait(remaining-30000);agentWindowStarted=Date.now();agentCalls=1;}
|
||||
fs.writeFileSync(agentRequest,JSON.stringify(body));
|
||||
const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.data.credentialPath,'--request',agentRequest],{stdio:['ignore','pipe','pipe'],windowsHide:true});let output='',errors='';child.stdout.on('data',c=>output+=c);child.stderr.on('data',c=>errors+=c);
|
||||
const exit=await new Promise((resolve,reject)=>{child.on('exit',resolve);child.on('error',reject);});return {exit,output,errors};
|
||||
@@ -82,8 +118,30 @@ app.whenReady().then(async()=>{
|
||||
const readResult=await localAgent({action:'read',args:{sessionId}});assert.equal(readResult.exit,0,readResult.errors);assert.equal(readResult.output.includes('never-export-this'),false);
|
||||
const typeResult=await localAgent({action:'type',args:{sessionId,selector:'#public-input',text:'Agent typed public text'}});assert.equal(typeResult.exit,0,typeResult.errors);assert.equal(await page.executeJavaScript('document.getElementById("public-input").value'),'Agent typed public text');
|
||||
const deniedType=await localAgent({action:'type',args:{sessionId,selector:'#private-input',text:'forbidden'}});assert.equal(deniedType.exit,1);
|
||||
const inspectResult=await localAgent({action:'inspect',args:{sessionId}});assert.equal(inspectResult.exit,0,inspectResult.errors);const inspected=JSON.parse(inspectResult.output).result;assert.ok(inspected.controls.some(control=>control.selector==='#public-input'&&control.value==='Agent typed public text'));assert.equal(inspectResult.output.includes('never-export-this'),false);
|
||||
const scrollResult=await localAgent({action:'scroll',args:{sessionId,x:0,y:100}});assert.equal(scrollResult.exit,0,scrollResult.errors);
|
||||
log('Authenticated agent sessions, sanitized read, type and sensitive-field denial passed');
|
||||
let structuredAgentEvents;
|
||||
await clipboardWait(async()=>{structuredAgentEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:{authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`}})).json()).events.filter(e=>e.metadata.agent);return structuredAgentEvents.some(e=>e.metadata.agent.action==='read'&&e.metadata.agent.outcome==='completed')&&structuredAgentEvents.some(e=>e.metadata.agent.action==='type'&&e.kind==='agent.denied'&&['denied','failed'].includes(e.metadata.agent.outcome));},'Structured agent audit');
|
||||
const completedRead=structuredAgentEvents.find(e=>e.metadata.agent.action==='read'&&e.metadata.agent.outcome==='completed');
|
||||
assert.equal(completedRead.metadata.agent.scope,'browser:read');assert.equal(completedRead.metadata.agent.sessionId,sessionId);
|
||||
assert.equal(await page.executeJavaScript('document.getElementById("private-input").value'),'never-export-this','Blocked agent changed a sensitive field');
|
||||
const blockedType=structuredAgentEvents.find(e=>e.metadata.agent.action==='type'&&e.kind==='agent.denied'&&['denied','failed'].includes(e.metadata.agent.outcome));
|
||||
const serializedAgentEvents=JSON.stringify(structuredAgentEvents);
|
||||
for(const privateValue of ['Agent typed public text','never-export-this','forbidden'])assert.equal(serializedAgentEvents.includes(privateValue),false,'Agent arguments reached durable audit');
|
||||
assert.ok(structuredAgentEvents.every(e=>!Object.hasOwn(e.metadata.agent,'principalId')),'Shared capability fabricated individual identity');
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'structured-agent-audit-native-proof.json'),JSON.stringify({actualElectron:true,completedReadHasScopeAndSession:true,blockedActionRecorded:true,blockedActionOutcome:blockedType.metadata.agent.outcome,sensitiveFieldUnchanged:true,noArgumentsOrFabricatedIdentity:true,traceId:completedRead.metadata.traceId,eventId:completedRead.id},null,2));
|
||||
async function agentApp(action,args={}){const response=await localAgent({action,args});assert.equal(response.exit,0,response.errors);return JSON.parse(response.output).result;}
|
||||
await agentApp('select',{sessionId,selector:'#public-select',value:'two'});assert.equal(await page.executeJavaScript('document.getElementById("public-select").value'),'two');
|
||||
const lockedSelect=await localAgent({action:'select',args:{sessionId,selector:'#public-select',value:'locked'}});assert.equal(lockedSelect.exit,1);
|
||||
await agentApp('check',{sessionId,selector:'#public-checkbox',checked:true});assert.equal(await page.executeJavaScript('document.getElementById("public-checkbox").checked'),true);
|
||||
await agentApp('check',{sessionId,selector:'#public-checkbox',checked:false});assert.equal(await page.executeJavaScript('document.getElementById("public-checkbox").checked'),false);
|
||||
await agentApp('navigate',{sessionId,url:url+'/history-second'});assert.ok(page.getURL().endsWith('/history-second'));
|
||||
await agentApp('back',{sessionId});assert.equal(new URL(page.getURL()).pathname,'/');
|
||||
await agentApp('forward',{sessionId});assert.ok(page.getURL().endsWith('/history-second'));
|
||||
await agentApp('reload',{sessionId});assert.ok(page.getURL().endsWith('/history-second'));
|
||||
await agentApp('back',{sessionId});assert.equal(new URL(page.getURL()).pathname,'/');
|
||||
log('PQ agent history back/forward and same-URL reload passed');
|
||||
const appStatus=await agentApp('application:state');assert.equal(appStatus.activeTabId,sessionId);
|
||||
await agentApp('settings:update',{appearance:{mode:'dark'}});assert.equal((await agentApp('settings:get')).appearance.mode,'dark');
|
||||
const library=await agentApp('bookmarks:list');const bookmark={id:'agent-native-proof',title:'Public fixture bookmark',url:url+'/'};
|
||||
@@ -138,7 +196,7 @@ app.whenReady().then(async()=>{
|
||||
await win.webContents.executeJavaScript('document.getElementById("passwords").click()');await wait(200);
|
||||
assert.equal(await win.webContents.executeJavaScript('document.getElementById("vault").hidden'),false);
|
||||
try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'browser-vault.png'),(await win.webContents.capturePage()).toPNG());}catch{log('Vault screenshot unavailable');}
|
||||
win.emit('blur');await wait(100);assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultStatus()')).data.locked,true);
|
||||
win.emit('blur');await wait(100);const blurVaultStatus=await win.webContents.executeJavaScript('window.safetynet.vaultStatus()');if(!blurVaultStatus.ok){const failedState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;log('Vault status denial: '+JSON.stringify({error:blurVaultStatus.error,ready:failedState.ready,message:failedState.message,policyVersion:failedState.policyVersion}));}assert.equal(blurVaultStatus.ok,true,blurVaultStatus.error);assert.equal(blurVaultStatus.data.locked,true);
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultList()')).ok,false);
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.vaultUnlock("wrong-fixture-password-only")')).ok,false);
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.vaultUnlock(${JSON.stringify(fixtureMaster)})`)).ok,true);
|
||||
@@ -151,10 +209,20 @@ app.whenReady().then(async()=>{
|
||||
await page.executeJavaScript('document.getElementById("safe-download").click()');
|
||||
const safeFile=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'safe.bin');for(let n=0;n<600;n++){if(fs.existsSync(safeFile)&&fs.statSync(safeFile).size===5)break;await wait(100);}
|
||||
assert.equal(fs.existsSync(safeFile),true,'Scanned benign download was not released');assert.deepEqual(fs.readFileSync(safeFile),Buffer.from([0,1,2,3,255]));assert.equal(saveCalls,1);
|
||||
await clipboardWait(async()=>{const completed=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;return completed.ready&&completed.message==='Download scanned and saved.';});
|
||||
await page.executeJavaScript('document.getElementById("blocked-download").click()');
|
||||
for(let n=0;n<300;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.message.startsWith('Download blocked:'))break;await wait(100);}
|
||||
assert.equal(fs.existsSync(path.join(process.env.SAFETYNET_SMOKE_USERDATA,'blocked.bin')),false);assert.equal(saveCalls,1,'Blocked download reached save dialog');
|
||||
log('Real YARA download gate: safe binary saved; blocked file never reaches save dialog/filesystem');
|
||||
const evidenceHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};let recorded;
|
||||
await clipboardWait(async()=>{recorded=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:evidenceHeaders})).json()).events.filter(e=>e.kind==='evidence.recorded');return recorded.length>=2;});
|
||||
const captured=[];for(const event of recorded){const response=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/evidence/${event.metadata.evidenceId}?reason=investigation`,{headers:evidenceHeaders});assert.equal(response.status,200);captured.push((await response.json()).payload);}
|
||||
assert.ok(captured.some(p=>p.decision==='allow'&&p.name==='safe.bin'&&p.bytes===5&&p.sourceUrl===url+'/download-safe'));
|
||||
assert.ok(captured.some(p=>p.decision==='block'&&p.name==='blocked.bin'&&p.sourceUrl===url+'/download-blocked'));
|
||||
assert.ok(captured.every(p=>!p.content&&!p.data&&!p.filePath));
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'download-evidence-native-proof.json'),JSON.stringify({actualElectron:true,allowedDownloadCaptured:true,blockedDownloadCaptured:true,noFileContents:true,protectedAccessAudited:true,eventIds:recorded.map(e=>e.metadata.sourceEventId)},null,2));
|
||||
log('Native allowed/blocked download metadata captured in encrypted evidence with audited analyst access');
|
||||
|
||||
}finally{dialog.showSaveDialog=originalSave;}
|
||||
const sample=path.join(process.env.SAFETYNET_SMOKE_USERDATA,'secret-sample.docx');
|
||||
const runtime=path.resolve('.runtime/windows/python/python.exe');
|
||||
@@ -167,6 +235,16 @@ app.whenReady().then(async()=>{
|
||||
}
|
||||
await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:new Uint8Array([255,254])}).catch(()=>{})`);
|
||||
assert.equal((await (await fetch(url+'/count')).json()).posts,1,'Binary upload reached the destination');
|
||||
const requestAuditHeaders={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`};
|
||||
const credentialBody='password=synthetic-low-entropy-only';
|
||||
await page.executeJavaScript(`fetch(${JSON.stringify(url+'/sink')},{method:'POST',body:${JSON.stringify(credentialBody)}}).then(r=>r.text())`);
|
||||
let requestEvents;await clipboardWait(async()=>{requestEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers:requestAuditHeaders})).json()).events;return requestEvents.some(e=>e.kind==='upload.authorized'&&e.metadata.request?.bytes===Buffer.byteLength(credentialBody));});
|
||||
const credentialHash=require('node:crypto').createHash('sha256').update(credentialBody).digest('hex');
|
||||
assert.equal(JSON.stringify(requestEvents).includes(credentialBody),false,'Credential request body leaked into central events');
|
||||
assert.equal(JSON.stringify(requestEvents).includes(credentialHash),false,'Credential request fingerprint leaked into central events');
|
||||
assert.ok(requestEvents.filter(e=>e.kind.startsWith('upload.')).every(e=>!e.metadata.file),'Upload request bodies remained classified as hash-bearing files');
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'request-privacy-native-proof.json'),JSON.stringify({actualElectron:true,credentialRequestReceived:true,bodyNotLogged:true,bodyHashNotLogged:true,safeBytesRetained:true,traceId:requestEvents.find(e=>e.kind==='upload.authorized'&&e.metadata.request?.bytes===Buffer.byteLength(credentialBody))?.metadata.traceId},null,2));
|
||||
log('Real credential POST preserves byte count without body or fingerprint logging');
|
||||
log('Capturing browser');
|
||||
try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'browser.png'),(await win.webContents.capturePage()).toPNG());}catch(e){log('Screenshot unavailable: '+e.message);}
|
||||
log('Loading admin console');
|
||||
@@ -175,6 +253,7 @@ app.whenReady().then(async()=>{
|
||||
await admin.webContents.executeJavaScript(`document.getElementById('token').value=${JSON.stringify(process.env.SAFETYNET_SMOKE_ADMIN)};document.getElementById('connect').click();`);
|
||||
for(let n=0;n<30;n++){if(await admin.webContents.executeJavaScript('!document.getElementById("workspace").hidden'))break;await wait(100);}
|
||||
assert.equal(await admin.webContents.executeJavaScript('document.getElementById("workspace").hidden'),false);
|
||||
assert.equal(await admin.webContents.executeJavaScript('document.getElementById("evidence-policy-enabled").checked&&document.getElementById("evidence-policy-downloads").checked'),true,'Signed evidence policy missing from actual admin controls');
|
||||
try{fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'admin.png'),(await admin.webContents.capturePage()).toPNG());}catch(e){log('Screenshot unavailable: '+e.message);}
|
||||
const headers={authorization:`Bearer ${process.env.SAFETYNET_SMOKE_ADMIN}`,'content-type':'application/json'};
|
||||
const secondary=process.env.SAFETYNET_SMOKE_SECONDARY;
|
||||
@@ -189,6 +268,7 @@ app.whenReady().then(async()=>{
|
||||
const publication=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/policy`,{method:'PUT',headers,body:JSON.stringify(managedPolicy)});assert.equal(publication.status,200);
|
||||
for(let n=0;n<180;n++){const updated=await win.webContents.executeJavaScript('window.safetynet.state()');if(updated.data.ready&&updated.data.policyVersion===3)break;await wait(100);}
|
||||
const navigation=await win.webContents.executeJavaScript(`window.safetynet.navigate('http://proxy-check.invalid/')`);assert.equal(navigation.ok,true,navigation.error);
|
||||
for(let n=0;n<50;n++){if((await (await fetch(url+'/count')).json()).proxyRequests>0)break;await wait(100);}
|
||||
assert.ok((await (await fetch(url+'/count')).json()).proxyRequests>0,'Managed proxy did not receive browser navigation');log('Managed proxy enforcement passed');
|
||||
const reference='The confidential engineering roadmap contains restricted architecture budgets and unreleased customer contracts.';
|
||||
const draftResponse=await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/teaching`,{method:'POST',headers,body:JSON.stringify({label:'Internal engineering reference',filename:'internal.txt',data:Buffer.from(reference).toString('base64')})});assert.equal(draftResponse.status,200);
|
||||
@@ -236,9 +316,15 @@ app.whenReady().then(async()=>{
|
||||
for(let n=0;n<200;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(s.data.ready&&s.data.policyVersion===9&&!s.data.loginCheckPending)break;await wait(100);}
|
||||
assert.equal((await win.webContents.executeJavaScript('window.safetynet.state()')).data.ready,true,'Approved visual origin blocked');
|
||||
const postsBeforePhish=(await (await fetch(url+'/count')).json()).posts;
|
||||
await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url+'/visual-login')})`);
|
||||
for(let n=0;n<120;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready)break;await wait(100);}
|
||||
const phishingState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;assert.equal(phishingState.ready,false);assert.equal(phishingState.message,'Blocked: this login resembles a protected brand on an unapproved origin.');
|
||||
const phishingNavigation=await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(url+'/visual-login')})`);
|
||||
const expectedPhishingMessage='Blocked: this login resembles a protected brand on an unapproved origin.';
|
||||
for(let n=0;n<120;n++){const s=await win.webContents.executeJavaScript('window.safetynet.state()');if(!s.data.ready&&s.data.message===expectedPhishingMessage)break;await wait(100);}
|
||||
const phishingState=(await win.webContents.executeJavaScript('window.safetynet.state()')).data;
|
||||
const visualDiagnosticEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).events.filter(e=>e.kind==='phishing.block');
|
||||
// Only fixed synthetic fixture state and safe identifiers enter this artifact.
|
||||
const knownMessages=[expectedPhishingMessage,'Login protection could not approve this view. Credential actions are held.','Blocked: this login could not be inspected safely.'];
|
||||
fs.writeFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'visual-phishing-native-diagnostic.json'),JSON.stringify({stage:'unapproved-synthetic-login',navigationOK:phishingNavigation.ok===true,navigationErrorClass:phishingNavigation.error?.includes('ERR_BLOCKED_BY_CLIENT')?'ERR_BLOCKED_BY_CLIENT':phishingNavigation.error?'other-error':null,ready:phishingState.ready,policyVersion:phishingState.policyVersion,loginCheckPending:phishingState.loginCheckPending,message:knownMessages.includes(phishingState.message)?phishingState.message:'other-status',phishingEvents:visualDiagnosticEvents.map(e=>({id:e.id,ruleId:e.metadata.ruleId,traceId:e.metadata.traceId})),destinationPostsUnchanged:(await (await fetch(url+'/count')).json()).posts===postsBeforePhish},null,2));
|
||||
assert.equal(phishingState.ready,false,'Unapproved visual login did not enter quarantine; see visual-phishing-native-diagnostic.json');assert.equal(phishingState.message,expectedPhishingMessage);
|
||||
assert.equal((await win.webContents.executeJavaScript(`window.safetynet.navigate(${JSON.stringify(page.getURL()+'#same-document')})`)).ok,false,'Hash-only navigation escaped visual quarantine');
|
||||
let visualEvents;await clipboardWait(async()=>{visualEvents=(await (await fetch(`${process.env.SAFETYNET_SMOKE_SERVER}/api/admin/overview`,{headers})).json()).events;return visualEvents.some(e=>e.kind==='phishing.block'&&e.metadata.ruleId==='identity-lab');});assert.ok(visualEvents.some(e=>e.kind==='phishing.block'&&e.metadata.ruleId==='identity-lab'),'Visual brand decision was not audited');
|
||||
assert.equal((await (await fetch(url+'/count')).json()).posts,postsBeforePhish);
|
||||
|
||||
Reference in new issue
Block a user