Add inspected agent viewport export with policy-bound final release
This commit is contained in:
1 parent
c8b6ff9cdd
commit
e7d778aeac
24 files changed
+483
-32
No files matched your search
@@ -1,21 +1,22 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow,session}=require('electron'),assert=require('node:assert/strict'),fs=require('node:fs');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 waitFor(s){for(let n=0;n<200;n++){if(await evaluate(s))return;await new Promise(r=>setTimeout(r,60));}const probe=await evaluate(`(async()=>{let error;try{await load()}catch(e){error={name:e.name,message:String(e.message).slice(0,300)}}return {workspaceHidden:document.getElementById('workspace').hidden,missingScopes:agentScopes.filter(scope=>!document.getElementById('agent-scope-'+scope)),error};})()`);fs.writeFileSync('artifacts/visual-admin-load-diagnostic.json',JSON.stringify(probe,null,2));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:value+'-synthetic-fixture-session-capability',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 selectReview(){await evaluate(`document.querySelector('#reviews-rows button').click()`);await waitFor(`!document.getElementById('independent-review-detail').hidden`);}
|
||||
async function approve(version){assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);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==='v${version}'`);}
|
||||
async function capture(file,section){await evaluate(`document.getElementById('${section}').scrollIntoView();document.getElementById('console-theme').value='dark';document.getElementById('console-theme').dispatchEvent(new Event('change'))`);await new Promise(r=>setTimeout(r,250));fs.mkdirSync('artifacts',{recursive:true});fs.writeFileSync(file,(await window.webContents.capturePage()).toPNG());}
|
||||
app.whenReady().then(async()=>{
|
||||
window=new BrowserWindow({show:false,width:1500,height:1100,webPreferences:{nodeIntegration:false,contextIsolation:true,sandbox:true,offscreen:true,backgroundThrottling:false}});
|
||||
const proofTraceIds=[],requestsById=new Map();session.defaultSession.webRequest.onCompleted({urls:[process.env.SAFETYNET_ADMIN_SMOKE_URL+'/api/*']},details=>{const row=requestsById.get(details.id);if(row){row.outcome='completed';row.statusCode=details.statusCode;}});session.defaultSession.webRequest.onErrorOccurred({urls:[process.env.SAFETYNET_ADMIN_SMOKE_URL+'/api/*']},details=>{const row=requestsById.get(details.id);if(row)row.outcome='failed';});session.defaultSession.webRequest.onBeforeSendHeaders({urls:[process.env.SAFETYNET_ADMIN_SMOKE_URL+'/api/*']},(details,callback)=>{const crypto=require('node:crypto'),traceId=crypto.randomBytes(16).toString('hex'),spanId=crypto.randomBytes(8).toString('hex');const path=new URL(details.url).pathname,route=path==='/api/admin/policy'?'/api/admin/policy':path==='/api/admin/policy/restore'?'/api/admin/policy/restore':/^\/api\/admin\/policy-reviews\/[^/]+\/approve$/.test(path)?'/api/admin/policy-reviews/:id/approve':/^\/api\/admin\/policy-history\/[^/]+\/restore$/.test(path)?'/api/admin/policy-history/:id/restore':'other-api';const row={traceId,parentSpanId:spanId,outcome:'pending',method:details.method,route};proofTraceIds.push(row);requestsById.set(details.id,row);callback({requestHeaders:{...details.requestHeaders,traceparent:'00-'+traceId+'-'+spanId+'-01'}});});
|
||||
await identity('grant-author');assert.equal(await evaluate('typeof require'),'undefined');for(const scope of ['history:list','history:go','window:state','window:control','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit'])assert.deepEqual(await evaluate(`(()=>{const field=document.getElementById('agent-scope-${scope}');return {present:!!field,checked:field.checked,disabled:field.disabled};})()`),{present:true,checked:false,disabled:false});assert.equal(await evaluate(`document.getElementById('vault-controlled-submit').checked`),false);await evaluate(`document.getElementById('agent-enabled').checked=true;document.getElementById('vault-controlled-submit').checked=true;document.getElementById('vault-submit-forms').value=JSON.stringify([{id:'company-login',actionUrl:'https://login.company.example/signin',usernameField:'username',passwordField:'password'}]);document.getElementById('agent-scope-sessions').checked=true;document.getElementById('agent-scope-read').checked=true;for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit'])document.getElementById('agent-scope-'+scope).checked=true;document.getElementById('agent-hosts').value='*.company.example';document.getElementById('agent-identity-mode').value='individual';document.getElementById('agent-identity-mode').dispatchEvent(new Event('change'));document.getElementById('agent-grant-add').click();`);
|
||||
await identity('grant-author');assert.equal(await evaluate('typeof require'),'undefined');for(const scope of ['history:list','history:go','window:state','window:control','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.deepEqual(await evaluate(`(()=>{const field=document.getElementById('agent-scope-${scope}');return {present:!!field,checked:field.checked,disabled:field.disabled};})()`),{present:true,checked:false,disabled:false});assert.equal(await evaluate(`document.getElementById('vault-controlled-submit').checked`),false);await evaluate(`document.getElementById('agent-enabled').checked=true;document.getElementById('vault-controlled-submit').checked=true;document.getElementById('vault-submit-forms').value=JSON.stringify([{id:'company-login',actionUrl:'https://login.company.example/signin',usernameField:'username',passwordField:'password'}]);document.getElementById('agent-scope-sessions').checked=true;document.getElementById('agent-scope-read').checked=true;for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])document.getElementById('agent-scope-'+scope).checked=true;document.getElementById('agent-hosts').value='*.company.example';document.getElementById('agent-identity-mode').value='individual';document.getElementById('agent-identity-mode').dispatchEvent(new Event('change'));document.getElementById('agent-grant-add').click();`);
|
||||
assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor .checks input')).every(x=>!x.checked)`),true);assert.equal(await evaluate(`document.getElementById('agent-vault-help').textContent.includes('A person must unlock')&&document.getElementById('agent-vault-help').textContent.includes('cannot unlock it or export passwords')`),true);assert.equal(await evaluate(`document.getElementById('agent-grants-editor').textContent.includes('human unlock')`),true);assert.equal(await evaluate(`document.getElementById('agent-sessions-help').textContent.includes('no passwords, form values or page state')&&document.getElementById('agent-sessions-help').textContent.includes('never restores revoked agent authority')`),true);
|
||||
await evaluate(`const box=document.querySelector('#agent-grants-editor>fieldset'),fields=box.querySelectorAll('.grid input,.grid textarea');fields[0].value='research-agent';fields[0].dispatchEvent(new Event('input'));fields[1].value='Research Agent';fields[1].dispatchEvent(new Event('input'));fields[2].checked=true;fields[2].dispatchEvent(new Event('change'));fields[3].value='2099-12-31T23:59:59Z';fields[3].dispatchEvent(new Event('input'));fields[4].value='*.company.example';fields[4].dispatchEvent(new Event('input'));for(const label of box.querySelectorAll('.checks label'))if(['sessions','read','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit'].includes(label.textContent)){const input=label.querySelector('input');input.checked=true;input.dispatchEvent(new Event('change'));}document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');await selectReview();assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);
|
||||
await evaluate(`const box=document.querySelector('#agent-grants-editor>fieldset'),fields=box.querySelectorAll('.grid input,.grid textarea');fields[0].value='research-agent';fields[0].dispatchEvent(new Event('input'));fields[1].value='Research Agent';fields[1].dispatchEvent(new Event('input'));fields[2].checked=true;fields[2].dispatchEvent(new Event('change'));fields[3].value='2099-12-31T23:59:59Z';fields[3].dispatchEvent(new Event('input'));fields[4].value='*.company.example';fields[4].dispatchEvent(new Event('input'));for(const label of box.querySelectorAll('.checks label'))if(['sessions','read','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'].includes(label.textContent)){const input=label.querySelector('input');input.checked=true;input.dispatchEvent(new Event('change'));}document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v1');await selectReview();assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);
|
||||
assert.equal(await evaluate(`fetch('/api/admin/policy-reviews/'+selectedIndependentReview.id+'/approve',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({payloadHash:selectedIndependentReview.payloadHash,expectedVersion:selectedIndependentReview.expectedVersion,approve:true})}).then(r=>r.status)`),403);
|
||||
await identity('grant-reviewer');await selectReview();const diff=await evaluate(`document.getElementById('independent-review-diff').textContent`);for(const text of ['/agents/grants','research-agent','2099','company.example','sessions','read','generation','controlledSubmission','company-login','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit'])assert.ok(diff.includes(text),text);await capture('artifacts/agent-grants-review-admin.png','independent-review-detail');await approve(2);
|
||||
await capture('artifacts/credential-submission-admin-controls.png','password-vault');await identity('grant-auditor');for(const id of ['vault-controlled-submit','vault-submit-forms'])assert.equal(await evaluate(`document.getElementById('${id}').disabled`),true);for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit'])assert.equal(await evaluate(`document.getElementById('agent-scope-${scope}').disabled`),true);assert.equal(await evaluate(`document.getElementById('agent-grant-add').disabled&&document.getElementById('agent-identity-mode').disabled`),true);assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor input, #agent-grants-editor textarea, #agent-grants-editor button')).every(x=>x.disabled)`),true);
|
||||
await identity('grant-reviewer');await selectReview();const diff=await evaluate(`document.getElementById('independent-review-diff').textContent`);for(const text of ['/agents/grants','research-agent','2099','company.example','sessions','read','generation','controlledSubmission','company-login','vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.ok(diff.includes(text),text);await capture('artifacts/agent-grants-review-admin.png','independent-review-detail');await approve(2);
|
||||
await capture('artifacts/credential-submission-admin-controls.png','password-vault');await identity('grant-auditor');for(const id of ['vault-controlled-submit','vault-submit-forms'])assert.equal(await evaluate(`document.getElementById('${id}').disabled`),true);for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit','screenshot'])assert.equal(await evaluate(`document.getElementById('agent-scope-${scope}').disabled`),true);assert.equal(await evaluate(`document.getElementById('agent-grant-add').disabled&&document.getElementById('agent-identity-mode').disabled`),true);assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor input, #agent-grants-editor textarea, #agent-grants-editor button')).every(x=>x.disabled)`),true);
|
||||
await identity('grant-author');assert.equal(await evaluate(`document.querySelector('#agent-grants-editor .grid input').readOnly`),true);await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor button')).find(x=>x.textContent==='Invalidate previous credentials').click();document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v2');await identity('grant-reviewer');await selectReview();assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('"generation":2'));await approve(3);
|
||||
await identity('grant-author');await evaluate(`document.getElementById('brand-name').value='Unpublished preserved draft';document.getElementById('history-load').click()`);await waitFor(`document.querySelectorAll('#history-rows tr').length===3`);await evaluate(`document.querySelectorAll('#history-rows button')[1].click()`);await waitFor(`!document.getElementById('history-review').hidden`);assert.equal(await evaluate(`document.getElementById('brand-name').value`),'Unpublished preserved draft');assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v3');assert.equal(await evaluate(`document.getElementById('history-restore').disabled`),true);const history=await evaluate(`document.getElementById('history-diff-list').textContent`);assert.ok(history.includes('/agents/grants'));assert.ok(history.includes('generation'));assert.ok((await evaluate(`document.getElementById('history-review-summary').textContent`)).includes('does not restore invalidated credentials'));await capture('artifacts/agent-grants-history-admin.png','history-review');await evaluate(`document.getElementById('history-approve').checked=true;document.getElementById('history-approve').dispatchEvent(new Event('change'));document.getElementById('history-restore').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v3');await selectReview();assert.equal(await evaluate(`document.getElementById('independent-review-approve').disabled`),true);await identity('grant-reviewer');await selectReview();assert.ok((await evaluate(`document.getElementById('independent-review-diff').textContent`)).includes('research-agent'));await approve(4);
|
||||
await identity('grant-author');assert.equal(await evaluate(`document.getElementById('agent-scope-screenshot').checked`),true);assert.equal(await evaluate(`document.getElementById('agent-visual-export-help').textContent.includes('does not authorize desktop')`),true);await evaluate(`document.getElementById('agent-scope-screenshot').checked=false;document.getElementById('agent-scope-screenshot').dispatchEvent(new Event('input'));for(const label of document.querySelectorAll('#agent-grants-editor .checks label'))if(label.textContent==='screenshot'){label.querySelector('input').checked=false;label.querySelector('input').dispatchEvent(new Event('change'));}document.getElementById('publish').click()`);await waitFor(`document.getElementById('status').textContent.includes('prepared for independent approval')`);assert.equal(await evaluate(`document.getElementById('policy-version').textContent`),'v4');await identity('grant-reviewer');await selectReview();const revokeDiff=await evaluate(`document.getElementById('independent-review-diff').textContent`);assert.ok(revokeDiff.includes('screenshot'));await approve(5);assert.equal(await evaluate(`document.getElementById('agent-scope-screenshot').checked`),false);assert.equal(await evaluate(`Array.from(document.querySelectorAll('#agent-grants-editor .checks label')).filter(l=>l.textContent==='screenshot').every(l=>!l.querySelector('input').checked)`),true);
|
||||
for(let n=0;n<100&&proofTraceIds.some(row=>row.outcome==='pending');n++)await new Promise(r=>setTimeout(r,50));assert.equal(proofTraceIds.some(row=>row.outcome==='pending'),false,'API requests must settle before telemetry shutdown');
|
||||
fs.writeFileSync('artifacts/agent-grants-admin-request-trace-ids.json',JSON.stringify(proofTraceIds));console.log('GRANT_ADMIN_OK');clearTimeout(deadline);window.destroy();app.exit(0);
|
||||
}).catch(error=>{console.error('GRANT_ADMIN_FAILED',error.message);clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
}).catch(error=>{console.error('GRANT_ADMIN_FAILED',error.message);try{fs.writeFileSync('artifacts/visual-admin-startup-diagnostics.json',JSON.stringify({message:error.message},null,2));}catch{}clearTimeout(deadline);window?.destroy();app.exit(1);});
|
||||
@@ -12,11 +12,12 @@ process.env.OTEL_EXPORTER_OTLP_ENDPOINT='http://127.0.0.1:4318';
|
||||
const health=await fetch('http://127.0.0.1:4318/health').then(r=>r.json());assert.equal(health.rejectedWrites,0);
|
||||
const sdk=startTelemetry(),require=createRequire(import.meta.url),directory=mkdtempSync(join(tmpdir(),'safetynet-agent-grant-admin-'));let store,server,child;
|
||||
try {
|
||||
store=openStore(join(directory,'management'));for(const [credential,role,sub] of [['grant-author','admin','grant-author'],['grant-reviewer','admin','grant-reviewer'],['grant-auditor','auditor','grant-auditor']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+3600000,JSON.stringify({sub,role}));
|
||||
store=openStore(join(directory,'management'));for(const [credential,role,sub] of [['grant-author','admin','grant-author'],['grant-reviewer','admin','grant-reviewer'],['grant-auditor','auditor','grant-auditor']])store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(credential+'-synthetic-fixture-session-capability'),Date.now()+3600000,JSON.stringify({sub,role}));
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,requireIndependentPolicyApproval:true};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 preflight=await fetch(config.publicUrl+'/api/admin/session',{headers:{cookie:'sn_session=grant-author-synthetic-fixture-session-capability'}});assert.equal(preflight.status,200,'Synthetic author session must authenticate before UI');
|
||||
const env={...process.env,SAFETYNET_ADMIN_SMOKE_PROFILE:join(directory,'browser'),SAFETYNET_ADMIN_SMOKE_URL:config.publicUrl};delete env.ELECTRON_RUN_AS_NODE;
|
||||
child=spawn(require('electron'),['scripts/agent-grants-history-admin-smoke.cjs'],{env,windowsHide:true,stdio:['ignore','pipe','pipe']});child.stdout.on('data',d=>{if(String(d).includes('GRANT_ADMIN_OK'))console.log('Grant admin actual UI passed');});child.stderr.on('data',d=>{if(String(d).includes('GRANT_ADMIN_FAILED'))console.error(String(d).trim());});const timer=setTimeout(()=>child.kill(),60000),code=await new Promise(r=>{child.once('exit',c=>r(c??1));child.once('error',()=>r(1));});clearTimeout(timer);assert.equal(code,0);
|
||||
const p=verifyPolicy(store.envelope(),store.publicKey());assert.equal(p.version,4);assert.equal(p.agents.grants[0].generation,1);assert.equal(p.agents.identityMode,'individual');for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit']){assert.ok(p.agents.scopes.includes(scope));assert.ok(p.agents.grants[0].scopes.includes(scope));}
|
||||
const p=verifyPolicy(store.envelope(),store.publicKey());assert.equal(p.version,5);assert.equal(p.agents.grants[0].generation,1);assert.equal(p.agents.identityMode,'individual');assert.equal(p.agents.scopes.includes('screenshot'),false);assert.equal(p.agents.grants[0].scopes.includes('screenshot'),false);for(const scope of ['vault:status','vault:list','vault:fill','sessions:save','sessions:list','sessions:restore','vault:prepare-submit']){assert.ok(p.agents.scopes.includes(scope));assert.ok(p.agents.grants[0].scopes.includes(scope));}
|
||||
assert.equal(p.passwordManager.controlledSubmission.enabled,true);assert.equal(p.passwordManager.controlledSubmission.forms[0].actionUrl,'https://login.company.example/signin');
|
||||
const traces=JSON.parse(readFileSync('artifacts/agent-grants-admin-request-trace-ids.json','utf8'));await sdk.shutdown();await new Promise(r=>setTimeout(r,100));const exported=[],failedAttempts=traces.filter(item=>item.outcome==='failed');assert.ok(traces.some(item=>item.outcome==='completed'),'Completed API requests required');assert.equal(traces.some(item=>item.outcome!=='completed'&&['POST','PUT','PATCH','DELETE'].includes(item.method)),false,'Every policy mutation must complete and export a correlated span');for(const item of traces.filter(item=>item.outcome==='completed')){const r=await fetch(`http://127.0.0.1:4318/api/trace/${item.traceId}`);assert.equal(r.ok,true,'Completed API request trace must be available');if(r.ok){const trace=await r.json();assert.ok(trace.spans.some(s=>s.name==='server.request'&&s.parentSpanId===item.parentSpanId&&s.durationMs>=0));exported.push({traceId:item.traceId,parentSpanId:item.parentSpanId,trace});}}assert.ok(exported.length,'Actual request traces required');mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/agent-grants-history-admin-traces.json',JSON.stringify(exported,null,2));writeFileSync('artifacts/agent-grants-history-admin-proof.json',JSON.stringify({actualUi:true,vaultScopesDefaultOffAndReviewed:true,sessionScopesDefaultOffAndReviewed:true,controlledCredentialPolicyReviewed:true,prepareSubmitScopeDefaultOffAndReviewed:true,separateAuthorReviewer:true,selfApprovalDenied:true,auditorReadonly:true,exactGrantDiff:true,rotationReviewed:true,historyDraftPreserved:true,restoreReviewed:true,finalVersion:p.version,completedRequests:traces.filter(item=>item.outcome==='completed').length,failedAttempts:failedAttempts.map(item=>({traceId:item.traceId,outcome:item.outcome,method:item.method,route:item.route})),traceIds:exported.map(x=>x.traceId)},null,2));console.log('Grant publication/history and exported traces verified');
|
||||
const traces=JSON.parse(readFileSync('artifacts/agent-grants-admin-request-trace-ids.json','utf8'));await sdk.shutdown();await new Promise(r=>setTimeout(r,100));const exported=[],failedAttempts=traces.filter(item=>item.outcome==='failed');assert.ok(traces.some(item=>item.outcome==='completed'),'Completed API requests required');assert.equal(traces.some(item=>item.outcome!=='completed'&&['POST','PUT','PATCH','DELETE'].includes(item.method)),false,'Every policy mutation must complete and export a correlated span');for(const item of traces.filter(item=>item.outcome==='completed')){const r=await fetch(`http://127.0.0.1:4318/api/trace/${item.traceId}`);assert.equal(r.ok,true,'Completed API request trace must be available');if(r.ok){const trace=await r.json();assert.ok(trace.spans.some(s=>s.name==='server.request'&&s.parentSpanId===item.parentSpanId&&s.durationMs>=0));exported.push({traceId:item.traceId,parentSpanId:item.parentSpanId,trace});}}assert.ok(exported.length,'Actual request traces required');mkdirSync('artifacts',{recursive:true});writeFileSync('artifacts/agent-grants-history-admin-traces.json',JSON.stringify(exported,null,2));writeFileSync('artifacts/agent-grants-history-admin-proof.json',JSON.stringify({actualUi:true,vaultScopesDefaultOffAndReviewed:true,sessionScopesDefaultOffAndReviewed:true,controlledCredentialPolicyReviewed:true,prepareSubmitScopeDefaultOffAndReviewed:true,screenshotScopeDefaultOffAndReviewed:true,screenshotBaselineAndGrantRevocationReviewed:true,separateAuthorReviewer:true,selfApprovalDenied:true,auditorReadonly:true,exactGrantDiff:true,rotationReviewed:true,historyDraftPreserved:true,restoreReviewed:true,finalVersion:p.version,completedRequests:traces.filter(item=>item.outcome==='completed').length,failedAttempts:failedAttempts.map(item=>({traceId:item.traceId,outcome:item.outcome,method:item.method,route:item.route})),traceIds:exported.map(x=>x.traceId)},null,2));console.log('Grant publication/history and exported traces verified');
|
||||
} finally {child?.kill();if(server)await new Promise(r=>{server.closeAllConnections?.();server.close(r);});store?.db.close();const target=resolve(directory);if(!target.startsWith(resolve(tmpdir())+sep)||!basename(target).startsWith('safetynet-agent-grant-admin-'))throw Error('Unsafe cleanup');rmSync(target,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
const {app,BrowserWindow,session}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),{X509Certificate}=require('node:crypto');
|
||||
app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration();const localWitnesses=[],inspection=require('../apps/browser/inspection.cjs'),startInspector=inspection.startInspector;inspection.startInspector=(...args)=>{const client=startInspector(...args),inspect=client.inspect.bind(client);client.inspect=async(bytes,policy)=>{const result=await inspect(bytes,policy);localWitnesses.push({png:Buffer.isBuffer(bytes)&&bytes.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])),decision:result.decision,contentType:result.contentType,complete:result.coverage?.complete===true,confidentialRule:result.ruleId==='visual-confidential-fixture'});return result;};return client;};let replyGate;const control=require('../apps/browser/agent-control.cjs'),startControl=control.startAgentControl;control.startAgentControl=options=>startControl({...options,audit:async event=>{await options.audit(event);if(event.action==='screenshot'&&event.outcome==='completed'&&replyGate){const gate=replyGate;replyGate=null;gate.enter();await gate.wait;}}});require('../apps/browser/main.cjs');
|
||||
const wait=ms=>new Promise(r=>setTimeout(r,ms)),base=process.env.SAFETYNET_SMOKE_SERVER,origin=process.env.SAFETYNET_SMOKE_ORIGIN,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN,'content-type':'application/json'};
|
||||
async function until(fn){for(let i=0;i<250;i++){const value=await fn();if(value)return value;await wait(100);}throw Error('Native visual fixture timeout');}
|
||||
app.whenReady().then(async()=>{const pages=[];let stage='startup';try{const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});const fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256;session.fromPartition('persist:safetynet-managed').setCertificateVerifyProc((req,callback)=>callback(req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint?0:-2));const ui=async(method,value)=>{const result=await win.webContents.executeJavaScript(`window.safetynet.${method}(${value===undefined?'':JSON.stringify(value)})`);assert.equal(result.ok,true,method);return result.data;},overview=async()=>(await fetch(base+'/api/admin/overview',{headers})).json();stage='enrollment';await ui('enroll',{server:base,code:process.env.SAFETYNET_SMOKE_CODE});const page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;pages.push(page);await until(()=>!page.isLoading()&&page.getURL()===origin+'/public');const a=await ui('agentConnection','visual-a'),b=await ui('agentConnection','visual-b'),requestFile=path.join(app.getPath('userData'),'visual-request.json');async function call(connection,action,args={}){fs.writeFileSync(requestFile,JSON.stringify({action,args}));const child=require('node:child_process').spawn(process.execPath,['scripts/agent-client.cjs','--connection',connection.credentialPath,'--request',requestFile],{windowsHide:true,stdio:['ignore','pipe','pipe']});let output='';child.stdout.on('data',v=>output+=v);child.stderr.on('data',()=>{});const exit=await new Promise(r=>{child.once('exit',r);child.once('error',()=>r(1));});return {exit,result:exit===0?JSON.parse(output).result:undefined};}const sessionId=(await call(a,'sessions')).result[0].id;
|
||||
const change=async modify=>{const p=(await overview()).policy;modify(p);p.version++;const proposed=await fetch(base+'/api/admin/policy',{method:'PUT',headers,body:JSON.stringify(p)});assert.equal(proposed.status,202);const review=await proposed.json();const approval=await fetch(base+`/api/admin/policy-reviews/${review.reviewId}/approve`,{method:'POST',headers:{cookie:'sn_session='+process.env.SAFETYNET_SMOKE_REVIEWER,origin:base,'content-type':'application/json'},body:JSON.stringify({payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true})});assert.equal(approval.status,200);await until(async()=>{const state=await ui('state');return state.ready&&state.policyVersion===p.version;});};
|
||||
stage='default disabled';assert.equal((await call(a,'screenshot',{sessionId})).exit,1);stage='independently approved scope';await change(p=>{p.agents.scopes.push('screenshot');p.agents.grants.find(g=>g.id==='visual-a').scopes.push('screenshot');});await until(()=>!page.isLoading());win.focus();page.focus();await wait(150);
|
||||
stage='public viewport approved';const captured=await call(a,'screenshot',{sessionId});assert.equal(captured.exit,0,'Public screenshot export refused');assert.deepEqual(Object.keys(captured.result).sort(),['data','height','mime','width']);assert.equal(captured.result.mime,'image/png');const png=Buffer.from(captured.result.data,'base64');assert.equal(png.toString('base64'),captured.result.data);assert.ok(png.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])));assert.equal(png.readUInt32BE(16),captured.result.width);assert.equal(png.readUInt32BE(20),captured.result.height);assert.ok(png.length<=2097152);fs.writeFileSync(path.resolve('artifacts/agent-visual-public-viewport.png'),png);png.fill(0);captured.result.data='';
|
||||
stage='other actor denied';assert.equal((await call(b,'screenshot',{sessionId})).exit,1);stage='reject arbitrary screenshot parameters';assert.equal((await call(a,'screenshot',{sessionId,clip:{x:0,y:0,width:20,height:20}})).exit,1);
|
||||
const navigate=async route=>{await ui('navigate',origin+route);await until(()=>!page.isLoading()&&page.getURL()===origin+route);win.focus();page.focus();await wait(150);};
|
||||
stage='canvas confidential text OCR blocks';await navigate('/canvas');assert.equal(await page.executeJavaScript("document.body.innerText.includes('CLASSIFIED ORCHID DOCUMENT')"),false);assert.equal((await call(a,'screenshot',{sessionId})).exit,1);assert.ok(localWitnesses.some(w=>w.png&&w.contentType==='image'&&w.decision==='block'&&w.confidentialRule));
|
||||
stage='mandatory actual PNG AV blocks';await navigate('/av-block');assert.equal((await call(a,'screenshot',{sessionId})).exit,1);stage='offscreen credential control denied';await navigate('/sensitive');assert.equal((await call(a,'screenshot',{sessionId})).exit,1);stage='closed shadow credential control denied';await navigate('/closed-shadow');assert.equal((await call(a,'screenshot',{sessionId})).exit,1);stage='open public shadow allowed';await navigate('/open-shadow');const shadowExport=await call(a,'screenshot',{sessionId});assert.equal(shadowExport.exit,0);shadowExport.result.data='';stage='child frames fail closed';await navigate('/frames');assert.ok(page.mainFrame.frames.length>0);assert.equal((await call(a,'screenshot',{sessionId})).exit,1);
|
||||
async function delayedCapture(effect){let entered,release;const started=new Promise(r=>entered=r),gate=new Promise(r=>release=r),api=page.debugger,original=api.sendCommand.bind(api);api.sendCommand=async(...args)=>{const result=await original(...args);if(args[0]==='Page.captureScreenshot'){entered();await gate;}return result;};try{const pending=call(a,'screenshot',{sessionId});await Promise.race([started,new Promise((_,reject)=>setTimeout(()=>reject(Error('Native screenshot did not reach capture boundary')),10000))]);await effect();release();assert.equal((await pending).exit,1);}finally{release();api.sendCommand=original;}}
|
||||
stage='foreground race denies release';await navigate('/public');let otherId;await delayedCapture(async()=>{const state=await ui('createTab',{url:origin+'/public'});otherId=state.activeTabId;const other=win.contentView.children.find(v=>v.webContents!==win.webContents&&v.webContents!==page).webContents;if(other)pages.push(other);});await ui('activateTab',sessionId);await ui('closeTab',otherId);win.focus();page.focus();
|
||||
stage='open shadow mutation and revert denies release';await navigate('/open-shadow');await delayedCapture(()=>page.executeJavaScript("const p=document.getElementById('host').shadowRoot.querySelector('p'),old=p.textContent;p.textContent='CLASSIFIED ORCHID DOCUMENT';p.textContent=old"));
|
||||
async function delayedReply(effect){let enter,release;const entered=new Promise(r=>enter=r),waitForRelease=new Promise(r=>release=r);replyGate={enter,wait:waitForRelease};try{const pending=call(a,'screenshot',{sessionId});await Promise.race([entered,new Promise((_,reject)=>setTimeout(()=>reject(Error('Native screenshot did not reach completed audit boundary')),30000))]);await effect();release();const result=await pending;assert.equal(result.exit,1);assert.equal(result.result,undefined);}finally{release();replyGate=null;}}
|
||||
stage='late policy classification blocks inspected image';await navigate('/public');await delayedReply(async()=>{await change(p=>{p.dlp.rules.push({id:'late-visual-classification',label:'Synthetic late visual classification',kind:'literal',value:'Public approved visual page',action:'block'});});const p=(await overview()).policy;assert.ok(p.agents.scopes.includes('screenshot'));assert.ok(p.agents.grants.find(g=>g.id==='visual-a').enabled&&p.agents.grants.find(g=>g.id==='visual-a').scopes.includes('screenshot'));});await change(p=>p.dlp.rules=p.dlp.rules.filter(r=>r.id!=='late-visual-classification'));await navigate('/public');
|
||||
stage='late foreground change blocks inspected image';let lateOther;await delayedReply(async()=>{const state=await ui('createTab',{url:origin+'/public'});lateOther=state.activeTabId;const other=win.contentView.children.find(v=>v.webContents!==win.webContents&&v.webContents!==page).webContents;if(other)pages.push(other);});await ui('activateTab',sessionId);await ui('closeTab',lateOther);win.focus();page.focus();
|
||||
stage='policy race denies release';await delayedCapture(()=>change(p=>{p.agents.scopes=p.agents.scopes.filter(s=>s!=='screenshot');p.agents.grants.find(g=>g.id==='visual-a').scopes=['sessions'];}));assert.equal((await call(a,'screenshot',{sessionId})).exit,1);await change(p=>{p.agents.scopes.push('screenshot');p.agents.grants.find(g=>g.id==='visual-a').scopes.push('screenshot');});await until(()=>!page.isLoading());stage='target revoke denies release';await change(p=>{p.agents.grants.find(g=>g.id==='visual-a').enabled=false;});assert.equal((await call(a,'screenshot',{sessionId})).exit,1);
|
||||
stage='actual observability';await wait(1500);const events=[];let after=0;for(let batch=0;batch<20;batch++){const response=await fetch(base+`/api/admin/events/export?after=${after}&limit=1000&format=ecs`,{headers});assert.equal(response.status,200);const rows=(await response.text()).trim().split('\n').filter(Boolean).map(line=>JSON.parse(line));for(const row of rows){assert.ok(!JSON.stringify(row).includes('CLASSIFIED ORCHID DOCUMENT'));assert.ok(!JSON.stringify(row).includes('synthetic-hidden-field'));assert.ok(!JSON.stringify(row).includes('synthetic-closed-shadow-password'));events.push(row);}const next=Number(response.headers.get('x-safetynet-next-cursor'));if(rows.length<1000)break;assert.ok(next>after);after=next;}const event=events.find(e=>e.safetynet?.agent?.action==='screenshot'&&e.safetynet.agent.outcome==='completed'&&e.trace?.id);assert.ok(event);const trace=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.trace.id)).json();assert.ok(trace.spans.some(s=>s.name==='browser.agent'&&s.operation==='screenshot'));assert.ok(trace.spans.some(s=>s.name==='browser.inspect.local'));assert.ok(trace.spans.some(s=>s.name==='inspector.ocr'));for(const operation of ['visual.probe','visual.capture','visual.validate','visual.inspect.local','visual.inspect.enterprise','visual.audit','visual.project'])assert.ok(trace.spans.some(s=>s.name==='browser.visual'&&s.operation===operation),operation);const byId=new Map(trace.spans.map(s=>[s.spanId,s]));for(const operation of ['visual.probe','visual.capture','visual.validate','visual.inspect.local','visual.inspect.enterprise','visual.audit','visual.project']){let span=trace.spans.find(s=>s.name==='browser.visual'&&s.operation===operation),found=false;const seen=new Set();while(span&&!seen.has(span.spanId)){if(span.name==='browser.agent'&&span.operation==='screenshot'){found=true;break;}seen.add(span.spanId);span=byId.get(span.parentSpanId);}assert.ok(found,'Visual stage parent mismatch');}let released=trace.spans.find(s=>s.name==='browser.visual'&&s.operation==='visual.release'),releaseAncestor=false;const releaseSeen=new Set();while(released&&!releaseSeen.has(released.spanId)){if(released.name==='browser.agent'&&released.operation==='agent.request'){releaseAncestor=true;break;}releaseSeen.add(released.spanId);released=byId.get(released.parentSpanId);}assert.ok(releaseAncestor,'Final visual release must belong to outer agent request');assert.ok(!JSON.stringify(trace).includes('CLASSIFIED ORCHID DOCUMENT'));assert.ok(!JSON.stringify(trace).includes('synthetic-hidden-field'));assert.ok(!JSON.stringify(trace).includes('synthetic-closed-shadow-password'));const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0);const summary=await(await fetch('http://127.0.0.1:4318/api/summary')).json();const avFacts=await new Promise((resolve,reject)=>{const request=require('node:https').get(origin+'/av-stats',{ca:fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem')),checkServerIdentity:(_host,cert)=>new X509Certificate(cert.raw).fingerprint256===fingerprint?undefined:Error('Synthetic certificate mismatch')},response=>{let body='';response.on('data',chunk=>body+=chunk);response.on('end',()=>resolve(JSON.parse(body)));});request.on('error',reject);});assert.ok(avFacts.pngScans>=3);assert.equal(avFacts.pngBlocks,1);fs.writeFileSync(path.resolve('artifacts/agent-visual-export-native-proof.json'),JSON.stringify({actualElectron:true,syntheticPinnedTLS:true,defaultScopeDenied:true,independentPublicationApproved:true,individualScopeEnforced:true,publicViewportApproved:true,canonicalBoundedPNG:true,mandatoryOCRWithDocumentAnalysisDisabled:true,confidentialCanvasBlocked:true,mandatoryPNGAVBlocked:true,avFacts,localWitnesses,offscreenSensitiveControlDenied:true,closedShadowControlDenied:true,publicOpenShadowApproved:true,childFrameDenied:true,foregroundRaceDenied:true,lateClassificationPolicyDenied:true,lateForegroundDenied:true,openShadowMutationRevertDenied:true,policyRaceDenied:true,targetRevokeDenied:true,finalReleaseOuterRequestTraced:true,ordinaryAuditSecretFree:true,traceId:event.trace.id,stages:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs})),summary,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}},null,2));console.log('Agent visual export native proof passed');for(const p of pages)if(!p.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(p);app.exit(0);
|
||||
}catch(error){fs.writeFileSync(path.resolve('artifacts/agent-visual-export-native-error.json'),JSON.stringify({stage,name:error.name,message:error.message,localWitnesses},null,2));console.error('Agent visual native failed at '+stage+': '+error.name);for(const p of pages)if(!p.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(p);app.exit(1);}});
|
||||
@@ -0,0 +1,26 @@
|
||||
import {createServer} from 'node:https';
|
||||
import {createRequire} from 'node:module';
|
||||
import {mkdtempSync,mkdirSync,rmSync,readFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join,resolve,sep} from 'node:path';
|
||||
import {spawn} from 'node:child_process';
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import net from 'node:net';
|
||||
import {createApp} from '../apps/server/app.mjs';
|
||||
import {openStore,digest} from '../apps/server/store.mjs';
|
||||
import {startTelemetry} from '../apps/server/telemetry.mjs';
|
||||
const collector='http://127.0.0.1:4318';if(!(await fetch(collector+'/health')).ok)throw Error('Protected collector unavailable');
|
||||
process.env.OTEL_EXPORTER_OTLP_ENDPOINT=collector;const telemetry=startTelemetry();
|
||||
const temporary=mkdtempSync(join(tmpdir(),'safetynet-agent-visual-'));mkdirSync('artifacts',{recursive:true});
|
||||
if(!resolve(temporary).startsWith(resolve(tmpdir())+sep)||!temporary.split(/[\\/]/).at(-1).startsWith('safetynet-agent-visual-'))throw Error('Unsafe temporary fixture path');
|
||||
let blockPNG=false;const avFacts={scans:0,pngScans:0,pngBlocks:0},avSockets=new Set(),pngSignature=Buffer.from([137,80,78,71,13,10,26,10]);const av=net.createServer(socket=>{avSockets.add(socket);socket.on('close',()=>avSockets.delete(socket));socket.on('error',()=>{});let input=Buffer.alloc(0),done=false;socket.on('data',chunk=>{input=Buffer.concat([input,chunk]);if(input.length>4194304){socket.destroy();return;}if(done||input.indexOf('\r\n\r\n')<0)return;const options=input.subarray(0,7).toString()==='OPTIONS';if(!options&&!input.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;if(options){input.fill(0);socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD, REQMOD\r\nAllow: 204\r\nISTag: "visual-fixture"\r\nEncapsulated: null-body=0\r\n\r\n');return;}avFacts.scans++;const png=input.indexOf(pngSignature)>=0;if(png)avFacts.pngScans++;const blocked=png&&blockPNG;if(blocked)avFacts.pngBlocks++;input.fill(0);socket.end(blocked?'ICAP/1.0 204 No Content\r\nISTag: "visual-fixture"\r\nX-Infection-Found: Type=0; Threat=synthetic-visual;\r\n\r\n':'ICAP/1.0 204 No Content\r\nISTag: "visual-fixture"\r\n\r\n');});}).listen(0,'127.0.0.1');await new Promise(r=>av.once('listening',r));
|
||||
let requests=0;const sink=createServer({cert:readFileSync('tests/fixtures/icap/cert.pem'),key:readFileSync('tests/fixtures/icap/key.pem')},(req,res)=>{requests++;if(req.url==='/av-stats'){res.setHeader('content-type','application/json');res.end(JSON.stringify(avFacts));return;}if(req.url==='/av-block')blockPNG=true;else if(['/public','/canvas','/sensitive','/closed-shadow','/open-shadow','/frames'].includes(req.url))blockPNG=false;res.setHeader('content-type','text/html');const base='<!doctype html><meta name="viewport" content="width=device-width"><style>body{margin:0;background:white;color:#123;font:28px Arial}h1{margin:70px}canvas{display:block}</style><title>Public visual fixture</title>';if(req.url==='/canvas')res.end(base+'<canvas width="1200" height="360"></canvas><script>const c=document.querySelector("canvas").getContext("2d");c.fillStyle="white";c.fillRect(0,0,1200,360);c.fillStyle="black";c.font="bold 48px Arial";c.fillText("CLASSIFIED ORCHID DOCUMENT",40,170);</script>');else if(req.url==='/sensitive')res.end(base+'<h1>Public form</h1><input type="password" value="synthetic-hidden-field" style="position:absolute;left:-9999px"><script>document.querySelectorAll=()=>[]</script>');else if(req.url==='/closed-shadow')res.end(base+`<div id=host></div><script>document.getElementById("host").attachShadow({mode:"closed"}).innerHTML='<input type="password" value="synthetic-closed-shadow-password">';document.querySelectorAll=()=>[]</script>`);else if(req.url==='/open-shadow')res.end(base+`<div id=host></div><script>document.getElementById("host").attachShadow({mode:"open"}).innerHTML='<p style="margin:70px;font:28px Arial">Public shadow content</p>'</script>`);else if(req.url==='/frames')res.end(base+'<h1>Public parent</h1><iframe src="/public"></iframe>');else res.end(base+'<h1>Public approved visual page</h1><p style="margin:70px">Research and documentation workspace.</p>');}).listen(0,'127.0.0.1');await new Promise(r=>sink.once('listening',r));
|
||||
const origin=`https://127.0.0.1:${sink.address().port}`,store=openStore(join(temporary,'db')),code=randomBytes(32).toString('base64url'),adminToken=randomBytes(32).toString('base64url');
|
||||
const p=store.get('policy');p.browser.homeUrl=origin+'/public';p.inspection.documentAnalysisEnabled=false;p.dlp.rules.push({id:'visual-confidential-fixture',label:'Synthetic visual confidential rule',kind:'literal',value:'CLASSIFIED ORCHID DOCUMENT',action:'block'});
|
||||
p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${av.address().port}/scan`,scanUploads:true,scanDownloads:false,scanClipboard:false,scanAI:false};
|
||||
p.agents={...p.agents,enabled:true,identityMode:'individual',scopes:['sessions'],allowedHosts:['127.0.0.1'],grants:[{id:'visual-a',name:'Visual A',enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes:['sessions'],allowedHosts:['127.0.0.1'],generation:1},{id:'visual-b',name:'Visual B',enabled:true,expiresAt:new Date(Date.now()+3600000).toISOString(),scopes:['sessions'],allowedHosts:['127.0.0.1'],generation:1}]};
|
||||
p.diagnostics={...p.diagnostics,enabled:true,mode:'debug',endpoint:collector,sampleRate:1,debugSampleRate:1,flushIntervalMs:250};store.put('policy',p);store.db.prepare('INSERT INTO enrollments VALUES(?,?,?)').run(digest(code),Date.now()+600000,'vault-fixture');
|
||||
const config={secure:false,publicUrl:'http://127.0.0.1',adminToken,requirePQManagement:true,requireIndependentPolicyApproval:true};const management=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>management.once('listening',r));config.publicUrl=`http://127.0.0.1:${management.address().port}`;
|
||||
const reviewer=randomBytes(32).toString('base64url');store.db.prepare('INSERT INTO sessions VALUES(?,?,?)').run(digest(reviewer),Date.now()+600000,JSON.stringify({sub:'independent-vault-reviewer',role:'admin'}));
|
||||
const child=spawn(createRequire(import.meta.url)('electron'),['scripts/agent-visual-export-smoke.cjs'],{windowsHide:true,stdio:['ignore','pipe','pipe'],env:{...process.env,SAFETYNET_SMOKE_SERVER:config.publicUrl,SAFETYNET_SMOKE_CODE:code,SAFETYNET_SMOKE_ADMIN:adminToken,SAFETYNET_SMOKE_REVIEWER:reviewer,SAFETYNET_SMOKE_USERDATA:join(temporary,'browser'),SAFETYNET_SMOKE_ARTIFACTS:resolve('artifacts'),SAFETYNET_SMOKE_ORIGIN:origin}});child.stdout.on('data',v=>process.stdout.write(v));child.stderr.on('data',v=>process.stderr.write(v));const deadline=setTimeout(()=>child.kill(),240000);const exit=await new Promise(r=>{child.once('exit',v=>r(v??1));child.once('error',()=>r(1));});clearTimeout(deadline);
|
||||
for(const socket of avSockets)socket.destroy();await new Promise(r=>av.close(r));for(const server of [management,sink])server.closeAllConnections();await Promise.all([new Promise(r=>management.close(r)),new Promise(r=>sink.close(r))]);store.db.close();await telemetry?.shutdown();rmSync(temporary,{recursive:true,force:true});if(exit===0&&(avFacts.pngScans<3||avFacts.pngBlocks!==1))throw Error('Actual visual AV exchanges incomplete');process.exit(exit);
|
||||
@@ -14,7 +14,7 @@ assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize
|
||||
for(const file of ['apps/browser/upload-lease.cjs','apps/browser/file-upload-gate.cjs','apps/browser/request-source.cjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged upload binding source is stale: ${file}`);
|
||||
assert.deepEqual(await fs.readFile(path.join(root,'resources/inspector/upload_lease.py')),await fs.readFile('apps/inspector/upload_lease.py'),'Packaged Windows upload lease helper is stale');
|
||||
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize('apps/browser/header-inspection.cjs')),await fs.readFile('apps/browser/header-inspection.cjs'),'Packaged outgoing header DLP source is stale');
|
||||
for(const file of ['apps/browser/credential-submission.cjs','packages/core/credential-policy.mjs','apps/browser/session-snapshot.cjs','apps/browser/agent-session-adapters.cjs','apps/browser/session-restore.cjs','apps/browser/vault-agent-controls.cjs','apps/browser/vault.cjs','apps/browser/window-controls.cjs','apps/browser/history-snapshot.cjs','apps/browser/human-interaction.cjs','packages/core/siem.mjs','packages/core/evidence.mjs','packages/core/evidence-payload.cjs','packages/core/interaction-facts.cjs','apps/browser/evidence-capture.cjs','apps/browser/evidence-delivery.cjs','packages/core/request-transfer-facts.cjs','packages/core/transfer-facts.cjs','packages/core/inspection-stages.cjs','apps/browser/history-navigation.cjs','apps/browser/agent-transport-client.cjs','apps/browser/agent-client.cjs','apps/browser/library-organization.cjs','packages/core/library.mjs','packages/core/branding.mjs','packages/core/file-transfer.cjs','packages/core/file-transfer.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged new security/branding source is stale: ${file}`);
|
||||
for(const file of ['apps/browser/agent-visual-export.cjs','apps/browser/agent-visual-probe.cjs','apps/browser/credential-submission.cjs','packages/core/credential-policy.mjs','apps/browser/session-snapshot.cjs','apps/browser/agent-session-adapters.cjs','apps/browser/session-restore.cjs','apps/browser/vault-agent-controls.cjs','apps/browser/vault.cjs','apps/browser/window-controls.cjs','apps/browser/history-snapshot.cjs','apps/browser/human-interaction.cjs','packages/core/siem.mjs','packages/core/evidence.mjs','packages/core/evidence-payload.cjs','packages/core/interaction-facts.cjs','apps/browser/evidence-capture.cjs','apps/browser/evidence-delivery.cjs','packages/core/request-transfer-facts.cjs','packages/core/transfer-facts.cjs','packages/core/inspection-stages.cjs','apps/browser/history-navigation.cjs','apps/browser/agent-transport-client.cjs','apps/browser/agent-client.cjs','apps/browser/library-organization.cjs','packages/core/library.mjs','packages/core/branding.mjs','packages/core/file-transfer.cjs','packages/core/file-transfer.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged new security/branding source is stale: ${file}`);
|
||||
for(const file of ['apps/browser/agent-grants.cjs','apps/browser/policy-assignment.cjs','packages/core/policy-groups.mjs','apps/browser/tab-registry.cjs','apps/browser/agent-adapters.cjs','apps/browser/agent-control.cjs','apps/browser/password-fill.cjs','apps/browser/diagnostics.cjs','packages/core/diagnostics.mjs','packages/core/security.mjs'])assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged device/session policy source is stale: ${file}`);
|
||||
for(const file of ['apps/browser/main.cjs','apps/browser/preload.cjs','apps/browser/site-preload.cjs','apps/browser/clipboard-deny.cjs','apps/browser/clipboard-guard.cjs','apps/browser/clipboard-dom.cjs','apps/browser/page-clipboard-guard.cjs','apps/browser/startup-security.cjs','apps/browser/visual-capture.cjs','apps/browser/renderer.js','apps/browser/index.html','apps/browser/style.css','packages/core/clipboard.mjs']){
|
||||
assert.deepEqual(extractFile(path.join(root,'resources/app.asar'),path.normalize(file)),await fs.readFile(file),`Packaged source is stale: ${file}`);
|
||||
|
||||
Reference in new issue
Block a user