const { app, BrowserWindow, WebContentsView, ipcMain, session, safeStorage, shell, dialog, powerMonitor, clipboard, Menu } = require('electron'); const {assertSecureStartup}=require('./startup-security.cjs'); const {assertPolicyAssignment}=require('./policy-assignment.cjs'); assertSecureStartup(app.commandLine); // Force Chromium sandboxing globally before any renderer is created. The subframe // preload option below never grants a Node engine: native proof tests verify this. app.enableSandbox(); const fs = require('node:fs'); const path = require('node:path'); const os = require('node:os'); const { pathToFileURL } = require('node:url'); const {createDiagnostics}=require('./diagnostics.cjs'); const diagnostics=createDiagnostics(); function diagnosticContext(extra={}){return {policyVersion:policy?.version||0,...(device?.id?{deviceId:device.id}:{}),...extra};} function diagnosticSpan(name,attributes,operation){ // Instrumentation never replaces, skips or retries a security operation. let started=false;const run=()=>{started=true;return Promise.resolve(operation()).then(result=>{if(['allow','block','deny','unknown'].includes(result?.decision)){try{diagnostics.event(name,diagnosticContext({...attributes,decision:result.decision}));}catch{}}return result;});}; try{return diagnostics.span(name,diagnosticContext(attributes),run);}catch(error){if(started)throw error;return run();} } let diagnosticsConfiguration=''; function configureDiagnostics(config){const value=config||{enabled:false},context=diagnosticContext(),key=JSON.stringify([value,context]);if(key===diagnosticsConfiguration)return;try{diagnostics.configure(value,context);diagnosticsConfiguration=key;}catch{diagnosticsConfiguration='';try{diagnostics.configure({enabled:false});}catch{}}} function diagnosticCorrelation(){try{const ids=diagnostics.correlation?.();return /^[a-f0-9]{32}$/.test(ids?.traceId||'')&&/[1-9a-f]/.test(ids.traceId)&&/^[a-f0-9]{16}$/.test(ids?.spanId||'')&&/[1-9a-f]/.test(ids.spanId)?{traceId:ids.traceId,spanId:ids.spanId}:{};}catch{return {};}} let win,view,browserSession,managementSession,core,device,policy,ready=false,syncing=false,aiPanel=false,vaultPanel=false,vault,vaultTimer,fileUploadGate,uploadLeaseFatal=false; let chromeHeight=104,leftSidebar=false,startPage=false,overviewPanel=false,toolsOpen=false,pageTitle='',pageColor='#edf1f6'; const {createTabRegistry}=require('./tab-registry.cjs'); let tabs,tabsRevision=0; function activeTab(){return tabs?.active();} function tabContext(id,{foreground=true}={}){const tab=id?tabs.get(id):activeTab();if(!tab)throw Error('No native browser tab');return tabs.context(tab.id,{foreground});} function currentTabContext(token){return ready&&token?.policyVersion===policy?.version&&tabs.current(token)&&Date.parse(policy?.expiresAt)>Date.now();} function selectTab(id){const target=tabs.get(id);if(!target.guarded)throw Error('Native tab security is still initializing');const changed=target!==activeTab();const tab=tabs.activate(id);if(changed){win.webContents.focus();vault?.lock();vaultPanel=false;}view=tab.view;startPage=tab.startPage;pageTitle=tab.title;pageColor=tab.color;layout();return tab;} function blockedTab(){return tabs?.all().find(tab=>tab.security.visualBlocked||tab.security.contentBlocked);} function stickyBlock(){const tab=blockedTab();return tab&&(tab.security.visualBlocked||tab.security.contentBlocked);} const {startInspector}=require('./inspection.cjs'); const {icapRequired,scanEnterprise,scanDownloadEnterprise}=require('./enterprise-inspection.cjs'); const {createDownloadGate}=require('./download-gate.cjs'); const {resolverOptions,createNetworkPosture}=require('./network-posture.cjs'); const networkPosture=createNetworkPosture();let networkTimer,networkChecking=false; const {startAgentControl}=require('./agent-control.cjs'); const {createAgentAdapters}=require('./agent-adapters.cjs'); const {createVault}=require('./vault.cjs'); const {createDeveloperToolsGuard,developerAssetRequestAllowed}=require('./developer-tools.cjs'); const {settingsState,sectionVisible,sectionAllowed,featureAllowed,requireFeature,requireSection,validateAppearance}=require('./managed-settings.cjs'); const {bookmarkState,validateMutation:validateBookmarkMutation,libraryVisible}=require('./bookmarks.cjs'); let bookmarkBusy=false,personalBookmarks={revision:0,items:[]},bookmarkSnapshotJSON='',libraryOrganizer; function effectiveBookmarkPolicy(){return policy?{...policy,browser:{...policy.browser,bookmarks:{...policy.browser.bookmarks,personal:personalBookmarks}}}:null;} let appearancePreferences={}; function managedSettings(){return settingsState({policy,ready,preferences:appearancePreferences});} function requireManagedSection(section){requireSection(managedSettings(),section);} function requireFeatureSection(section){requireFeature(managedSettings(),section);} function loadAppearance(){try{appearancePreferences=validateAppearance(JSON.parse(fs.readFileSync(path.join(app.getPath('userData'),'appearance.json'),'utf8')));}catch{appearancePreferences={};}} function persistAppearance(){fs.writeFileSync(path.join(app.getPath('userData'),'appearance.json'),JSON.stringify(appearancePreferences),{mode:0o600});} const {fillScript}=require('./password-fill.cjs'); const {createVisualPhishing,describeScript}=require('./visual-phishing.cjs'); const {fingerprintPNG}=require('../../packages/core/visual-image.cjs'); const {hammingDistance}=require('../../packages/core/visual-hash.cjs'); const {capturePNG}=require('./visual-capture.cjs'); const {createClipboardGuard}=require('./clipboard-guard.cjs'); const {clipboardScript}=require('./clipboard-dom.cjs'); const {attachPageClipboardGuard,disposePageClipboardGuard}=require('./page-clipboard-guard.cjs'); let clipboardGuard,clipboardCore,clipboardFatal=false; function installClipboardControls(contents){ contents.on('before-input-event',(event,input)=>{ if(input.type!=='keyDown')return; const key=input.key.toLowerCase(),modifier=(input.control||input.meta)&&!input.alt; const operation=modifier&&key==='c'||input.control&&key==='insert'?'copy':modifier&&key==='x'||input.shift&&key==='delete'?'cut':modifier&&key==='v'||input.shift&&key==='insert'?'paste':null; if(operation){event.preventDefault();diagnosticSpan('browser.clipboard',{operation},()=>clipboardGuard?.perform(operation,contents)).catch(()=>{});} }); contents.on('context-menu',(_event,params)=>{ const actions=params.isEditable?['copy','cut','paste']:params.selectionText?['copy']:[]; if(!actions.length)return; Menu.buildFromTemplate(actions.map(operation=>({label:operation[0].toUpperCase()+operation.slice(1),click:()=>diagnosticSpan('browser.clipboard',{operation},()=>clipboardGuard?.perform(operation,contents)).catch(()=>{})}))).popup({window:win}); }); } let managementHealthy=false; function visualDeadline(operation){let timeout;return Promise.race([operation,new Promise((_,reject)=>{timeout=setTimeout(()=>reject(Error('Visual check timed out')),5000);})]).finally(()=>clearTimeout(timeout));} function checkVisual(...args){return diagnosticSpan('browser.visual',args[0]?.id?{sessionId:args[0].id}:{},()=>checkVisualImpl(...args));} async function checkVisualImpl(tab=activeTab()){ if(!tab||tab.view.webContents.isDestroyed())return {decision:'unknown'}; if(!policy?.security.visualPhishing.enabled)return {decision:'allow'}; const security=tab.security; if(!security.visualPending){const operation=security.visualScanner.check({force:true});security.visualPending=operation;operation.finally(()=>{if(security.visualPending===operation)security.visualPending=null;}).catch(()=>{});} const generation=tab.navigationId,version=policy.version,url=tab.view.webContents.getURL(),result=await security.visualPending; if(ready&&generation===tab.navigationId&&version===policy.version&&!tab.view.webContents.isDestroyed()&&url===tab.view.webContents.getURL()&&result.decision==='allow')security.visualApproval={generation,version,url}; if(!tab.view.webContents.isDestroyed()&&generation===tab.navigationId&&version===policy.version&&url===tab.view.webContents.getURL()){if(ready){if(result.decision==='unknown')message='Login protection could not approve this view. Credential actions are held.';emit();}return result;} return {decision:'unknown',reason:'stale-context'}; } async function configureVisualPage(enabled,tab=activeTab()){ if(!tab)return;tabs.navigation(tab.id);const contents=tab.view.webContents; if(contents.isDestroyed()||!/^https?:\/\//.test(contents.getURL()))return; const token=require('node:crypto').randomUUID(); await new Promise((resolve,reject)=>{ const handler=(event,value)=>{if(event.sender===contents&&event.senderFrame===contents.mainFrame&&value===token){clearTimeout(timeout);ipcMain.removeListener('managed:visual-configured',handler);resolve();}}; const timeout=setTimeout(()=>{ipcMain.removeListener('managed:visual-configured',handler);enabled?reject(Error('Visual protection could not initialize')):resolve();},2000); ipcMain.on('managed:visual-configured',handler);contents.send('managed:visual-config',{enabled,token}); }); } let companyNews=[],companyNewsKey='',companyNewsFetchedAt=0; let message='Enroll this browser to receive company policy.',syncTimer,localInspector,auditQueue=[],flushing=false,agentService,agentStarting=false,securityCore; const recentRequests=new Map(); const agentFile=()=>path.join(app.getPath('userData'),'agent-connection.json'); const agentGrants=require('./agent-grants.cjs'); const agentActorStorage=new (require('node:async_hooks').AsyncLocalStorage)(); let agentServiceMode; function effectiveAgentPolicy(){if(!policy)return policy;const {grants}=agentGrants.reconcileGenerationFloor(policy.agents.grants||[],device?.agentGenerationFloor||{});return {...policy,agents:{...policy.agents,grants}};} function approvedAgentGrants(){return agentGrants.activeGrants(effectiveAgentPolicy());} function actorGrantAllowed(actor,action,url){return agentGrants.authorizeGrant({policy:effectiveAgentPolicy(),ready:ready&&featureAllowed(managedSettings(),'agents'),principalId:actor?.principalId,generation:actor?.generation,action,url,hostMatches:core.hostMatches}).allowed;} function agentRequestOwnerAllowed(source){return policy?.agents?.identityMode!=='individual'||!!source?.guarded;} function actorDestinationAllowed(url,actor=agentActorStorage.getStore()){if(!actor?.action)return core.destinationAllowed(url,policy);return core.destinationAllowed(url,policy)&&actorGrantAllowed(actor,actor.action,url);} async function reconcileAgentPolicy(){ const result=agentGrants.reconcileGenerationFloor(policy.agents.grants||[],device.agentGenerationFloor||{}); if(JSON.stringify(result.floor)!==JSON.stringify(device.agentGenerationFloor||{})){device.agentGenerationFloor=result.floor;saveDevice(device);} const mode=policy.agents.identityMode; if(agentService&&(agentServiceMode!==mode||!policy.agents.enabled||policy.browser.settings.hiddenSections.includes('agents')||policy.browser.settings.lockedSections.includes('agents'))){agentService.revoke();await agentService.close();agentService=null;agentServiceMode=undefined;} if(mode==='individual'){try{fs.unlinkSync(agentFile());}catch(error){if(error.code!=='ENOENT')throw error;}agentService?.reconcilePrincipals(result.grants);} } async function pairIndividualAgent(principalId){ if(typeof principalId!=='string')throw Error('Select an approved agent'); const grant=approvedAgentGrants().find(g=>g.id===principalId),initial=policy,service=agentService; if(!grant||!service||agentServiceMode!=='individual')throw Error('Agent grant unavailable'); const connection=service.issuePrincipal(grant.id,grant.generation,grant.expiresAt); const credentialPath=await agentGrants.persistConnection({filePath:agentGrants.descriptorPath(app.getPath('userData'),grant.id),connection,protect:value=>require('./dpapi.cjs').capabilityCrypto('protect',value),isCurrent:()=>ready&&policy===initial&&agentService===service&&featureAllowed(managedSettings(),'agents')&&actorGrantAllowed({principalId:grant.id,generation:grant.generation},grant.scopes[0])}); return {address:service.address,credentialPath,principalId:grant.id,generation:grant.generation}; } app.commandLine.appendSwitch('disable-http-cache'); const uiUrl=pathToFileURL(path.join(__dirname,'index.html')).href; const configPath=()=>path.join(app.getPath('userData'),'enrollment.json'); const cliRequest=app.commandLine.getSwitchValue('agent-action'),cliConnection=app.commandLine.getSwitchValue('agent-connection'); if(cliRequest){try{require('./agent-client.cjs').configureClient(cliConnection);}catch{app.exit(1);}} const ownsBrowserInstance=!!cliRequest||app.requestSingleInstanceLock(); if(!ownsBrowserInstance)app.quit(); else if(!cliRequest)app.on('second-instance',()=>{if(win&&!win.isDestroyed()){if(win.isMinimized())win.restore();win.focus();}}); function requireEncryption(){ if(!safeStorage.isEncryptionAvailable() || (process.platform==='linux' && safeStorage.getSelectedStorageBackend()==='basic_text'))throw Error('An OS credential store is required. Configure Keychain, Windows DPAPI, or Linux Secret Service.'); } function saveDevice(value){requireEncryption();fs.writeFileSync(configPath(),JSON.stringify({encryptedEnrollment:safeStorage.encryptString(JSON.stringify(value)).toString('base64')}),{mode:0o600});} function loadDevice(){ if(!fs.existsSync(configPath()))return; requireEncryption();const d=JSON.parse(fs.readFileSync(configPath(),'utf8'));return JSON.parse(safeStorage.decryptString(Buffer.from(d.encryptedEnrollment,'base64'))); } function managementUrl(value){ const u=new URL(value); if(u.username||u.password||u.search||u.hash||u.pathname!=='/')throw Error('Enter a management server origin without path or credentials'); if(u.protocol!=='https:' && !(u.protocol==='http:'&&['localhost','127.0.0.1','[::1]'].includes(u.hostname)))throw Error('Management requires HTTPS; local development may use loopback HTTP.'); if(process.env.SAFETYNET_MANAGEMENT_URL&&new URL(process.env.SAFETYNET_MANAGEMENT_URL).origin!==u.origin)throw Error('Enrollment must use the administrator-deployed management server'); return u.origin; } const managementRequests=new Map(); const evidenceRequestTypes=new Map(),evidenceSendStorage=new (require('node:async_hooks').AsyncLocalStorage)(); function request(...args){return diagnosticSpan('browser.management',{operation:args[1]==='/api/device/ai'?'ai':args[1]==='/api/device/enroll'?'enroll':'sync'},()=>requestImpl(...args));} async function requestImpl(server,route,body,credential){ if(route==='/api/device/evidence'){ if(!device||device.server!==server||device.token!==credential)throw Error('Protected evidence identity unavailable'); const identity=device,type=body?.type;if(!['download','interaction'].includes(type))throw Error('Invalid protected evidence type'); const assertCurrent=()=>{if(device!==identity||!require('./evidence-delivery.cjs').policyAllows({policy},type))throw Object.assign(Error('Protected evidence collection disabled'),{status:403});if(!ready)throw Error('Evidence delivery paused until managed context is ready');}; assertCurrent();return evidenceSendStorage.run({type},()=>require('./management-channel.cjs').protectedEvidenceRequest({device,request,save:saveDevice,payload:body,assertCurrent,traceOperation:(operation,fn)=>diagnosticSpan('browser.evidence',{operation},fn)})); } if(route==='/api/device/policy'){ if(!device||device.server!==server||device.token!==credential)throw Error('Protected management identity unavailable'); return require('./management-channel.cjs').protectedPolicyRequest({device,request,save:saveDevice}); } const headers={};if(body)headers['content-type']='application/json';if(credential)headers.authorization=`Bearer ${credential}`; const correlation=diagnosticCorrelation();if(correlation.traceId&&correlation.spanId)headers.traceparent=`00-${correlation.traceId}-${correlation.spanId}-01`; if(route==='/api/device/enroll'||route==='/api/device/sso/poll')headers['x-safetynet-pq-bootstrap']='1'; const controller=new AbortController(),timer=setTimeout(()=>controller.abort(),route.endsWith('/ai')||route==='/api/device/library/propose'?65000:10000);managementRequests.set(controller,route);if(evidenceSendStorage.getStore()?.type&&route.startsWith('/api/device/channel/evidence'))evidenceRequestTypes.set(controller,evidenceSendStorage.getStore().type); try{ const fetcher=managementSession?managementSession.fetch.bind(managementSession):fetch; const r=await fetcher(`${server}${route}`,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined,credentials:'omit',redirect:'error',signal:controller.signal}); const chunks=[];let size=0;for await(const part of r.body){size+=part.length;if(size>5*1024*1024)throw Error('Management response exceeds its limit');chunks.push(part);} const data=JSON.parse(Buffer.concat(chunks).toString('utf8'));if(!r.ok && r.status!==202){const e=Error('Management request refused');e.status=r.status;throw e;}return data; }finally{clearTimeout(timer);managementRequests.delete(controller);evidenceRequestTypes.delete(controller);} } function state(){return {startPage:{...(policy?.startPage||{}),news:ready?companyNews:[]},libraryAI:{suggestionsEnabled:ready&&!!policy?.ai.enabled&&!!policy?.library?.aiSuggestionsEnabled&§ionAllowed(managedSettings(),'library')&&featureAllowed(managedSettings(),'ai'),autoApplyEnabled:ready&&!!policy?.library?.aiAutoApplyEnabled&&policy?.ai?.trustMode==='local',busy:libraryOrganizer?.isBusy()||false},bookmarks:bookmarkState({policy:effectiveBookmarkPolicy(),ready,settings:managedSettings()}),settings:managedSettings(),enrolled:!!device,deviceId:device?.id||'',ready,clipboardPending:clipboardGuard?.isPending()||false,...tabs?.summary(),tabsRevision:++tabsRevision,canNavigate:ready||!!(stickyBlock()&&managementHealthy),loginCheckPending:ready&&!!policy?.security.visualPhishing.enabled&&(!activeTab()?.security.visualApproval||activeTab().security.visualApproval.generation!==activeTab().navigationId),message,brand:policy?.brand||{name:'SafetyNet',color:'#137c66'},policyVersion:policy?.version||0,server:device?.server||'',url:view?.webContents.getURL()||'',aiEnabled:ready&&!!policy?.ai.enabled,documentScanEnabled:ready&&policy?.inspection?.documentAnalysisEnabled!==false,passwordManagerEnabled:ready&&!!policy?.passwordManager?.enabled,vaultLocked:vault?.status().locked??true,vaultExists:vault?.status().exists??fs.existsSync(path.join(app.getPath('userData'),'password-vault.json')),agentIdentityMode:policy?.agents.identityMode||'legacy',agentGrants:ready&&featureAllowed(managedSettings(),'agents')?approvedAgentGrants().map(({id,name,generation,expiresAt})=>({id,name,generation,expiresAt})):[],agentsEnabled:ready&&!!policy?.agents.enabled,agentConnected:!!agentService};} function emit(){if(win&&!win.isDestroyed()){const settings=managedSettings();if(!settings.visible)toolsOpen=false;if(!sectionAllowed(settings,'appearance'))chromeHeight=settings.appearance.tabMode==='compact'?64:104;if(!featureAllowed(settings,'ai'))aiPanel=false;if(!featureAllowed(settings,'passwords')){vaultPanel=false;vault?.lock();}if(!libraryVisible(settings))leftSidebar=false;layout();win.webContents.send('managed:state',{...state(),pageTitle,pageColor,windowMaximized:win.isMaximized()});win.setTitle(`S Browser · ${state().brand.name}`);}} function layout(){const tab=activeTab();if(!win||!tab)return;view=tab.view;startPage=tab.startPage;const [width,height]=win.getContentSize(),left=leftSidebar?240:0;view.setBounds({x:left,y:chromeHeight,width:Math.max(0,width-left-((aiPanel||vaultPanel)?360:0)),height:Math.max(0,height-chromeHeight)});view.setVisible(ready&&tab.guarded&&!startPage&&!overviewPanel&&!toolsOpen);} async function pageTheme(tab=activeTab()){ if(!tab||!ready)return;const contents=tab.view.webContents,url=contents.getURL(),context=tabContext(tab.id,{foreground:false});if(!/^https?:\/\//.test(url))return; try{const result=await contents.executeJavaScriptInIsolatedWorld(999,[{code:`(()=>{if(location.href!==${JSON.stringify(url)})return null;return {color:(document.querySelector('meta[name="theme-color"]')?.content||'').slice(0,32),background:getComputedStyle(document.body||document.documentElement).backgroundColor};})()`}],false); if(!currentTabContext(context)||!result)return;const hex=/^#[a-f0-9]{6}$/i.test(result.color)?result.color:null,rgb=/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(result.background||'');tab.color=hex||(rgb&&rgb.slice(1).every(v=>+v<=255)?'#'+rgb.slice(1).map(v=>(+v).toString(16).padStart(2,'0')).join(''):'#edf1f6');tab.title=contents.getTitle().slice(0,200);if(tab===activeTab()){pageColor=tab.color;pageTitle=tab.title;}emit(); }catch{} } function lockVault(){const unlocked=vault&&!vault.status().locked;vault?.lock();if(unlocked)audit('vault.locked');emit();} function quarantine(reason){ready=false;fileUploadGate?.cancelAll();if(policy&&Date.parse(policy.expiresAt)<=Date.now()){configureDiagnostics({enabled:false});const expiredService=agentService;agentService=null;agentServiceMode=undefined;expiredService?.revoke();void expiredService?.close().catch(()=>{});}for(const [controller,route] of managementRequests)if(route==='/api/device/ai')controller.abort();enforceEvidencePolicy();message=reason;vaultPanel=false;vault?.lock();tabs?.quarantine();emit();} function vaultSettings(){return {enabled:ready&&!!policy?.passwordManager?.enabled,allowSaving:!!policy?.passwordManager?.allowSaving,allowAutofill:!!policy?.passwordManager?.allowAutofill,idleLockSeconds:policy?.passwordManager?.idleLockSeconds||300};} function vaultStatus(){return {...(vault?.status()||{exists:fs.existsSync(path.join(app.getPath('userData'),'password-vault.json')),locked:true}),...vaultSettings()};} function requireVault(){ requireFeatureSection('passwords'); const settings=vaultSettings();if(!settings.enabled||Date.parse(policy.expiresAt)<=Date.now())throw Error('Password manager is disabled or browser access is paused'); requireEncryption(); if(!vault||vault.status().idleSeconds!==settings.idleLockSeconds){vault?.lock();vault=createVault({filePath:path.join(app.getPath('userData'),'password-vault.json'),idleSeconds:settings.idleLockSeconds,protect:bytes=>safeStorage.encryptString(bytes.toString('base64')),unprotect:bytes=>Buffer.from(safeStorage.decryptString(bytes),'base64')});} return vault; } function persistAudit(){ requireEncryption();const target=path.join(app.getPath('userData'),'audit-queue.bin'),temporary=target+'.'+require('node:crypto').randomUUID()+'.tmp';let fd; try{fd=fs.openSync(temporary,'wx',0o600);fs.writeFileSync(fd,safeStorage.encryptString(JSON.stringify(auditQueue)));fs.fsyncSync(fd);fs.closeSync(fd);fd=undefined;fs.renameSync(temporary,target);}finally{if(fd!==undefined)fs.closeSync(fd);try{fs.unlinkSync(temporary);}catch{}} } function enforceEvidencePolicy(){ if(!policy)return;const delivery=require('./evidence-delivery.cjs'); for(const [controller,type]of evidenceRequestTypes)if(!delivery.policyAllows({policy},type))controller.abort(); const next=delivery.stripPendingEvidence(auditQueue,{policy}); if(next.some((record,index)=>record!==auditQueue[index])){auditQueue=next;persistAudit();} } async function flushAudit(){ if(!device||flushing||!auditQueue.length)return;flushing=true; try{for(let n=0;n<100&&auditQueue.length;n++){const record=auditQueue[0];await diagnostics.withCorrelation(record,()=>diagnosticSpan('browser.management',{operation:'audit.deliver'},()=>require('./evidence-delivery.cjs').deliverAuditRecord({record,send:(route,body)=>request(device.server,route,body,device.token),getContext:()=>({ready,policy,deviceId:device.id})})));auditQueue.shift();persistAudit();}} catch{}finally{flushing=false;} } async function audit(kind,ruleId,host,fileFacts,requestFacts,evidencePayload,agentFacts,interactionFacts){ if(!device||!policy)return; // Operational correlation contains no rule text, hostname or forensic payload. try{diagnostics.event('browser.security',diagnosticContext({decision:kind.endsWith('.block')?'block':kind.endsWith('.denied')?'deny':kind.endsWith('.authorized')||kind.endsWith('.released')?'allow':'unknown',reason:ruleId}));}catch{} if(auditQueue.length>=10000){quarantine('Security event queue is full. Contact your administrator.');return;} try{const file=fileFacts?require('../../packages/core/transfer-facts.cjs').transferFacts(fileFacts):undefined;const request=requestFacts?require('../../packages/core/request-transfer-facts.cjs').requestTransferFacts(requestFacts):undefined;const agent=agentFacts?require('../../packages/core/agent-audit.cjs').agentAuditFacts(agentFacts):undefined;const interaction=interactionFacts?require('../../packages/core/interaction-facts.cjs').InteractionFactsSchema.parse(interactionFacts):undefined;const evidenceType=evidencePayload?.action?'interaction':'download';auditQueue.push({eventId:require('node:crypto').randomUUID(),kind,...(ruleId?{ruleId}:{}),...(host?{host}:{}),...(file?{file}:{}),...(request?{request}:{}),...(agent?{agent}:{}),...(interaction?{interaction}:{}),...(evidencePayload&&require('./evidence-delivery.cjs').collectionAllowed({ready,policy,type:evidenceType})?{protectedEvidence:{deviceId:device.id,type:evidenceType,payload:require('./evidence-delivery.cjs').validatedEvidence(evidencePayload,evidenceType)}}:{}),...diagnosticCorrelation(),policyVersion:policy.version});persistAudit();const eventId=auditQueue.at(-1).eventId;flushAudit();return eventId;} catch{quarantine('Security events cannot be persisted. Contact your administrator.');} } function auditAgent(event){ const action=event.action==='agent.read'?'read':event.action||'agent-request',outcome=event.outcome==='blocked'?'denied':event.outcome; const helper=require('../../packages/core/agent-audit.cjs'),session=helper.AgentAuditSchema.shape.sessionId.safeParse(event.sessionId); const agent=helper.agentAuditFacts({action,outcome,...(event.scope?{scope:event.scope}:{}),...(session.success&&session.data?{sessionId:session.data}:{}),...(event.principalId?{principalId:event.principalId,credentialGeneration:event.generation}:{})}); return audit(['denied','failed'].includes(outcome)?'agent.denied':'agent.action',`${event.action||'agent-request'}:${event.outcome}`,undefined,undefined,undefined,event.protectedInteraction,agent); } function applyPolicy(envelope){return diagnosticSpan('browser.policy',{},()=>applyPolicyImpl(envelope));} async function applyPolicyImpl(envelope){ const next=await diagnosticSpan('browser.policy',{operation:'policy.verify'},()=>assertPolicyAssignment(core.verifyPolicy(envelope,device.publicKey,device.minimumVersion||0),device.id)); const {nextBookmarks,nextBookmarkJSON}=await diagnosticSpan('browser.policy',{operation:'policy.bookmarks'},async()=>{ const nextBookmarks=(await import('../../packages/core/bookmarks.mjs')).verifyBookmarkSnapshot(envelope.bookmarks,device.publicKey,{deviceId:device.id,policyVersion:next.version,minimumRevision:device.bookmarkRevision||0}); const nextBookmarkJSON=JSON.stringify(nextBookmarks.items); if(nextBookmarks.revision===(device.bookmarkRevision||0)&&device.bookmarkDigest&&require('node:crypto').createHash('sha256').update(nextBookmarkJSON).digest('hex')!==device.bookmarkDigest||nextBookmarks.revision===personalBookmarks.revision&&bookmarkSnapshotJSON&&nextBookmarkJSON!==bookmarkSnapshotJSON)throw Error('Bookmarks changed without a new revision'); return {nextBookmarks,nextBookmarkJSON}; }); const acceptBookmarks=()=>{personalBookmarks={revision:nextBookmarks.revision,items:nextBookmarks.items};bookmarkSnapshotJSON=nextBookmarkJSON;device.bookmarkRevision=nextBookmarks.revision;device.bookmarkDigest=require('node:crypto').createHash('sha256').update(nextBookmarkJSON).digest('hex');saveDevice(device);}; if(process.env.SAFETYNET_POLICY_KEY_FILE&&fs.readFileSync(process.env.SAFETYNET_POLICY_KEY_FILE,'utf8').trim()!==device.publicKey.trim())throw Error('Policy signing key does not match deployment pin'); if(policy&&next.version===policy.version){ // Same versions must describe identical policy; reject equivocation. if(JSON.stringify(next)!==JSON.stringify(policy))throw Error('Policy changed without a new version');acceptBookmarks();await diagnosticSpan('browser.policy',{operation:'policy.agent-reconcile'},()=>reconcileAgentPolicy());enforceEvidencePolicy();if(Date.parse(policy.expiresAt)>Date.now())configureDiagnostics(policy.diagnostics);emit();return; } configureDiagnostics({enabled:false});quarantine('Applying company policy…'); const proxy=next.proxy; const settings={mode:proxy.mode,...(proxy.mode==='fixed_servers'?{proxyRules:proxy.rules,proxyBypassRules:proxy.bypass}:{}),...(proxy.mode==='pac_script'?{pacScript:proxy.pacUrl}:{})}; await browserSession.closeAllConnections();await browserSession.setProxy(settings); await managementSession.closeAllConnections();await managementSession.setProxy(settings); app.configureHostResolver(resolverOptions(next.enterprise?.dns)); await browserSession.clearHostResolverCache();recentRequests.clear(); await managementSession.clearHostResolverCache(); policy=next;enforceEvidencePolicy();device.minimumVersion=next.version;acceptBookmarks();configureDiagnostics(next.diagnostics); await diagnosticSpan('browser.policy',{operation:'policy.page-configure'},async()=>{for(const tab of tabs.all()){await configureVisualPage(next.security.visualPhishing.enabled,tab);if(!next.browser.allowDevTools){tab.security.developerToolsGuard?.close();tab.view.webContents.closeDevTools();}}}); await diagnosticSpan('browser.policy',{operation:'policy.agent-reconcile'},()=>reconcileAgentPolicy()); await diagnosticSpan('browser.policy',{operation:'policy.audit'},()=>audit('policy.applied')); } async function sync(){ if(!device||syncing)return;syncing=true; try{ const envelope=await request(device.server,'/api/device/policy',null,device.token);await applyPolicy(envelope); await request(device.server,'/api/device/heartbeat',{policyVersion:policy.version},device.token); await flushAudit();if(auditQueue.length>=10000)throw Error('Security event queue is full'); // Mandatory local inspection must be attested before unlocking the browser; // first user gestures must not consume their scan budget waiting for ACL setup. if(policy.security.yara.enabled||policy.dlp.semantic.enabled){ if(!localInspector)throw Error('Required private content inspector is unavailable'); await diagnosticSpan('browser.parser',{operation:'scan'},()=>localInspector.waitReady()); } if(Date.parse(policy.expiresAt)<=Date.now())throw Error('Company policy expired'); if(clipboardFatal)throw Error('Managed renderer security failed; restart the browser'); if(uploadLeaseFatal)throw Error('Upload protection failed; restart the browser'); const posture=await diagnosticSpan('browser.network',{operation:'scan'},()=>networkPosture.check(policy.enterprise?.vpn)); if(!posture.allowed)throw Error('Required corporate network is unavailable. Connect your company VPN.'); managementHealthy=true; const blocked=stickyBlock(); if(blocked&&blocked.policyVersion===policy.version&&(blockedTab()?.security.visualBlocked?policy.security.visualPhishing.enabled:policy.security.content.blockedPhrases.length)){message=blocked.message;ready=false;emit();layout();return;} for(const tab of tabs.all()){tab.security.visualBlocked=null;tab.security.contentBlocked=null;} const recovering=!ready;ready=true;flushAudit();tabs.resume(policy.version);message=`Managed · policy v${policy.version}`;emit();layout(); await ensureAgentAPI(); void refreshCompanyNews(); if(recovering){for(const tab of tabs.all()){const current=tab.view.webContents.getURL();if(/^https?:\/\//.test(current)&&core.destinationAllowed(current,policy))tab.view.webContents.reload();else if(current!=='about:blank'||tabs.all().length===1)await navigate({url:policy.browser.homeUrl,tabId:tab.id});}emit();} }catch(e){ // A deliberate page quarantine aborts in-flight management requests too. // Preserve that stronger, sticky verdict instead of replacing it with an // expected cancellation error or treating it as a management outage. const blocked=stickyBlock(); if(e.name==='AbortError'&&!ready&&blocked&&message===blocked.message){emit();return;} managementHealthy=false;quarantine(e.status===401?'Device revoked or credentials rejected. Contact your administrator.':`Browsing paused: ${e.message}`);if(e.status===401){configureDiagnostics({enabled:false});agentService?.revoke();await agentService?.close();agentService=null;try{fs.unlinkSync(agentFile());}catch{}await browserSession.closeAllConnections();await browserSession.clearStorageData();} } finally{syncing=false;} } async function refreshCompanyNews(){ const p=policy,key=JSON.stringify([p?.version,p?.startPage]);if(!ready||!p?.startPage?.newsEnabled){companyNews=[];companyNewsKey='';return;}if(key===companyNewsKey&&Date.now()-companyNewsFetchedAt<300000)return;companyNewsKey=key;companyNewsFetchedAt=Date.now();companyNews=[];emit();try{const result=await request(device.server,'/api/device/start-page/news',undefined,device.token);if(!ready||policy!==p||result.policyVersion!==p.version)return;companyNews=Array.isArray(result.news)?result.news.slice(0,12).filter(item=>typeof item.title==='string'&&item.title.length<=200&&typeof item.url==='string'&&/^https:\/\//.test(item.url)&&!new URL(item.url).username&&!new URL(item.url).password).map(item=>({title:item.title,url:item.url,publishedAt:item.publishedAt||null})):[];emit();}catch{if(policy===p){companyNews=[];emit();}} } async function completeEnrollment(server,result){ if(device)throw Error('This installation is already enrolled'); const candidate={server,id:result.id,token:result.token,publicKey:result.publicKey,minimumVersion:0}; candidate.channelSignatureKey=require('../../packages/core/policy-channel.cjs').verifyIdentity(result.managementChannel,candidate.publicKey); const channelPin=require('./management-channel.cjs').deploymentPin();if(channelPin&&candidate.channelSignatureKey.trim()!==channelPin)throw Error('Protected management signing pin mismatch'); // Optional deployment-time key pin strengthens first enrollment beyond TLS trust. if(process.env.SAFETYNET_POLICY_KEY_FILE && fs.readFileSync(process.env.SAFETYNET_POLICY_KEY_FILE,'utf8').trim()!==candidate.publicKey.trim())throw Error('Management signing key does not match deployment pin'); const initialPolicy=await require('./management-channel.cjs').protectedPolicyRequest({device:candidate,request,save:()=>{}}); assertPolicyAssignment(core.verifyPolicy(initialPolicy,candidate.publicKey),candidate.id); saveDevice(candidate);device=candidate;await sync();return state(); } function navigate(...args){return diagnosticSpan('browser.navigation',{},()=>navigateImpl(...args));} async function navigateImpl(input,captured,options={}){ const address=typeof input==='string'?input:input?.url; if(typeof address!=='string'||address.length>4096)throw Error('Invalid address'); const tab=tabs.get(captured?.sessionId||input?.tabId||activeTab().id),contents=tab.view.webContents; if(captured&&(!currentTabContext(captured.token)||captured.webContents!==contents))throw Error('Agent context changed'); const url=address.includes('://')?address:`https://${address}`; if(!policy||!['https:','http:'].includes(new URL(url).protocol)||!actorDestinationAllowed(url))throw Error('Destination is blocked by company policy'); const blocked=stickyBlock(),documentURL=value=>{const parsed=new URL(value);parsed.hash='';return parsed.href;}; if(blocked&&managementHealthy&&Date.parse(policy.expiresAt)>Date.now()&&blockedTab()===tab&&documentURL(url)!==documentURL(blocked.checkedURL)){ for(const record of tabs.all()){record.security.visualBlocked=null;record.security.contentBlocked=null;}ready=true;tabs.resume(policy.version);message='Checking destination…'; } if(!ready)throw Error('Browser is not managed and ready'); if(options.signal?.aborted)throw Error('Agent action cancelled'); if(!agentActorStorage.getStore()?.principalId&&!captured)tab.security.agentAuthority=null; const initial=policy;tab.startPage=false;if(tab===activeTab())startPage=false;layout(); const abort=()=>contents.stop();options.signal?.addEventListener('abort',abort,{once:true}); try{await contents.loadURL(url);}finally{options.signal?.removeEventListener('abort',abort);} if(options.signal?.aborted||!ready||policy!==initial||contents.isDestroyed()||tabs.fromContents(contents.id)!==tab)throw Error('Navigation context changed'); const refreshed=captured?agentContext(tab.id):null; if(captured?.token.foreground&&(!refreshed.foreground||captured.token.activation!==refreshed.token.activation))throw Error('Agent foreground changed during navigation'); emit();return refreshed||state(); } async function agentHistory(action,captured,{signal}={}){ if(!currentTabContext(captured.token)||captured.foreground!==true)throw Error('Agent context changed'); const tab=tabs.get(captured.sessionId),contents=tab.view.webContents,initial=policy,activation=captured.token.activation; const current=(phase='after')=>ready&&policy===initial&&!signal?.aborted&&!contents.isDestroyed()&&tabs.fromContents(contents.id)===tab&&activeTab()===tab&&tabContext(tab.id,{foreground:true}).activation===activation&&(phase!=='before'||currentTabContext(captured.token)); const allowed=url=>actorDestinationAllowed(url)&&(!initial.agents.allowedHosts.length||initial.agents.allowedHosts.some(pattern=>core.hostMatches(new URL(url).hostname,pattern))); const result=await diagnosticSpan('browser.navigation',{operation:action},()=>require('./history-navigation.cjs').performHistory({contents,action,allowed,current,signal})); if(!current())throw Error('History context changed');emit();return {completed:result.completed,context:agentContext(tab.id)}; } function nativeHistorySnapshot(contents){ return require('./history-snapshot.cjs').readHistorySnapshot(contents); } function getAgentHistory(captured){if(!currentTabContext(captured.token)||captured.webContents.isDestroyed())throw Error('Agent context changed');return nativeHistorySnapshot(captured.webContents);} async function goAgentHistory(index,captured,{signal,historyBinding}={}){ if(!currentTabContext(captured.token)||captured.foreground!==true)throw Error('Agent context changed'); const tab=tabs.get(captured.sessionId),contents=tab.contents,initial=policy,activation=captured.token.activation; const current=(phase='after')=>ready&&policy===initial&&!signal?.aborted&&!contents.isDestroyed()&&tabs.fromContents(contents.id)===tab&&activeTab()===tab&&tabContext(tab.id).activation===activation&&(phase!=='before'||currentTabContext(captured.token)); const allowed=url=>actorDestinationAllowed(url)&&(!initial.agents.allowedHosts.length||initial.agents.allowedHosts.some(pattern=>core.hostMatches(new URL(url).hostname,pattern))); const result=await diagnosticSpan('browser.navigation',{operation:'history:go'},()=>require('./history-navigation.cjs').performHistory({contents,action:'index',index,getSnapshot:()=>nativeHistorySnapshot(contents),historyBinding,allowed,current,signal})); if(!current())throw Error('History context changed');emit();return {completed:result.completed,context:agentContext(tab.id)}; } function agentContext(id){const tab=id?tabs.get(id):activeTab(),foreground=tab===activeTab(),actor=agentActorStorage.getStore();return {ready:ready&&featureAllowed(managedSettings(),'agents'),interactive:foreground&&!tab.startPage&&!overviewPanel&&!toolsOpen,policy,policyVersion:policy?.version,sessionId:tab.id,foreground,token:tabContext(tab.id,{foreground}),webContents:tab.view.webContents,...(actor?.principalId?{actor:Object.freeze({agentId:actor.principalId,generation:actor.generation})}:{}),destinationAllowed:url=>actorDestinationAllowed(url)};} function managedAI(...args){return diagnosticSpan('browser.ai',{},()=>managedAIImpl(...args));} async function managedAIImpl(prompt){ requireFeatureSection('ai'); if(!ready||!policy?.ai.enabled)throw Error('AI access is disabled by company policy'); if(typeof prompt!=='string'||!prompt.trim()||prompt.length>100000)throw Error('Enter a prompt of up to 100,000 characters'); const initial=policy,verdict=core.inspectForAI(Buffer.from(prompt),initial); if(verdict.decision==='block'){await audit('dlp.block',verdict.ruleId);throw Error(`Blocked by company policy: ${verdict.ruleId}`);} if(initial.dlp.semantic.enabled||initial.security.yara.enabled){if(!localInspector)throw Error('Local inspector unavailable');const result=await diagnosticSpan('browser.inspect.local',{bytes:Buffer.byteLength(prompt)},()=>localInspector.inspect(Buffer.from(prompt),initial));if(result.decision!=='allow'&&!core.allowsConfidentialLocal(result,initial))throw Error('Local content policy blocked this prompt');} const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:Buffer.byteLength(prompt)},()=>scanEnterprise(Buffer.from(prompt),initial,'ai'));if(external.decision!=='allow'){await audit('dlp.block',external.ruleId);throw Error('Company external scanner blocked this prompt');} if(!ready||policy.version!==initial.version)throw Error('Policy changed during inspection'); const result=await request(device.server,'/api/device/ai',{prompt},device.token); if(!ready||policy.version!==initial.version)throw Error('Policy changed during AI request');return result; } async function mutateManagedBookmarks(input){ if(bookmarkBusy||syncing||libraryOrganizer?.isBusy())throw Object.assign(Error('Management update in progress. Try again.'),{status:409,code:'browser-management-busy'}); const initial=policy,identity=device; const payload=validateBookmarkMutation(input,{policy:effectiveBookmarkPolicy(),ready,settings:managedSettings(),destinationAllowed:core.destinationAllowed}); bookmarkBusy=true; try{ const result=await request(identity.server,'/api/device/bookmarks',payload,identity.token); if(policy!==initial||device!==identity)throw Error('Managed policy changed during bookmark update'); const deadline=Date.now()+12000;while(syncing){if(Date.now()>=deadline)throw Error('Bookmark update awaits management synchronization');await new Promise(resolve=>setTimeout(resolve,50));} await sync(); if(!ready||device!==identity||personalBookmarks.revision!==result.revision||policy.version!==result.version)throw Error('Bookmark update requires a fresh signed policy'); return state(); }finally{bookmarkBusy=false;} } function requireLibraryAI(){ requireManagedSection('library');requireFeatureSection('ai'); if(!ready||!policy?.ai.enabled||!policy.library?.aiSuggestionsEnabled||Date.parse(policy.expiresAt)<=Date.now())throw Error('AI library organization is disabled by company policy'); if(!policy.browser.bookmarks.allowUserEdit)throw Error('Bookmark organization is controlled by your administrator'); } function nativeLibraryRows(){return tabs.all().filter(tab=>!tab.startPage&&/^https?:\/\//.test(tab.view.webContents.getURL())).map(tab=>({id:tab.id,title:String(tab.title).replace(/[\u0000-\u001f\u007f]/g,' ').slice(0,120),url:tab.view.webContents.getURL()}));} function nativeLibraryEpoch(){return JSON.stringify(tabs.all().map(tab=>[tab.id,tab.navigationId,tab.title,tab.view.webContents.getURL(),tab.group]));} function assertAgentLibraryDestinations(snapshot){if(agentActorStorage.getStore()?.principalId&&[...(snapshot.tabs||[]),...(snapshot.bookmarks||[])].some(row=>!actorDestinationAllowed(row.url)))throw Error('Agent destination denied');} function getLibraryOrganizer(){ if(libraryOrganizer)return libraryOrganizer; libraryOrganizer=require('./library-organization.cjs').createLibraryOrganization({ capture:async()=>{const library=await import('../../packages/core/library.mjs');requireLibraryAI();if(bookmarkBusy||syncing)throw Error('Management update in progress');const rows=library.LibraryTabsSchema.parse(nativeLibraryRows());if(agentActorStorage.getStore()?.principalId&&[...rows,...personalBookmarks.items].some(row=>!actorDestinationAllowed(row.url)))throw Error('Agent destination denied');return {policy,identity:device,policyVersion:policy.version,bookmarkRevision:personalBookmarks.revision,bookmarks:structuredClone(personalBookmarks.items),tabs:rows,snapshotHash:library.librarySnapshotHash(rows),epoch:nativeLibraryEpoch()};}, assertCurrent:(snapshot,{afterCommit=false,result}={})=>{requireLibraryAI();assertAgentLibraryDestinations(snapshot);if(policy!==snapshot.policy||device!==snapshot.identity||nativeLibraryEpoch()!==snapshot.epoch||personalBookmarks.revision!==snapshot.bookmarkRevision||afterCommit&&(result.version!==policy.version||result.snapshotHash!==snapshot.snapshotHash))throw Error('Library context changed. Generate a fresh proposal.');}, inspect:async snapshot=>{const library=await import('../../packages/core/library.mjs');const bytes=Buffer.from(JSON.stringify({bookmarks:library.libraryMetadata(snapshot.bookmarks),tabs:library.libraryMetadata(snapshot.tabs)}));let verdict=core.inspectForAI(bytes,snapshot.policy);if(verdict.decision!=='allow')throw Error('Company policy blocks library metadata');if(snapshot.policy.dlp.semantic.enabled||snapshot.policy.security.yara.enabled){if(!localInspector)throw Error('Local inspector unavailable');verdict=await localInspector.inspect(bytes,snapshot.policy);if(verdict.decision!=='allow'&&!core.allowsConfidentialLocal(verdict,snapshot.policy))throw Error('Local policy blocks library metadata');}verdict=await scanEnterprise(bytes,snapshot.policy,'ai');if(verdict.decision!=='allow')throw Error('Enterprise scanner blocks library metadata');}, propose:snapshot=>request(snapshot.identity.server,'/api/device/library/propose',{policyVersion:snapshot.policyVersion,bookmarkRevision:snapshot.bookmarkRevision,tabs:snapshot.tabs,snapshotHash:snapshot.snapshotHash},snapshot.identity.token), commit:async(proposalId,snapshot,automatic)=>{const result=await request(snapshot.identity.server,'/api/device/library/apply',{proposalId,policyVersion:snapshot.policyVersion,expectedRevision:snapshot.bookmarkRevision,snapshotHash:snapshot.snapshotHash,mode:automatic?'automatic':'manual'},snapshot.identity.token);const signed=(await import('../../packages/core/bookmarks.mjs')).verifyBookmarkSnapshot(result.bookmarks,snapshot.identity.publicKey,{deviceId:snapshot.identity.id,policyVersion:snapshot.policyVersion,minimumRevision:snapshot.bookmarkRevision});if(signed.revision!==result.revision||![snapshot.bookmarkRevision,snapshot.bookmarkRevision+1].includes(signed.revision))throw Error('Invalid signed organization revision');if(signed.revision===snapshot.bookmarkRevision&&JSON.stringify(signed.items)!==JSON.stringify(snapshot.bookmarks))throw Error('Bookmarks changed without a new revision');return {...result,verifiedBookmarks:signed};}, applyTabs:(assignments,result)=>{for(const row of assignments)tabs.get(row.id);const signed=result.verifiedBookmarks;personalBookmarks={revision:signed.revision,items:signed.items};bookmarkSnapshotJSON=JSON.stringify(signed.items);device.bookmarkRevision=signed.revision;device.bookmarkDigest=require('node:crypto').createHash('sha256').update(bookmarkSnapshotJSON).digest('hex');saveDevice(device);for(const row of assignments)tabs.get(row.id).group=row.group;}, autoAllowed:()=>policy?.library?.aiAutoApplyEnabled===true&&policy?.ai?.trustMode==='local', audit,changed:emit });return libraryOrganizer; } async function organizeLibrary(options){return getLibraryOrganizer().plan(options);} async function applyLibraryOrganization(input,options){ const result=await getLibraryOrganizer().apply(input,options); await sync();return result; } async function inspectManagedAgentText(text,context){ const aiContext=context.action==='ai'; let verdict=aiContext?core.inspectForAI(Buffer.from(text),context.policy):core.inspect(Buffer.from(text),context.policy);if(verdict.decision==='block')return verdict; if(context.policy.dlp.semantic.enabled||context.policy.security.yara.enabled){ if(!localInspector)return {decision:'block',ruleId:'inspection-unavailable',coverage:{complete:false}}; const result=await diagnosticSpan('browser.inspect.local',{bytes:Buffer.byteLength(text)},()=>localInspector.inspect(Buffer.from(text),context.policy)); verdict=aiContext&&core.allowsConfidentialLocal(result,context.policy)?{...result,decision:'allow',classification:'local-only'}:result; } if(verdict.decision!=='allow')return verdict; const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:Buffer.byteLength(text)},()=>scanEnterprise(Buffer.from(text),context.policy,aiContext?'ai':'upload'));return external.decision==='allow'?verdict:external; } async function ensureAgentAPI(){ if(!ready||!policy.agents.enabled||!featureAllowed(managedSettings(),'agents')||agentService||agentStarting)return;agentStarting=true; try{ const getContext=agentContext; const actions=createAgentAdapters({getContext,appControls:agentAppControls(),getSessions:()=>tabs.all().filter(tab=>!agentActorStorage.getStore()?.principalId||actorDestinationAllowed(tab.view.webContents.getURL())).map(tab=>({id:tab.id,title:tab.title,foreground:tab===activeTab()})),contextCurrent:currentTabContext,navigate,history:agentHistory,getHistory:getAgentHistory,goHistory:goAgentHistory,ai:managedAI, inspect:inspectManagedAgentText,audit:auditAgent,onInteraction:async metadata=>{ const payload=await diagnosticSpan('browser.evidence',{operation:'evidence.capture',sessionId:metadata.sessionId},()=>require('./evidence-capture.cjs').buildInteractionEvidence({...metadata,url:metadata.pageUrl,captureSource:'agent-adapter'},policy));if(!payload)return; const actor=agentActorStorage.getStore();await auditAgent({action:'click',outcome:'completed',sessionId:metadata.sessionId,...(actor?.principalId?{principalId:actor.principalId,generation:actor.generation}:{}),protectedInteraction:payload}); }}); for(const action of ['click','type','select','check']){const original=actions[action];actions[action]=async(...args)=>{const tab=tabs.get(args[0]?.sessionId||activeTab().id),context=tabContext(tab.id);const checked=await checkVisual(tab);if(!currentTabContext(context)||checked.decision!=='allow')throw Error('Visual login protection has not approved this page');return original(...args);};} for(const action of Object.keys(actions)){const original=actions[action];actions[action]=(args,context={})=>agentActorStorage.run({...context,action},()=>diagnosticSpan('browser.agent',{operation:action},async()=>{ if(context.principalId&&['navigate','back','forward','reload','history:go','vault:fill','click','type','select','check'].includes(action)){const tab=tabs.get(args.sessionId||activeTab().id);tab.security.agentAuthority={principalId:context.principalId,generation:context.generation,action};} return original(args,context); }));} agentServiceMode=policy.agents.identityMode;agentService=await startAgentControl({actions,principalMode:agentServiceMode,principals:agentServiceMode==='individual'?effectiveAgentPolicy().agents.grants:[], traceOperation:(operation,fn)=>diagnosticSpan('browser.agent',{operation},fn), onError:error=>{if(!app.isPackaged&&process.env.SAFETYNET_SMOKE_ARTIFACTS)fs.appendFileSync(path.join(process.env.SAFETYNET_SMOKE_ARTIFACTS,'smoke-entry.txt'),`Agent action diagnostic: ${error.reason}\n`);}, authorize:({action,args,scope,principalId,generation})=>{ const actor={principalId,generation,action}; if(!actorGrantAllowed(actor,action))return {allowed:false}; const checkURL=url=>actorDestinationAllowed(url,actor); if(action==='sessions')return {allowed:true,role:'agent',scopes:[scope]}; if(action==='bookmarks:mutate'&&args.bookmark?.url&&!checkURL(args.bookmark.url))return {allowed:false}; if(action==='bookmarks:mutate'&&args.action!=='add'){const existing=personalBookmarks.items.find(row=>row.id===(args.id||args.bookmark?.id));if(!existing||!checkURL(existing.url))return {allowed:false};} if(action==='tabs:create'&&args.url&&!checkURL(args.url))return {allowed:false}; if(['tabs:activate','tabs:close','tabs:group'].includes(action)){let tab;try{tab=tabs.get(args.sessionId);}catch{return {allowed:false};}const url=tab.view.webContents.getURL();if(url!=='about:blank'&&!checkURL(url))return {allowed:false};} if(Object.hasOwn(agentAppControls(),action))return {allowed:true,role:'agent',scopes:[scope]}; let tab;try{tab=args.sessionId?tabs.get(args.sessionId):activeTab();}catch{return {allowed:false};} const url=action==='navigate'?args.url:tab.view.webContents.getURL(); if(!checkURL(url))return {allowed:false}; return {allowed:true,role:'agent',scopes:[scope]}; },audit:auditAgent, persistCredential:connection=>agentGrants.persistConnection({filePath:agentFile(),connection,protect:value=>require('./dpapi.cjs').capabilityCrypto('protect',value),isCurrent:()=>ready&&policy?.agents.identityMode==='legacy'&&policy.agents.enabled}) });emit(); }finally{agentStarting=false;} } function dnsGate(...args){return diagnosticSpan('browser.dns',{},()=>dnsGateImpl(...args));} async function dnsGateImpl(url,currentPolicy){ const settings=currentPolicy.security.dns; if(!settings.enabled||!settings.blockedIPs.length)return; const host=new URL(url).hostname; if(securityCore.dnsAddressBlocked(host.replace(/^\[|\]$/g,''),currentPolicy))throw Error('dns-ip-blocklist'); let timer;const resolved=await Promise.race([browserSession.resolveHost(host,{cacheUsage:'allowed'}),new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('dns-resolution-timeout')),5000);})]).finally(()=>clearTimeout(timer)); const addresses=resolved.endpoints?.map(v=>v.address)||[]; if(!addresses.length||addresses.some(ip=>securityCore.dnsAddressBlocked(ip,currentPolicy)))throw Error('dns-ip-blocklist'); } function hooks(){ browserSession.setPermissionRequestHandler((_wc,_permission,callback)=>callback(false)); browserSession.setPermissionCheckHandler(()=>false); const leaseRoot=app.isPackaged?process.resourcesPath:path.join(__dirname,'../..'); fileUploadGate=require('./file-upload-gate.cjs').createFileUploadGate({ maxBytes:context=>context.policy.dlp.maxBytes, isCurrent:context=>!uploadLeaseFatal&&ready&&policy===context.policy&&Date.parse(context.policy.expiresAt)>Date.now()&&context.pending()&&(!context.source||currentTabContext(context.source)), // Phase callbacks cancel denied requests. Live handles remain held until a // native terminal event; stop()/quarantine alone is not proof of termination. abort:()=>{}, createLease:options=>diagnosticSpan('browser.upload.lease',{count:options.paths.length},()=>require('./upload-lease.cjs').acquireUploadLease({...options, pythonPath:path.resolve(leaseRoot,app.isPackaged?'python/python.exe':'.runtime/windows/python/python.exe'), helperPath:path.resolve(leaseRoot,app.isPackaged?'inspector/upload_lease.py':'apps/inspector/upload_lease.py'), onLost:()=>{uploadLeaseFatal=true;options.onLost();quarantine('Upload protection failed. Restart the browser.');diagnosticSpan('browser.upload.fault',{decision:'block',reason:'upload-lease-lost'},()=>audit('upload.block','upload-lease-lost')).catch(()=>{});}})), inspectPart:(bytes,{kind,context,filename})=>diagnosticSpan('browser.upload.part',{bytes:bytes.length},async()=>{ const p=context.policy; if(kind==='file'){ const transfer=require('../../packages/core/file-transfer.cjs').inspectFileTransfer(bytes,p,'upload',{filename}); if(transfer.decision!=='allow')return transfer; } const verdict=core.inspect(bytes,p); if(verdict.decision!=='allow'&&verdict.ruleId!=='uninspectable')return verdict; const needsLocal=verdict.ruleId==='uninspectable'||p.dlp.semantic.enabled||p.security.yara.enabled; if(needsLocal){ if(!localInspector)return {decision:'block',ruleId:'inspector-unavailable'}; const local=await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,p)); if(local.decision!=='allow'||local.coverage?.complete!==true)return local; } return diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,p,'upload')); })}); for(const event of ['onCompleted','onErrorOccurred'])browserSession.webRequest[event]({urls:['']},details=>{ if(!fileUploadGate.has(details.id))return; diagnosticSpan('browser.upload.release',{},()=>fileUploadGate.terminal(details.id)).catch(()=>{uploadLeaseFatal=true;quarantine('Upload protection cleanup failed. Restart the browser.');diagnosticSpan('browser.upload.fault',{decision:'block',reason:'upload-lease-release-failed'},()=>audit('upload.block','upload-lease-release-failed')).catch(()=>{});}); }); const download=createDownloadGate({getContext:id=>{const tab=tabs.fromContents(id);return tab?{ready,policy,webContentsId:id,token:tabContext(tab.id,{foreground:false})}:null;},isCurrent:context=>!!context&¤tTabContext(context.token)&&policy===context.policy, fetchResponse:(url,options)=>diagnosticSpan('browser.request',{operation:'download'},()=>browserSession.fetch(url,options)),scan:(bytes,p)=>diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanDownloadEnterprise(bytes,p,localInspector?{inspect:(data,pol)=>diagnosticSpan('browser.inspect.local',{bytes:data.length},()=>localInspector.inspect(data,pol)),scanDownload:(data,pol)=>diagnosticSpan('browser.yara',{operation:'scan',bytes:data.length},()=>localInspector.scanDownload(data,pol))}:null)), choosePath:async name=>{const chosen=await dialog.showSaveDialog(win,{title:'Save scanned download (choose a new file)',defaultPath:path.join(app.getPath('downloads'),name)});return chosen.canceled?null:chosen.filePath;}, writeFile:async(file,bytes,current)=>{ if(!current()||!path.isAbsolute(file)||file.includes('\\\\?\\')||file.slice(2).includes(':'))throw Error('Save path rejected'); const handle=await fs.promises.open(file,'wx',0o600);let release=false; try{if(!current())throw Error('Policy changed');await handle.writeFile(bytes);await handle.sync();if(!current())throw Error('Policy changed');release=true;} finally{await handle.close();if(!release)await fs.promises.unlink(file).catch(()=>{});} },audit:(kind,rule,host,facts,evidence)=>audit(kind,rule,host,facts,undefined,evidence),status:text=>{message=text;emit();}}); browserSession.on('will-download',(event,item,contents)=>{ const url=item.getURL(),name=item.getFilename(),tracked=recentRequests.get(`${contents?.id}:${url}`);event.preventDefault(); if(!/^https?:\/\//.test(url)){audit('download.block','unsupported-download');return;} diagnosticSpan('browser.download',{},()=>download({url,name,method:tracked?.method,webContentsId:contents?.id})).catch(()=>audit('download.block','download-inspection-failed')); }); browserSession.webRequest.onBeforeRequest({urls:['']},(details,callback)=>{ // A delegated native frontend may load its bundled Chromium UI only. Its HTTP // requests receive no exemption, and neither pages nor trusted chrome qualify. if(developerAssetRequestAllowed(details,{ready,policy,tabs:tabs.all()})){callback({cancel:false});return;} let called=false;const finish=cancel=>{if(!called){called=true;callback({cancel});}}; const initialPolicy=policy,source=details.webContentsId>0?tabs.fromContents(details.webContentsId):null,sourceContext=source?tabContext(source.id,{foreground:false}):null; const sourceCurrent=()=>require('./request-source.cjs').requestSourceCurrent({details,source,token:sourceContext,registry:tabs,currentDocument:currentTabContext});let timer; // Async gates complete before Chromium may transmit the request. diagnosticSpan('browser.request',{requestType:details.resourceType,...(source?{sessionId:source.id}:{})},async()=>{try{ timer=setTimeout(()=>{if(called)return;finish(true);audit('navigation.block','request-inspection-timeout',new URL(details.url).hostname).catch(()=>{});},40000); if(!ready||!policy||Date.parse(policy.expiresAt)<=Date.now()||!agentRequestOwnerAllowed(source)||(details.webContentsId>0&&(!source||!source.guarded))){if(ready&&policy&&(!agentRequestOwnerAllowed(source)||details.webContentsId>0&&(!source||!source.guarded)))await audit('navigation.block','invalid-source-owner',new URL(details.url).hostname);finish(true);return;} if(!core.destinationAllowed(details.url,policy)||(source?.security.agentAuthority&&!actorDestinationAllowed(details.url,source.security.agentAuthority))){audit('navigation.block',null,new URL(details.url).hostname);finish(true);return;} recentRequests.set(`${details.webContentsId}:${details.url}`,{method:details.method,time:Date.now()});if(recentRequests.size>2000)for(const [url,value]of recentRequests)if(value.time2000)recentRequests.delete(url); try{await dnsGate(details.url,initialPolicy);}catch(error){audit('dns.block',error.message,new URL(details.url).hostname);finish(true);return;} if(called||!ready||policy!==initialPolicy||!sourceCurrent()){if(!called&&ready&&policy===initialPolicy&&!sourceCurrent())await audit('navigation.block','request-source-context-changed',new URL(details.url).hostname);finish(true);return;} if(details.resourceType==='webSocket'&&!policy.browser.allowWebSockets){audit('dlp.block','websocket-disabled',new URL(details.url).hostname);finish(true);return;} const address=new URL(details.url),query=address.pathname+address.search; const queryVerdict=core.inspect(Buffer.from(query),policy); if(queryVerdict.decision==='block'){audit('dlp.block',queryVerdict.ruleId,new URL(details.url).hostname);finish(true);return;} const uploads=details.uploadData||[]; if(policy.security.visualPhishing.enabled&&(uploads.length||(address.search&&details.resourceType==='xhr')||details.resourceType==='webSocket')){ const checked=source?await checkVisual(source):{decision:'unknown'};if(checked.decision!=='allow'||!ready||policy!==initialPolicy||!sourceCurrent()){finish(true);return;} } if(uploads.some(part=>part.type==='file')){ const context={policy:initialPolicy,source:sourceContext,pending:()=>!called}; const verdict=await fileUploadGate.inspect(details,context); if(verdict.decision!=='allow'){await audit('upload.block',verdict.ruleId||'inspection-incomplete',address.hostname);finish(true);return;} await audit('upload.authorized',null,address.hostname,undefined,verdict.request); finish(uploadLeaseFatal||!ready||policy!==initialPolicy||!sourceCurrent());return; } // Data-pipe/blob bodies still lack an immutable bounded native snapshot. if(uploads.some(part=>!part.bytes || part.file || part.blobUUID)){audit('dlp.block','uninspectable-upload',new URL(details.url).hostname);finish(true);return;} if(!['GET','HEAD','OPTIONS'].includes(details.method)&&!uploads.length){audit('dlp.block','missing-request-body',new URL(details.url).hostname);finish(true);return;} if(uploads.length){ if(uploads.reduce((n,p)=>n+p.bytes.length,0)>policy.dlp.maxBytes){audit('dlp.block','size-limit',new URL(details.url).hostname);finish(true);return;} const requestBytes=Buffer.concat(uploads.map(p=>Buffer.from(p.bytes))); const transfer=require('../../packages/core/file-transfer.cjs').inspectFileTransfer(requestBytes,policy,'upload'); // An arbitrary request may contain credentials even when its magic bytes look binary. // Keep size/type for analysts; never fingerprint request bodies in ordinary audit. const requestFacts=require('../../packages/core/request-transfer-facts.cjs').requestFactsForTransfer(transfer); const releaseUpload=async()=>{if(called)return;await audit('upload.authorized',null,new URL(details.url).hostname,undefined,requestFacts);finish(!ready||policy!==initialPolicy||!sourceCurrent());}; if(transfer.decision!=='allow'){await audit('upload.block',transfer.ruleId,new URL(details.url).hostname,undefined,requestFacts);requestBytes.fill(0);finish(true);return;} const verdict=core.inspect(requestBytes,policy);requestBytes.fill(0); if(verdict.decision==='block'&&verdict.ruleId!=='uninspectable'){audit('dlp.block',verdict.ruleId,new URL(details.url).hostname);finish(true);return;} if(verdict.ruleId==='uninspectable'||policy.dlp.semantic.enabled||policy.security.yara.enabled||icapRequired(policy,'upload')){ const needsLocal=verdict.ruleId==='uninspectable'||policy.dlp.semantic.enabled||policy.security.yara.enabled; if(needsLocal&&!localInspector){audit('dlp.block','inspector-unavailable',new URL(details.url).hostname);finish(true);return;} const snapshot=Buffer.concat(uploads.map(p=>Buffer.from(p.bytes))),currentPolicy=policy; try{let result=needsLocal?await diagnosticSpan('browser.inspect.local',{bytes:snapshot.length},()=>localInspector.inspect(snapshot,currentPolicy)):{decision:'allow'};if(result.decision==='allow')result=await diagnosticSpan('browser.inspect.enterprise',{bytes:snapshot.length},()=>scanEnterprise(snapshot,currentPolicy,'upload')); const cancel=called||!ready||policy.version!==currentPolicy.version||!sourceCurrent()||result.decision!=='allow'; if(cancel){audit('dlp.block',result.ruleId||'policy-changed',new URL(details.url).hostname);finish(true);}else await releaseUpload(); }catch{audit('dlp.block','inspection-incomplete',new URL(details.url).hostname);finish(true);}finally{snapshot.fill(0);}return; }else{await releaseUpload();return;} } finish(false); }catch{finish(true);}finally{clearTimeout(timer);}}).catch(()=>finish(true)); }); browserSession.webRequest.onBeforeSendHeaders({urls:['']},(details,callback)=>{ if(developerAssetRequestAllowed(details,{ready,policy,tabs:tabs.all()})){callback({cancel:false});return;} let called=false;const finish=(cancel,requestHeaders)=>{if(called)return;called=true;callback(cancel?{cancel:true}:{cancel:false,requestHeaders});}; const initialPolicy=policy,source=details.webContentsId>0?tabs.fromContents(details.webContentsId):null,sourceContext=source?tabContext(source.id,{foreground:false}):null; const sourceCurrent=()=>require('./request-source.cjs').requestSourceCurrent({details,source,token:sourceContext,registry:tabs,currentDocument:currentTabContext}); const current=()=>!called&&ready&&policy===initialPolicy&&Date.parse(initialPolicy?.expiresAt)>Date.now()&&sourceCurrent(); let timer; diagnosticSpan('browser.request',{requestType:details.resourceType,...(source?{sessionId:source.id}:{})},async()=>{ timer=setTimeout(()=>{if(called)return;finish(true);audit('dlp.block','headers-inspection-timeout',new URL(details.url).hostname).catch(()=>{});},40000); try{ if(!current()||!agentRequestOwnerAllowed(source)||(details.webContentsId>0&&(!source||!source.guarded))||!core.destinationAllowed(details.url,initialPolicy)||(source?.security.agentAuthority&&!actorDestinationAllowed(details.url,source.security.agentAuthority))){if(ready&&policy===initialPolicy&&(!agentRequestOwnerAllowed(source)||details.webContentsId>0&&(!source||!source.guarded)))await audit('navigation.block','invalid-source-owner',new URL(details.url).hostname);finish(true);return;} const {snapshotHeaders,inspectHeaders}=require('./header-inspection.cjs'); let snapshot;try{snapshot=snapshotHeaders(details.requestHeaders,initialPolicy);}catch(error){await audit('dlp.block',error.message==='headers-size-limit'?'headers-size-limit':'headers-uninspectable',new URL(details.url).hostname);finish(true);return;} const verdict=await diagnosticSpan('browser.request.headers',{bytes:snapshot.bytes,count:snapshot.count,requestType:details.resourceType},()=>inspectHeaders(snapshot,{policy:initialPolicy,inspect:core.inspect,isCurrent:current, inspectLocal:localInspector?(bytes,p)=>diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,p)):null, inspectEnterprise:(bytes,p)=>diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,p,'upload'))})); if(!current()){if(ready&&policy===initialPolicy&&!sourceCurrent())await audit('navigation.block','header-source-context-changed',new URL(details.url).hostname);finish(true);return;} if(verdict.decision!=='allow'){await audit('dlp.block',verdict.ruleId||'headers-inspection-incomplete',new URL(details.url).hostname);finish(true);return;} // Chromium receives the exact immutable snapshot that was approved. finish(false,snapshot.headers); }catch{if(current())await audit('dlp.block','headers-inspection-incomplete',new URL(details.url).hostname);finish(true);} finally{clearTimeout(timer);} }).catch(()=>finish(true)); }); // Session request, header and permission gates are installed exactly once. } function interactionObserverContext(tab){ const contents=tab.contents||tab.view.webContents,nativeContext=tabContext(tab.id); return {ready:ready&¤tTabContext(nativeContext),enabled:policy?.evidence?.enabled===true&&policy.evidence.captureInteractions===true,policyVersion:policy?.version||0,sessionId:tab.id,url:contents.getURL(),token:`${tab.security.interactionDocumentGeneration}:${nativeContext.activation}`,foreground:tab===activeTab()&&!tab.startPage&&!overviewPanel&&!toolsOpen,webContents:contents,nativeContext,documentGeneration:tab.security.interactionDocumentGeneration}; } function currentInteractionObserverContext(context){ const tab=activeTab(); if(!context||!ready||!tab||policy?.evidence?.enabled!==true||policy.evidence.captureInteractions!==true||context.policyVersion!==policy.version||Date.parse(policy.expiresAt)<=Date.now()||context.sessionId!==tab.id||context.webContents!==tab.contents||tab.contents.isDestroyed()||context.url!==tab.contents.getURL()||context.documentGeneration!==tab.security.interactionDocumentGeneration||tab.startPage||overviewPanel||toolsOpen)return false; const live=tabs.context(tab.id); return !live.paused&&live.activation===context.nativeContext.activation; } function installSiteHooks(tab){ const contents=tab.view.webContents,security=tab.security; security.interactionDocumentGeneration=0; security.interactionObserver=require('./human-interaction.cjs').createHumanInteractionObserver({ getContext:()=>interactionObserverContext(tab),isCurrent:currentInteractionObserverContext, executeScript:(target,code,operation)=>diagnosticSpan('browser.evidence',{operation:`evidence.observer.${operation}`,sessionId:tab.id},()=>target.executeJavaScriptInIsolatedWorld(998,[{code}],false)), onObserved:(event,context)=>diagnosticSpan('browser.evidence',{operation:'evidence.capture',sessionId:context.sessionId},async()=>{ if(!currentInteractionObserverContext(context))return; const payload=require('./evidence-capture.cjs').buildInteractionEvidence({...event,sessionId:context.sessionId,url:context.url,captureSource:'human-observer'},policy); if(!payload||!currentInteractionObserverContext(context))return; await audit('browser.interaction',undefined,new URL(payload.pageUrl).hostname,undefined,undefined,payload,undefined,{action:payload.action,sessionId:payload.sessionId,outcome:'observed'}); }), onDropped:async(count,context)=>{if(currentInteractionObserverContext(context)){diagnostics.event('browser.evidence',diagnosticContext({operation:'evidence.capture',sessionId:context.sessionId,count,status:'error'}));await audit('browser.interaction.dropped','interaction-capture-bounded');}} }); contents.on('did-finish-load',()=>security.interactionObserver?.refresh()); contents.once('destroyed',()=>security.interactionObserver?.dispose()); const developerTools=createDeveloperToolsGuard({contents,getContext:()=>({ready,foreground:tab===activeTab()&&!tab.startPage&&!overviewPanel&&!toolsOpen,policy,url:contents.getURL(),token:tabContext(tab.id)}),isCurrent:context=>currentTabContext(context.token),audit:kind=>audit(kind),createFrontend:()=>{const frontend=new BrowserWindow({show:false,width:1000,height:750,title:'S Browser · Privileged developer tools',webPreferences:{session:browserSession,nodeIntegration:false,contextIsolation:true,sandbox:true,devTools:false}});security.developerFrontend=frontend.webContents;frontend.webContents.setWindowOpenHandler(()=>({action:'deny'}));return frontend;}}); security.developerToolsGuard=developerTools; contents.setWindowOpenHandler(({url})=>{createNativeTab({url,agentAuthority:tab.security.agentAuthority}).catch(e=>{message=e.message;emit();});return {action:'deny'};}); contents.on('will-navigate',(event,url)=>{if(!ready||!core.destinationAllowed(url,policy)||(tab.security.agentAuthority&&!actorDestinationAllowed(url,tab.security.agentAuthority)))event.preventDefault();}); contents.on('will-attach-webview',event=>event.preventDefault()); contents.on('before-input-event',(event,input)=>{if(developerTools.handleInput(event,input))return;const approval=security.visualApproval;if(policy?.security.visualPhishing.enabled&&(!approval||approval.generation!==tab.navigationId||approval.version!==policy.version||approval.url!==contents.getURL())){event.preventDefault();checkVisual(tab).catch(()=>{});}}); contents.on('did-start-navigation',(_event,_url,_inPlace,isMainFrame)=>{if(isMainFrame){if(!_inPlace){security.interactionDocumentGeneration++;security.interactionObserver?.refresh();}tabs.navigation(tab.id);if(_url!=='about:blank')tab.startPage=false;}}); contents.on('did-navigate',()=>emit());contents.on('did-navigate-in-page',()=>emit()); contents.on('did-finish-load',()=>pageTheme(tab));contents.on('page-title-updated',()=>{tab.title=contents.getTitle().slice(0,200);if(tab===activeTab())pageTitle=tab.title;emit();}); installClipboardControls(contents); security.visualScanner=createVisualPhishing({getContext:()=>({ready,policy,webContents:contents,url:contents.getURL(),navigationId:tab.navigationId}), describe:url=>visualDeadline(contents.executeJavaScriptInIsolatedWorld(999,[{code:describeScript(url)}],false)), capture:async(scope,description)=>{let rect;if(scope==='login-form'){const r=description.formRect;if(!r)throw Error('Login region unavailable');rect={x:Math.floor(r.x),y:Math.floor(r.y),width:Math.ceil(r.width),height:Math.ceil(r.height)};}const bytes=await visualDeadline(capturePNG(contents,rect));if(bytes.length<24)throw Error('Capture dimensions unavailable');return {bytes,width:bytes.readUInt32BE(16),height:bytes.readUInt32BE(20)};}, fingerprint:capture=>{try{return fingerprintPNG(capture.bytes);}finally{capture.bytes.fill(0);}},compare:hammingDistance, audit:event=>audit(event.kind,event.ruleId,event.host),block:result=>{security.visualBlocked={...result,message:result.reason==='credential-brand-impersonation'?'Blocked: this login resembles a protected brand on an unapproved origin.':'Blocked: this login could not be inspected safely.'};security.visualApproval=null;quarantine(security.visualBlocked.message);} }); } function createNativeTab(input={}){return diagnosticSpan('browser.tab',{operation:'create'},()=>createNativeTabImpl(input));} async function createNativeTabImpl(input={}){ if(!input||typeof input!=='object'||Object.keys(input).some(key=>!['group','url'].includes(key)))throw Error('Invalid native tab request'); if(input.group)requireManagedSection('library'); const tab=tabs.create({group:input.group||''});tab.security.agentAuthority=input.agentAuthority||agentActorStorage.getStore()?.principalId&&{...agentActorStorage.getStore()};installSiteHooks(tab); try{ await tab.view.webContents.loadURL('about:blank'); await diagnosticSpan('browser.clipboard',{operation:'create',sessionId:tab.id},()=>attachPageClipboardGuard(tab.view.webContents,()=>{clipboardFatal=true;quarantine('Managed clipboard protection failed; restart the browser');audit('clipboard.block','clipboard-primitive-unavailable');app.quit();})); tab.guarded=true;selectTab(tab.id); if(input.url){if(tab.security.agentAuthority){await agentActorStorage.run(tab.security.agentAuthority,()=>navigate({url:input.url,tabId:tab.id}));}else await navigate({url:input.url,tabId:tab.id});}emit();return state(); }catch(error){if(tabs.all().includes(tab)){tabs.remove(tab.id);if(tabs.active()?.guarded)selectTab(tabs.active().id);}throw error;} } function updateManagedAppearance(input){requireManagedSection('appearance');appearancePreferences={...appearancePreferences,...validateAppearance(input)};persistAppearance();emit();return managedSettings();} async function closeNativeTab(id){tabs.remove(id);if(!tabs.all().length)await createNativeTab();else selectTab(tabs.active().id);emit();return state();} function groupNativeTab(input){requireManagedSection('library');if(libraryOrganizer?.isBusy())throw Error('Library organization is running');if(!input||typeof input.group!=='string'||input.group.length>40)throw Error('Invalid native tab group');tabs.get(input.id).group=input.group;emit();return state();} function agentAppControls(){ const actorIdentity=()=>{const actor=agentActorStorage.getStore();return actor?.principalId?`${actor.principalId}:${actor.generation}`:'legacy';}; const windowControls=require('./window-controls.cjs').createWindowControls({getWindow:()=>win,getContext:()=>({ready:ready&&featureAllowed(managedSettings(),'agents'),policy,policyVersion:policy?.version,deviceId:device?.id,actor:actorIdentity()}),isCurrent:context=>ready&&policy===context.policy&&device?.id===context.deviceId&&actorIdentity()===context.actor,authorize:action=>actorGrantAllowed(agentActorStorage.getStore(),action),traceOperation:(operation,fn)=>diagnosticSpan('browser.window',{operation},fn)}); const vaultControls=require('./vault-agent-controls.cjs').createVaultAgentControls({ getContext:id=>{const context=agentContext(id);return {...context,ready:context.ready&&featureAllowed(managedSettings(),'passwords'),actor:context.actor||Object.freeze({agentId:'legacy-paired-agent',generation:1})};}, contextCurrent:currentTabContext,getVault:requireVault,checkVisual:context=>checkVisual(tabs.get(context.sessionId)),inspect:inspectManagedAgentText, audit:event=>audit(event.action),traceOperation:(operation,fn)=>diagnosticSpan('browser.vault',{operation},fn) }); const checked=(operation)=>async(args,{signal,policyVersion}={})=>{ if(signal?.aborted||!ready||policy?.version!==policyVersion||!featureAllowed(managedSettings(),'agents'))throw Error('Managed application changed'); return operation(args,{signal}); }; return { 'vault:status':checked((args,options)=>vaultControls.status(args,options)), 'vault:list':checked((args,options)=>vaultControls.list(args,options)), 'vault:fill':checked((args,options)=>vaultControls.fill(args,options)), 'window:state':checked((args,options)=>windowControls.state(args,options)), 'window:control':checked((args,options)=>windowControls.control(args,options)), 'application:state':checked(()=>({ready,policyVersion:policy.version,activeTabId:activeTab()?.id,tabCount:tabs.all().length})), 'settings:get':checked(()=>managedSettings()), 'settings:update':checked(args=>updateManagedAppearance(args.appearance)), 'bookmarks:list':checked(()=>{const result=bookmarkState({policy:effectiveBookmarkPolicy(),ready,settings:managedSettings()});if(agentActorStorage.getStore()?.principalId){result.managed=result.managed.filter(row=>actorDestinationAllowed(row.url));result.personal.items=result.personal.items.filter(row=>actorDestinationAllowed(row.url));}return {...result,policyVersion:policy.version};}), 'bookmarks:mutate':checked(async args=>{await mutateManagedBookmarks(args);return {completed:true};}), 'library:organize-plan':checked((_args,options)=>organizeLibrary(options)), 'library:organize-apply':checked((args,options)=>applyLibraryOrganization(args,options)), 'tabs:create':checked(async args=>{const result=await createNativeTab(args);return {completed:true,sessionId:result.activeTabId};}), 'tabs:activate':checked(args=>{selectTab(args.sessionId);emit();return {completed:true,sessionId:args.sessionId};}), 'tabs:close':checked(async args=>{await closeNativeTab(args.sessionId);return {completed:true};}), 'tabs:group':checked(args=>{groupNativeTab({id:args.sessionId,group:args.group});return {completed:true,sessionId:args.sessionId};}) }; } function ipc(channel,fn){ipcMain.handle(channel,async(event,...args)=>{ if(event.sender!==win.webContents || event.senderFrame!==win.webContents.mainFrame || event.senderFrame?.url!==uiUrl)throw Error('Untrusted IPC sender'); try{return {ok:true,data:await diagnosticSpan('browser.ipc',{operation:channel.slice('managed:'.length)},()=>fn(...args))};}catch(e){return {ok:false,error:e.message};} });} app.whenReady().then(async()=>{ if(!ownsBrowserInstance)return; if(cliRequest){try{await require('./agent-client.cjs').runClient(cliConnection,cliRequest);}catch{console.error('Local agent action failed. Check policy and encrypted pairing file.');app.exit(1);}return;} core=await import('../../packages/core/policy.mjs'); clipboardCore=await import('../../packages/core/clipboard.mjs'); ipcMain.on('managed:clipboard-sandbox-failure',event=>{if(tabs?.fromContents(event.sender.id)||event.sender===win?.webContents){clipboardFatal=true;quarantine('Managed renderer security failed. Restart this browser.');}}); powerMonitor.on('lock-screen',lockVault);powerMonitor.on('suspend',lockVault); securityCore=await import('../../packages/core/security.mjs'); localInspector=startInspector(app.isPackaged?process.resourcesPath:path.join(__dirname,'../..'),app.isPackaged,{onStages:stages=>diagnostics.importStages(stages)}); browserSession=session.fromPartition('persist:safetynet-managed',{cache:false}); // Management has its own cookie jar and no website request gate. It uses the // same company proxy and Chromium DNS configuration as managed website traffic. managementSession=session.fromPartition('persist:safetynet-management',{cache:false}); browserSession.registerPreloadScript({type:'frame',id:'managed-clipboard-deny',filePath:path.join(__dirname,'clipboard-deny.cjs')}); session.defaultSession.registerPreloadScript({type:'frame',id:'shell-clipboard-deny',filePath:path.join(__dirname,'clipboard-deny.cjs')}); Menu.setApplicationMenu(null); win=new BrowserWindow({width:1320,height:900,minWidth:800,minHeight:600,frame:false,roundedCorners:true,hasShadow:true,backgroundMaterial:'acrylic',backgroundColor:'#eef1f5',autoHideMenuBar:true,webPreferences:{preload:path.join(__dirname,'preload.cjs'),nodeIntegration:false,contextIsolation:true,sandbox:true,devTools:false}}); win.removeMenu(); tabs=createTabRegistry({createView:()=>new WebContentsView({webPreferences:{session:browserSession,preload:path.join(__dirname,'site-preload.cjs'),nodeIntegration:false,nodeIntegrationInSubFrames:true,contextIsolation:true,sandbox:true,webSecurity:true,allowRunningInsecureContent:false}}),attach:v=>win.contentView.addChildView(v),detach:v=>win.contentView.removeChildView(v),dispose:tab=>{tab.security.interactionObserver?.dispose();disposePageClipboardGuard(tab.contents);}}); const executeClipboard=(mode,args)=>args.webContents.executeJavaScriptInIsolatedWorld(999,[{code:clipboardScript(mode,args)}],false); clipboardGuard=createClipboardGuard({ getContext:contents=>{const target=contents||view?.webContents,tab=target&&tabs.fromContents(target.id),site=!!tab,url=target?.getURL()||'',token=site?tabContext(tab.id):null;return {ready:ready&&target?.isFocused()&&(site?tab===activeTab()&&!tab.startPage&&!overviewPanel&&!toolsOpen&&core.destinationAllowed(url,policy):target===win.webContents&&url===uiUrl),policy,webContents:target,url,navigationId:site?tab.navigationId:0,tabId:tab?.id,activation:token?.activation};}, stage:async args=>{const tab=tabs.fromContents(args.webContents.id);if(tab&&args.operation==='paste'&&policy?.security.visualPhishing.enabled&&(await checkVisual(tab)).decision!=='allow')throw Error('Login protection holds paste');return executeClipboard('stage',args);}, validate:args=>executeClipboard('validate',args),commit:args=>executeClipboard('commit',args), discard:args=>executeClipboard('discard',{...args,url:'',operation:'copy',limit:0}), readClipboard:async limit=>{const items=await clipboard.read();if(items.length!==1)throw Error('Clipboard format unsupported');const item=items[0];if(!item.types.includes('text/plain')||item.types.some(type=>type!=='text/plain'&&!/^electron application\/osclipboard;format="(?:CF_TEXT|CF_UNICODETEXT|CF_LOCALE)"$/.test(type)))throw Error('Rich clipboard denied');const blob=await item.getType('text/plain');if(blob.size>limit)throw Error('Clipboard size limit');return blob.text();}, writeClipboard:text=>clipboard.writeText(text), inspect:async(bytes,currentPolicy,operation)=>{let verdict=clipboardCore.inspectClipboard(bytes,currentPolicy,operation);if(verdict.decision!=='allow')return verdict;if(currentPolicy.dlp.semantic.enabled||currentPolicy.security.yara.enabled){if(!localInspector)return {decision:'block',ruleId:'clipboard-inspector-unavailable'};verdict=await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,currentPolicy));}if(verdict.decision!=='allow')return verdict;const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,currentPolicy,'clipboard'));return external.decision==='allow'?verdict:external;}, onCompleted:async metadata=>{ const payload=await diagnosticSpan('browser.evidence',{operation:'evidence.capture',sessionId:metadata.sessionId},()=>require('./evidence-capture.cjs').buildInteractionEvidence({...metadata,url:metadata.pageUrl,captureSource:'guarded-clipboard'},policy)); await audit(`clipboard.${metadata.action}`,undefined,undefined,undefined,undefined,payload,undefined,{action:metadata.action,sessionId:metadata.sessionId,outcome:'completed'}); }, audit:async(kind,ruleId,facts)=>{const parsed=require('../../packages/core/interaction-facts.cjs').InteractionFactsSchema.safeParse(facts);await audit(kind,ruleId,undefined,undefined,undefined,undefined,undefined,parsed.success?parsed.data:undefined);if(kind==='clipboard.block'&&ready){message=`Clipboard blocked by company policy (${ruleId}).`;emit();}} }); installClipboardControls(win.webContents);hooks(); ipcMain.on('managed:visual-policy',event=>{event.returnValue={enabled:!!tabs.fromContents(event.sender.id)&&!!policy?.security.visualPhishing.enabled};}); ipcMain.handle('managed:visual-check',async(event,input)=>{ const tab=tabs.fromContents(event.sender.id);if(!tab||event.senderFrame!==event.sender.mainFrame||!input||typeof input.token!=='string'||input.token.length>80||!Number.isSafeInteger(input.revision)||input.revision<0||input.url!==event.sender.getURL())return {decision:'unknown'}; const security=tab.security;if(input.token!==security.visualToken||input.revision!==security.visualRevision){tabs.navigation(tab.id);security.visualToken=input.token;security.visualRevision=input.revision;} try{return await checkVisual(tab);}catch{return {decision:'unknown'};} }); await createNativeTab(); win.webContents.setWindowOpenHandler(()=>({action:'deny'}));win.webContents.on('will-navigate',event=>event.preventDefault()); ipc('managed:state',()=>({...state(),pageTitle,pageColor,windowMaximized:win.isMaximized()})); ipc('managed:window-control',action=>{if(action==='minimize')win.minimize();else if(action==='maximize'){win.isMaximized()?win.unmaximize():win.maximize();}else if(action==='close')win.close();else throw Error('Unknown window action');}); ipc('managed:authorize-setting',section=>{requireManagedSection(section);return {authorized:true,policyVersion:policy.version};}); ipc('managed:bookmarks-mutate',mutateManagedBookmarks); ipc('managed:library-organize',organizeLibrary); ipc('managed:library-apply',applyLibraryOrganization); ipc('managed:settings-update',updateManagedAppearance); ipc('managed:chrome-layout',async input=>{ if(!input||![64,104].includes(input.height)||['sidebarOpen','startPageOpen','overviewOpen'].some(key=>typeof input[key]!=='boolean'))throw Error('Unsupported browser layout'); if(managedSettings().available&&!sectionAllowed(managedSettings(),'appearance')&&input.height!==(managedSettings().appearance.tabMode==='compact'?64:104))throw Error('Tab layout is controlled by your administrator'); if(input.toolsOpen&&!managedSettings().visible)throw Error('Settings access is disabled by your administrator'); if(input.sidebarOpen&&!libraryVisible(managedSettings()))throw Error('Library access is controlled by your administrator'); if(input.toolsOpen!==undefined&&typeof input.toolsOpen!=='boolean')throw Error('Unsupported tools layout');chromeHeight=input.height;leftSidebar=input.sidebarOpen;startPage=activeTab()?.startPage??true;overviewPanel=input.overviewOpen;toolsOpen=!!input.toolsOpen;layout(); if(policy?.security.visualPhishing.enabled)await configureVisualPage(true,activeTab());return {height:chromeHeight}; }); ipc('managed:share-url',async()=>{ if(!ready)throw Error('Browser access paused');const initial=policy,context=tabContext(),contents=context.webContents,pageURL=contents.getURL(),url=new URL(pageURL); if(!['https:','http:'].includes(url.protocol)||url.username||url.password)throw Error('Cannot share this page');url.search='';url.hash='';const text=url.toString(),bytes=Buffer.from(text); let verdict=clipboardCore.inspectClipboard(bytes,initial,'copy'); if(verdict.decision==='allow'&&(initial.dlp.semantic.enabled||initial.security.yara.enabled))verdict=localInspector?await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,initial)):{decision:'block',ruleId:'clipboard-inspector-unavailable'}; if(verdict.decision==='allow')verdict=await diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,initial,'clipboard')); if(verdict.decision!=='allow'){await audit('clipboard.block',verdict.ruleId);throw Error('Company policy blocks sharing this link');} await audit('clipboard.copy','authorized-link'); if(!ready||policy!==initial||Date.parse(initial.expiresAt)<=Date.now()||!currentTabContext(context))throw Error('Browser state changed during link inspection'); await clipboard.writeText(text);return {copied:true}; }); ipc('managed:enroll',async input=>{ if(device)throw Error('Already enrolled');requireEncryption(); if(!input||typeof input.server!=='string'||typeof input.code!=='string'||input.code.length>200)throw Error('Invalid enrollment'); const server=managementUrl(input.server),result=await request(server,'/api/device/enroll',{code:input.code,name:os.hostname(),platform:process.platform}); return completeEnrollment(server,result); }); ipc('managed:sso',async input=>{ if(device)throw Error('Already enrolled');requireEncryption();const server=managementUrl(input.server); const ticket=await request(server,'/api/device/sso/start',{}); const loginUrl=new URL(ticket.url);if(loginUrl.origin!==server || loginUrl.pathname!=='/auth/login')throw Error('Invalid login URL'); await shell.openExternal(loginUrl.toString()); message='Complete enterprise sign-in in your system browser…';emit(); for(let attempt=0;attempt<100;attempt++){ await new Promise(resolve=>setTimeout(resolve,3000)); const result=await request(server,'/api/device/sso/poll',{id:ticket.id,secret:ticket.secret,name:os.hostname(),platform:process.platform}); if(!result.pending)return completeEnrollment(server,result); } throw Error('Enterprise sign-in expired'); }); ipc('managed:tab-create',createNativeTab); ipc('managed:tab-activate',id=>{selectTab(id);emit();return state();}); ipc('managed:tab-close',closeNativeTab); ipc('managed:tab-group',groupNativeTab); ipc('managed:navigate',navigate); ipc('managed:back',()=>{if(ready&&view.webContents.navigationHistory.canGoBack())view.webContents.navigationHistory.goBack();}); ipc('managed:forward',()=>{if(ready&&view.webContents.navigationHistory.canGoForward())view.webContents.navigationHistory.goForward();}); ipc('managed:reload',()=>{if(ready)view.webContents.reload();}); ipc('managed:ai-panel',open=>{if(open)requireFeatureSection('ai');aiPanel=!!open;if(aiPanel){vaultPanel=false;lockVault();}layout();}); ipc('managed:vault-panel',open=>{if(open)requireVault();vaultPanel=!!open;if(vaultPanel)aiPanel=false;else lockVault();layout();}); ipc('managed:vault-status',()=>{requireFeatureSection('passwords');return vaultStatus();}); ipc('managed:vault-create',async master=>{requireVault().create(master);await audit('vault.created');if(!ready){lockVault();throw Error('Browser access paused');}emit();return vaultStatus();}); ipc('managed:vault-unlock',async master=>{requireVault().unlock(master);await audit('vault.unlocked');if(!ready){lockVault();throw Error('Browser access paused');}emit();return vaultStatus();}); ipc('managed:vault-lock',()=>{lockVault();return vaultStatus();}); ipc('managed:vault-list',()=>requireVault().list()); ipc('managed:vault-save',async entry=>{if(!vaultSettings().allowSaving)throw Error('Saving passwords is disabled by company policy');const result=requireVault().upsert(entry);await audit('vault.saved');emit();return result;}); ipc('managed:vault-delete',async id=>{if(!vaultSettings().allowSaving)throw Error('Password changes are disabled by company policy');const result=requireVault().delete(id);await audit('vault.deleted');return result;}); ipc('managed:vault-generate',()=>{const current=requireVault();if(!vaultSettings().allowSaving||current.status().locked)throw Error('Unlock the vault before generating a password');return current.generatePassword();}); ipc('managed:vault-fill',async id=>{ const context=tabContext(),tab=tabs.get(context.tabId),contents=context.webContents; if(startPage||overviewPanel||toolsOpen)throw Error('Show the saved login website before filling credentials'); if((await checkVisual(tab)).decision!=='allow'||!currentTabContext(context))throw Error('Visual login protection has not approved this page'); if(startPage||overviewPanel||toolsOpen)throw Error('The login website is no longer visible'); const current=requireVault(),initial=policy,url=context.url; if(!initial.passwordManager.allowAutofill||!core.destinationAllowed(url,initial)||new URL(url).protocol!=='https:')throw Error('Password filling requires an approved HTTPS destination'); let credential; try{ const origin=new URL(url).origin;credential={...current.credential(id,origin),origin}; await audit('vault.fill-requested'); if(!currentTabContext(context)||startPage||overviewPanel||toolsOpen||policy!==initial||Date.parse(initial.expiresAt)<=Date.now()||current.status().locked)throw Error('Browser state changed'); contents.focus();if(!currentTabContext(context))throw Error('Browser state changed'); const result=await diagnosticSpan('browser.password.fill',{sessionId:context.tabId},()=>contents.executeJavaScriptInIsolatedWorld(999,[{code:fillScript(url,credential,Date.parse(initial.expiresAt))}],false)); if(!currentTabContext(context)||startPage||overviewPanel||toolsOpen||policy!==initial||current.status().locked||result?.completed!==true)throw Error('Browser state changed'); await audit('vault.filled');return {completed:true}; }catch{throw Error('Cannot fill this login. Unlock the vault and use its exact HTTPS origin with a single visible same-origin POST login form.');} finally{if(credential){credential.username='';credential.password='';}} }); ipc('managed:ai',managedAI); ipc('managed:agent-connection',async principalId=>{ requireFeatureSection('agents'); if(!ready||!policy.agents.enabled||!agentService)throw Error('Agent access is disabled by company policy');if(policy.agents.identityMode==='individual')return pairIndividualAgent(principalId);if(principalId!==undefined)throw Error('Individual agent selection requires individual mode');return {address:agentService.address,credentialPath:agentFile()}; }); ipcMain.on('managed:page-content',async(event,input)=>{ const tab=tabs.fromContents(event.sender.id);if(!tab||event.senderFrame!==event.sender.mainFrame||!ready||typeof input?.text!=='string'||input.text.length>250000)return; const initial=policy,context=tabContext(tab.id,{foreground:false});if(!initial.security.content.blockedPhrases.length)return; const result=core.inspect(Buffer.from(input.text),initial);if(result.decision==='block'&&result.ruleId?.startsWith('content-')&¤tTabContext(context)&&policy===initial){await audit('content.block',result.ruleId);if(!currentTabContext(context))return;tab.security.contentBlocked={checkedURL:context.url,policyVersion:initial.version,message:'This page contains content blocked by company policy.'};quarantine(tab.security.contentBlocked.message);} }); ipc('managed:inspect-file',async()=>{ requireFeatureSection('documentScan'); if(!ready||!policy)throw Error('Device must be managed before inspecting files'); if(policy.inspection?.documentAnalysisEnabled===false)throw Error('Document analysis is disabled for this browser by your administrator'); if(!localInspector)throw Error('Local content inspector is not installed'); const currentPolicy=policy; const selection=await dialog.showOpenDialog(win,{title:icapRequired(policy,'upload')?'Inspect content with company security services':'Inspect company content locally',properties:['openFile']});if(selection.canceled)return null; requireFeatureSection('documentScan'); if(!ready||policy!==currentPolicy)throw Error('Policy changed during file selection; inspect again'); const file=selection.filePaths[0],handle=await fs.promises.open(file,'r');let bytes; try{const info=await handle.stat();if(!info.isFile()||info.size>policy.dlp.maxBytes)throw Error('File exceeds company inspection limit'); // Read a bounded immutable snapshot, never release the subsequently mutable original. const chunks=[];let size=0;for await(const chunk of handle.createReadStream()){size+=chunk.length;if(size>policy.dlp.maxBytes)throw Error('File exceeds company inspection limit');chunks.push(chunk);}bytes=Buffer.concat(chunks); }finally{await handle.close();} if(!ready||policy!==currentPolicy){bytes.fill(0);throw Error('Policy changed during file selection; inspect again');} let result; try{result=await diagnosticSpan('browser.inspect.local',{bytes:bytes.length},()=>localInspector.inspect(bytes,currentPolicy));if(result.decision==='allow'&&icapRequired(currentPolicy,'upload')){const external=await diagnosticSpan('browser.inspect.enterprise',{bytes:bytes.length},()=>scanEnterprise(bytes,currentPolicy,'upload'));if(external.decision!=='allow')result=external;}}finally{bytes.fill(0);} if(!ready||currentPolicy.version!==policy.version)throw Error('Policy changed during inspection; inspect again'); if(result.decision==='block')await audit('dlp.block',result.ruleId); return {name:path.basename(file),...result}; }); win.on('close',()=>tabs.close()); win.on('resize',layout);win.on('maximize',emit);win.on('unmaximize',emit);win.on('blur',lockVault);win.on('minimize',lockVault);win.on('closed',()=>{vault?.lock();clearInterval(vaultTimer);win=null;clearInterval(syncTimer);localInspector?.stop();agentService?.close();}); loadAppearance(); await win.loadFile(path.join(__dirname,'index.html'));layout(); try{device=loadDevice();const queuePath=path.join(app.getPath('userData'),'audit-queue.bin');if(device&&fs.existsSync(queuePath)){auditQueue=require('../../packages/core/request-transfer-facts.cjs').migrateQueuedRequestFacts(JSON.parse(safeStorage.decryptString(fs.readFileSync(queuePath))));persistAudit();}if(device){await sync();await audit('browser.started');}}catch(e){quarantine(`Enrollment could not load: ${e.message}`);} const refreshMs=!app.isPackaged&&process.env.SAFETYNET_SMOKE_USERDATA===app.getPath('userData')?1000:15000; syncTimer=setInterval(()=>{if(policy&&Date.parse(policy.expiresAt)<=Date.now())quarantine('Company policy expired');sync();},refreshMs); networkTimer=setInterval(async()=>{ if(!policy?.enterprise?.vpn.required||networkChecking)return;networkChecking=true; const checkedPolicy=policy; try{const posture=await diagnosticSpan('browser.network',{operation:'scan'},()=>networkPosture.check(checkedPolicy.enterprise.vpn));if(policy===checkedPolicy&&!posture.allowed){managementHealthy=false;quarantine('Required corporate network is unavailable. Connect your company VPN.');await Promise.all([browserSession.closeAllConnections(),managementSession.closeAllConnections()]);}} finally{networkChecking=false;} },5000); let wasLocked=true;vaultTimer=setInterval(()=>{const locked=vault?.status().locked??true;if(locked!==wasLocked){if(locked)audit('vault.locked');wasLocked=locked;emit();}},1000); }).catch(error=>{console.error(error.stack);app.quit();}); app.on('window-all-closed',()=>app.quit()); app.on('before-quit',()=>{fileUploadGate?.dispose();tabs?.close();}); app.on('will-quit',()=>{Promise.resolve(diagnostics.shutdown()).catch(()=>{});vault?.lock();clearInterval(vaultTimer);clearInterval(networkTimer);localInspector?.stop();agentService?.close();}); app.on('certificate-error',(event,_wc,_url,_error,_certificate,callback)=>{event.preventDefault();callback(false);}); app.on('login',(event,_wc,_details,authInfo,callback)=>{ event.preventDefault(); // Proxy credentials are supplied by deployment environment, never a page or policy bundle. if(authInfo.isProxy&&process.env.SAFETYNET_PROXY_USER&&process.env.SAFETYNET_PROXY_PASSWORD)callback(process.env.SAFETYNET_PROXY_USER,process.env.SAFETYNET_PROXY_PASSWORD); else callback(); });