Files
safetynet/tests/credential-stage-diagnostics.test.mjs

6 lines
5.1 KiB
JavaScript

import test from 'node:test';import assert from 'node:assert/strict';import {createRequire} from 'node:module';import {createServer} from 'node:http';import {DiagnosticsSchema} from '../packages/core/diagnostics.mjs';
const require=createRequire(import.meta.url),{createDiagnostics}=require('../apps/browser/diagnostics.cjs'),{createCredentialSubmissionBroker}=require('../apps/browser/credential-submission.cjs');
async function fixture(t){const payloads=[],server=createServer((req,res)=>{const chunks=[];req.on('data',c=>chunks.push(c));req.on('end',()=>{payloads.push(JSON.parse(Buffer.concat(chunks).toString()));res.setHeader('content-type','application/json');res.end('{}');});}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));const diagnostics=createDiagnostics();diagnostics.configure(DiagnosticsSchema.parse({enabled:true,mode:'debug',endpoint:`http://127.0.0.1:${server.address().port}`,sampleRate:1,debugSampleRate:1,flushIntervalMs:10000}));const broker=createCredentialSubmissionBroker({isCurrent:()=>true,traceOperation:(operation,fn)=>diagnostics.spanSync('browser.credential',{operation},fn)});t.after(async()=>{broker.close();await diagnostics.shutdown();server.closeAllConnections();await new Promise(r=>server.close(r));});const frame={},wc={id:7,mainFrame:frame},vault={status:()=>({locked:false}),accessGeneration:()=>1},context={policy:{passwordManager:{controlledSubmission:{enabled:true,forms:[{id:'secret-form',actionUrl:'https://example.com/session',usernameField:'user',passwordField:'pass'}]}}},token:{},webContents:wc,mainFrame:frame,sourceURL:'https://example.com/login',actor:{id:'private-actor'}},body=Buffer.from('user=private-user&pass=PrivateVault123%21&note=public'),details={id:1,url:'https://example.com/session',method:'POST',resourceType:'mainFrame',webContentsId:7,frame,initiatorOrigin:'https://example.com',uploadData:[{bytes:body}],requestHeaders:{'Content-Type':'application/x-www-form-urlencoded'}};const stage=(operation,fn)=>diagnostics.spanSync('browser.credential',{operation,token:'private-token',password:'PrivateVault123!',url:details.url,formId:'secret-form'},fn);return {payloads,diagnostics,broker,context,details,body,vault,stage,spans:()=>payloads.flatMap(p=>p.resourceSpans||[]).flatMap(p=>p.scopeSpans||[]).flatMap(p=>p.spans||[])};}
test('real OTLP credential phase spans correlate and never export secrets or form descriptors',async t=>{const f=await fixture(t);await f.diagnostics.span('browser.agent',{operation:'vault:prepare-submit'},async()=>{f.stage('credential.issue',()=>f.broker.issue({context:f.context,formId:'secret-form',username:'private-user',password:'PrivateVault123!',vault:f.vault,vaultGeneration:1}));const claim=f.stage('credential.claim',()=>f.broker.claim(f.details,f.body,f.context));try{f.stage('credential.headers',()=>f.broker.validateHeaders(f.details,f.context));f.stage('credential.authorize',()=>f.broker.finalize(1,f.context,f.details));f.broker.redirect(1);f.stage('credential.redirect',()=>f.broker.validateRedirect({...f.details,method:'GET',url:'https://example.com/home',uploadData:[],requestHeaders:{}},f.context));}finally{claim.residual.fill(0);}});await f.diagnostics.flush();const spans=f.spans(),parent=spans.find(s=>s.name==='browser.agent');for(const operation of ['credential.issue','credential.claim','credential.headers','credential.authorize','credential.redirect']){const s=spans.find(s=>s.attributes.some(a=>a.key==='safetynet.operation'&&a.value.stringValue===operation));assert.ok(s,operation);assert.equal(s.name,'browser.credential');assert.equal(s.parentSpanId,parent.spanId);assert.equal(s.traceId,parent.traceId);assert.ok(BigInt(s.endTimeUnixNano)>=BigInt(s.startTimeUnixNano));}const text=JSON.stringify(f.payloads);for(const secret of ['private-user','PrivateVault123','private-actor','private-token','secret-form','example.com'])assert.equal(text.includes(secret),false,secret);for(const operation of ['credential.authority','credential.native','credential.parse','credential.match','credential.residual','credential.header-values']){const children=spans.filter(s=>s.attributes.some(a=>a.key==='safetynet.operation'&&a.value.stringValue===operation));assert.ok(children.length,operation);for(const child of children){assert.equal(child.traceId,parent.traceId);assert.ok(child.parentSpanId);assert.ok(spans.some(s=>s.spanId===child.parentSpanId));}}assert.equal(f.diagnostics.status().lifetimeFailedExports,0);});
test('admin diagnostics disable preserves one-use credential enforcement without exports',async t=>{const f=await fixture(t);f.diagnostics.configure({enabled:false});f.stage('credential.issue',()=>f.broker.issue({context:f.context,formId:'secret-form',username:'private-user',password:'PrivateVault123!',vault:f.vault,vaultGeneration:1}));const claim=f.stage('credential.claim',()=>f.broker.claim(f.details,f.body,f.context));claim.residual.fill(0);f.stage('credential.headers',()=>f.broker.validateHeaders(f.details,f.context));assert.equal(f.stage('credential.authorize',()=>f.broker.finalize(1,f.context,f.details)).authorized,true);assert.throws(()=>f.stage('credential.authorize',()=>f.broker.finalize(1,f.context,f.details)));await f.diagnostics.flush();assert.equal(f.spans().length,0);});