Files
safetynet/apps/browser/main.cjs
T

862 lines
101 KiB
JavaScript

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&&sectionAllowed(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:['<all_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&&currentTabContext(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:['<all_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.time<Date.now()-120000||recentRequests.size>2000)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:['<all_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&&currentTabContext(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-')&&currentTabContext(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();
});