'use strict'; // A second Electron process reads its original DPAPI enrollment, never reenrolls. const {app,BrowserWindow,session,clipboard,ClipboardItem}=require('electron'),fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),https=require('node:https'),{X509Certificate}=require('node:crypto'); assert.equal(process.env.SAFETYNET_SMOKE_CODE,undefined,'Restart must not receive an enrollment code');app.setPath('userData',process.env.SAFETYNET_SMOKE_USERDATA);app.disableHardwareAcceleration(); const origin=process.env.SAFETYNET_SMOKE_ORIGIN,base=process.env.SAFETYNET_SMOKE_SERVER,headers={authorization:'Bearer '+process.env.SAFETYNET_SMOKE_ADMIN},secret='MVP Company Forecast 2030',fingerprint=new X509Certificate(fs.readFileSync(path.join(__dirname,'../tests/fixtures/icap/cert.pem'))).fingerprint256,fromPartition=session.fromPartition.bind(session); session.fromPartition=(...args)=>{const current=fromPartition(...args);if(args[0]==='persist:safetynet-managed')current.setCertificateVerifyProc((req,callback)=>{let valid=false;try{valid=req.hostname==='127.0.0.1'&&new X509Certificate(req.certificate.data).fingerprint256===fingerprint;}catch{}callback(valid?0:-2);});return current;}; let actualDiagnostics;const moduleDiagnostics=require('../apps/browser/diagnostics.cjs'),createDiagnostics=moduleDiagnostics.createDiagnostics;moduleDiagnostics.createDiagnostics=(...args)=>{const value=createDiagnostics(...args);actualDiagnostics=value;return value;};require('../apps/browser/main.cjs'); const wait=ms=>new Promise(r=>setTimeout(r,ms));async function until(fn){for(let i=0;i<300;i++){const result=await fn();if(result)return result;await wait(100);}throw Error('Restarted native MVP timeout');} const privateFacts=()=>new Promise((resolve,reject)=>{const request=https.get(origin+'/facts',{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);}); app.whenReady().then(async()=>{let stage='persisted startup',page,original=[];const checks=[];try{for(const item of await clipboard.read()){const data={};for(const type of item.types)data[type]=await item.getType(type);original.push(new ClipboardItem(data));}const win=await until(()=>{const w=BrowserWindow.getAllWindows()[0];return w&&!w.webContents.isLoading()&&w.webContents.getURL().endsWith('index.html')&&w;});const ui=async()=>{const result=await win.webContents.executeJavaScript('window.safetynet.state()');assert.equal(result.ok,true);return result.data;};const initial=await until(async()=>{const current=await ui();return current.ready&¤t.enrolled&¤t;});assert.equal(initial.deviceId,process.env.SAFETYNET_SMOKE_EXPECT_DEVICE);assert.equal(initial.policyVersion,Number(process.env.SAFETYNET_SMOKE_EXPECT_POLICY));assert.notEqual(process.pid,Number(process.env.SAFETYNET_SMOKE_FIRST_PID));page=win.contentView.children.find(v=>v.webContents!==win.webContents).webContents;await until(()=>!page.isLoading()&&page.getURL()===origin+'/workspace');checks.push('same-dpapi-device-current-policy-new-process'); const before=await privateFacts(),key=async keyCode=>{win.focus();page.focus();page.sendInputEvent({type:'keyDown',keyCode,modifiers:['control']});page.sendInputEvent({type:'keyUp',keyCode,modifiers:['control']});await wait(400);await until(async()=>!(await ui()).clipboardPending);};const editor=async text=>page.executeJavaScript(`(()=>{const e=document.getElementById('editor');e.innerHTML='
';e.firstChild.textContent=${JSON.stringify(text)};e.focus();const r=document.createRange();r.selectNodeContents(e.firstChild);r.collapse(false);const s=getSelection();s.removeAllRanges();s.addRange(r);})()`);const value=()=>page.executeJavaScript("document.getElementById('editor').innerText.trimEnd()"); stage='post-restart public paste submit';await editor('');await clipboard.writeText('Public restarted AI prompt');await key('V');assert.equal(await value(),'Public restarted AI prompt');assert.equal(await page.executeJavaScript("window.mvpSubmit('editor')"),true);assert.equal((await privateFacts()).receiver.posts,before.receiver.posts+1);checks.push('restarted-public-paste-submit');stage='post-restart confidential paste held';await editor('Public preserved restart baseline');await clipboard.writeText(secret);await key('V');assert.equal(await value(),'Public preserved restart baseline');assert.equal(await page.executeJavaScript("window.mvpSubmit('editor')"),true);assert.equal((await privateFacts()).receiver.posts,before.receiver.posts+2);assert.equal((await privateFacts()).receiver.secretPosts,0);checks.push('restarted-confidential-paste-denied');stage='post-restart typed confidential body denied';assert.equal(await page.executeJavaScript(`fetch('/receiver/typed',{method:'POST',body:${JSON.stringify(secret)}}).then(r=>r.ok).catch(()=>false)`),false);assert.equal((await privateFacts()).receiver.posts,before.receiver.posts+2);checks.push('restarted-confidential-body-denied'); stage='post-restart public file allowed';const doc=await page.debugger.sendCommand('DOM.getDocument',{}),node=await page.debugger.sendCommand('DOM.querySelector',{nodeId:doc.root.nodeId,selector:'#file'});const upload=async file=>{await page.debugger.sendCommand('DOM.setFileInputFiles',{nodeId:node.nodeId,files:[file]});return page.executeJavaScript("(()=>{const body=new FormData();body.append('file',document.getElementById('file').files[0]);return fetch('/receiver/file',{method:'POST',body}).then(r=>r.ok).catch(()=>false)})()");};assert.equal(await upload(process.env.SAFETYNET_SMOKE_PUBLIC_FILE),true);let after=await privateFacts();assert.equal(after.receiver.posts,before.receiver.posts+3);assert.equal(after.receiver.filePosts,before.receiver.filePosts+1);assert.equal(after.receiver.exactPublicFile,before.receiver.exactPublicFile+1);stage='post-restart confidential file denied';assert.equal(await upload(process.env.SAFETYNET_SMOKE_SECRET_FILE),false);after=await privateFacts();assert.equal(after.receiver.posts,before.receiver.posts+3);assert.equal(after.receiver.secretPosts,0);checks.push('restarted-public-secret-ntfs-upload'); stage='post-restart audit and traces';await actualDiagnostics.flush();await wait(1500);const events=[];let cursor=Number(process.env.SAFETYNET_SMOKE_AFTER_EVENT);for(let batch=0;batch<20;batch++){const response=await fetch(base+`/api/admin/events/export?after=${cursor}&limit=1000&format=ecs`,{headers});assert.equal(response.status,200);const rows=(await response.text()).trim().split('\n').filter(Boolean).map(JSON.parse);for(const row of rows){assert.equal(row.safetynet.policy_version,initial.policyVersion,'Restart audit policy binding changed');assert.equal(row.host.id,initial.deviceId);assert.ok(!JSON.stringify(row).includes(secret));events.push(row);}if(rows.length<1000)break;const next=Number(response.headers.get('x-safetynet-next-cursor'));assert.ok(next>cursor);cursor=next;}assert.ok(events.some(e=>e.event.action==='browser.started'),'New browser process start was not audited');const traces=[];for(const action of ['clipboard.paste','clipboard.block','upload.authorized','upload.block']){const event=events.filter(e=>e.event.action===action&&e.trace?.id).at(-1);assert.ok(event,action+' restart event absent');const trace=await(await fetch('http://127.0.0.1:4318/api/trace/'+event.trace.id)).json();assert.ok(trace.spans.length);assert.ok(!JSON.stringify(trace).includes(secret));const expected=action.startsWith('clipboard')?'browser.clipboard':'browser.request';assert.ok(trace.spans.some(s=>s.name===expected));if(action.startsWith('upload.'))assert.ok(trace.spans.some(s=>s.name==='browser.upload.lease'),'Restart native file lease trace missing');const byId=new Map(trace.spans.map(s=>[s.spanId,s]));if(action.startsWith('upload.')){for(const operation of ['text.local','text.enterprise']){const normalized=trace.spans.find(s=>s.operation===operation);assert.ok(normalized,'Normalized URL stage absent');let current=normalized,linked=false;const seen=new Set();while(current&&!seen.has(current.spanId)){if(current.name==='browser.request'){linked=true;break;}seen.add(current.spanId);current=byId.get(current.parentSpanId);}assert.ok(linked,'Normalized URL parent correlation absent');}}for(const stage of trace.spans.filter(s=>s.name==='browser.upload.lease'||s.name==='browser.upload.part'||s.name==='browser.inspect.local')){let current=stage,linked=false;const seen=new Set();while(current&&!seen.has(current.spanId)){if(current.name===expected){linked=true;break;}seen.add(current.spanId);current=byId.get(current.parentSpanId);}assert.ok(linked,'Restart span parent binding absent');}traces.push({action,traceId:event.trace.id,stages:trace.spans.map(({name,operation,spanId,parentSpanId,durationMs})=>({name,operation,spanId,parentSpanId,durationMs}))});}const status=actualDiagnostics.status();assert.equal(status.enabled,true);assert.equal(status.failedExports,0);assert.equal(status.lifetimeFailedExports,0);const health=await(await fetch('http://127.0.0.1:4318/health')).json();assert.equal(health.rejectedWrites,0); fs.writeFileSync(path.resolve('artifacts/windows-dlp-mvp-restart-native-proof.json'),JSON.stringify({actualNewNativeProcess:true,firstProcessId:Number(process.env.SAFETYNET_SMOKE_FIRST_PID),restartedProcessId:process.pid,enrollmentCodeAbsent:true,persistedDeviceId:initial.deviceId,policyVersion:initial.policyVersion,checks,receiverBefore:before.receiver,receiverAfter:after.receiver,scannerBefore:before.scanner,scannerAfter:after.scanner,traces,diagnostics:status,health:{storedBytes:health.storedBytes,rejectedWrites:health.rejectedWrites}},null,2));console.log('Windows DLP MVP actual process restart proof passed');if(original.length)await clipboard.write(original);else await clipboard.clear();require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(0); }catch(error){let facts;try{facts=await privateFacts();await actualDiagnostics?.flush();}catch{}fs.writeFileSync(path.resolve('artifacts/windows-dlp-mvp-restart-native-error.json'),JSON.stringify({stage,name:error.name,message:error.message,checks,facts,diagnostics:actualDiagnostics?.status()},null,2));console.error('Restarted Windows MVP failed at '+stage+': '+error.name);try{if(original.length)await clipboard.write(original);else await clipboard.clear();}catch{}if(page&&!page.isDestroyed())require('../apps/browser/page-clipboard-guard.cjs').disposePageClipboardGuard(page);app.exit(1);}});