Implement managed browser security, individual agents and observability

This commit is contained in:
Karl committed 2026-10-13 16:58:15 +02:00
1 parent ff482ebe23
commit ea448de2d4
172 files changed
+5404 -347

No files matched your search

+12 -8
View File
@@ -6,7 +6,7 @@ let credential='',policy,presetPreview,managedBookmarks=[],personalBookmarks=[],
const managedSections=['appearance','library','ai','passwords','documentScan','agents'];
const agentScopes=['sessions','navigate','read','click','type','screenshot','ai','application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group'];
const agentScopes=['sessions','navigate','back','forward','reload','read','click','type','select','check','screenshot','ai','application:state','settings:get','settings:update','bookmarks:list','bookmarks:mutate','tabs:create','tabs:activate','tabs:close','tabs:group','library:organize-plan','library:organize-apply','inspect','scroll'];
function status(message,error=false){$('status').textContent=message;$('status').className=error?'error':'';}
@@ -28,6 +28,8 @@ function fill(p){
const bookmarks=p.browser.bookmarks||{managed:[],allowUserAdd:true,allowUserEdit:true,allowUserDelete:true,adminReadUserBookmarks:false};managedBookmarks=structuredClone(bookmarks.managed);for(const [id,key] of bookmarkPermissions)$(id).checked=bookmarks[key];renderBookmarkRows('managed-bookmark-rows',managedBookmarks);clearDeviceBookmarks();
$('library-ai-enabled').checked=p.library?.aiSuggestionsEnabled===true;$('library-ai-autoapply').checked=p.library?.aiAutoApplyEnabled===true;
$('visual-enabled').checked=p.security.visualPhishing.enabled;
const e=p.enterprise;$('enterprise-dns-mode').value=e.dns.mode;$('enterprise-dns-templates').value=e.dns.templates.join('\n');$('enterprise-vpn-required').checked=e.vpn.required;$('enterprise-vpn-interfaces').value=e.vpn.interfaceNames.join('\n');$('enterprise-vpn-dns').value=e.vpn.requiredDnsServers.join('\n');$('enterprise-icap-enabled').checked=e.icap.enabled;$('enterprise-icap-url').value=e.icap.url;$('enterprise-icap-method').value=e.icap.method;$('enterprise-icap-timeout').value=e.icap.timeoutMs;$('enterprise-icap-max').value=e.icap.maxBytes;for(const [id,key] of [['uploads','scanUploads'],['downloads','scanDownloads'],['clipboard','scanClipboard'],['ai','scanAI'],['insecure','allowInsecureTransport']])$(`enterprise-icap-${id}`).checked=e.icap[key];presetPreview=null;$('preset-apply').disabled=true;$('preset-description').textContent='';$('preset-changes').replaceChildren();
@@ -70,7 +72,7 @@ function draft(){
p.passwordManager={enabled:$('vault-enabled').checked,allowSaving:$('vault-saving').checked,allowAutofill:$('vault-autofill').checked,idleLockSeconds:Number($('vault-idle').value)};
p.brand={name:$('brand-name').value,color:$('brand-color').value};
p.brand={...p.brand,name:$('brand-name').value,color:$('brand-color').value};
p.browser={homeUrl:$('home-url').value,allowedHosts:lines('allowed-hosts'),blockedHosts:lines('blocked-hosts'),allowDownloads:$('downloads').checked,allowDevTools:$('devtools').checked,allowWebSockets:$('websockets').checked};
@@ -86,7 +88,9 @@ function draft(){
p.security.dns={...p.security.dns,enabled:$('security-dns-enabled').checked,blockedHosts:lines('security-dns-hosts'),blockedIPs:lines('security-dns-ips'),failClosed:true};p.security.content.blockedPhrases=lines('security-content-phrases');p.security.yara.enabled=$('security-yara-enabled').checked;p.security.yara.maxScanBytes=Number($('security-yara-max').value);
p.agents={enabled:$('agent-enabled').checked,scopes:agentScopes.filter(scope=>$(`agent-scope-${scope}`).checked),allowedHosts:lines('agent-hosts'),allowSensitiveRead:$('agent-sensitive').checked};p.ai={enabled:$('ai-enabled').checked,model:$('ai-model').value,provider:$('ai-provider').value,trustMode:$('ai-trust').value,endpointId:$('ai-endpoint').value,allowConfidentialLocal:$('ai-confidential-local').checked};p.dlp.maxBytes=Number($('max-bytes').value);p.dlp.semantic.enabled=$('semantic-enabled').checked;return p;
p.library={aiSuggestionsEnabled:$('library-ai-enabled').checked,aiAutoApplyEnabled:$('library-ai-autoapply').checked};
p.agents={...p.agents,enabled:$('agent-enabled').checked,scopes:agentScopes.filter(scope=>$(`agent-scope-${scope}`).checked),allowedHosts:lines('agent-hosts'),allowSensitiveRead:$('agent-sensitive').checked};p.ai={enabled:$('ai-enabled').checked,model:$('ai-model').value,provider:$('ai-provider').value,trustMode:$('ai-trust').value,endpointId:$('ai-endpoint').value,allowConfidentialLocal:$('ai-confidential-local').checked};p.dlp.maxBytes=Number($('max-bytes').value);p.dlp.semantic.enabled=$('semantic-enabled').checked;return p;
}
@@ -312,21 +316,21 @@ async function loadHistory(older=false){
const data=await api(`/api/admin/policy/history?limit=20${older&&historyNextBefore?`&before=${historyNextBefore}`:''}`);if(!older)$('history-rows').replaceChildren();clearHistoryReview();
for(const item of data.versions){const row=document.createElement('tr');cell(row,`v${item.version}${item.current?' · current':''}`);cell(row,new Date(item.time).toLocaleString());cell(row,item.actor);const action=cell(row,''),button=document.createElement('button');button.className='secondary';button.textContent=item.current?'Review current version':'Compare with current';button.onclick=task(()=>reviewHistory(item.version));action.append(button);$('history-rows').append(row);}
for(const item of data.versions){const row=document.createElement('tr');cell(row,`v${item.version}${item.current?' - current':''}`);cell(row,new Date(item.time).toLocaleString());cell(row,item.actor);const action=cell(row,''),button=document.createElement('button');button.className='secondary';button.textContent=item.current?'Review current version':'Compare with current';button.onclick=task(()=>reviewHistory(item.version));action.append(button);$('history-rows').append(row);}
historyNextBefore=data.nextBefore;$('history-more').hidden=!historyNextBefore;$('history-status').textContent=`${$('history-rows').children.length} persisted versions shown. Current signed policy: v${data.currentVersion}.`;
}
function renderHistoryDiff(changes){$('history-diff-list').replaceChildren();for(const change of changes){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong');title.textContent=`${change.path} · ${change.kind}`;const before=document.createElement('pre'),after=document.createElement('pre');before.textContent=`Selected version: ${JSON.stringify(change.before)??'unset'}`;after.textContent=`Current version: ${JSON.stringify(change.after)??'unset'}`;item.append(title,before,after);$('history-diff-list').append(item);}}
function renderHistoryDiff(changes){$('history-diff-list').replaceChildren();for(const change of changes){const item=document.createElement('article');item.className='change-item';const title=document.createElement('strong');title.textContent=`${change.path} - ${change.kind}`;const before=document.createElement('pre'),after=document.createElement('pre');before.textContent=`Selected version: ${JSON.stringify(change.before)??'unset'}`;after.textContent=`Current version: ${JSON.stringify(change.after)??'unset'}`;item.append(title,before,after);$('history-diff-list').append(item);}}
async function reviewHistory(version){
clearHistoryReview();const current=await api('/api/admin/policy/history?limit=1');if(current.currentVersion!==publishedSnapshot.version)throw Error('Current policy changed. Refresh the workspace before comparing or restoring.');
const comparison=await api(`/api/admin/policy/diff?from=${version}&to=${current.currentVersion}`);if(comparison.truncated||comparison.groupDiff?.truncated)throw Error('Policy difference exceeds the supported review size. Review the policy through the API before restoration.');
const comparison=await api(`/api/admin/policy/diff?from=${version}&to=${current.currentVersion}`);if(!Array.isArray(comparison.changes)||!Array.isArray(comparison.groupDiff?.changes)||!Array.isArray(comparison.profileDiff?.changes))throw Error('Complete baseline, group and user-profile differences are required before restoration. Refresh and compare again.');if(comparison.truncated||comparison.groupDiff.truncated||comparison.profileDiff.truncated)throw Error('Policy difference exceeds the supported review size. Review the policy through the API before restoration.');
historyReview={sourceVersion:version,expectedVersion:current.currentVersion};renderHistoryDiff([...comparison.changes,...(comparison.groupDiff?.changes||[]).map(change=>({...change,path:`/policyGroups${change.path}`}))]);$('history-review-title').textContent=`Review v${version} against current v${current.currentVersion}`;$('history-review-summary').textContent=`${comparison.changes.length} baseline paths and ${comparison.groupDiff?.changes.length||0} group paths changed. Restoration publishes v${current.currentVersion+1} with a fresh seven-day expiry. Your unpublished draft will be replaced after successful restoration.`;$('history-review').hidden=false;$('history-approve').disabled=!canWritePolicy()||version===current.currentVersion;$('history-restore').disabled=true;$('history-review').scrollIntoView({block:'nearest',behavior:'smooth'});
historyReview={sourceVersion:version,expectedVersion:current.currentVersion};renderHistoryDiff([...comparison.changes,...comparison.groupDiff.changes.map(change=>({...change,path:`/policyGroups${change.path}`})),...comparison.profileDiff.changes.map(change=>({...change,path:`/userProfiles${change.path}`}))]);$('history-review-title').textContent=`Review v${version} against current v${current.currentVersion}`;$('history-review-summary').textContent=`${comparison.changes.length} baseline paths, ${comparison.groupDiff.changes.length} group paths and ${comparison.profileDiff.changes.length} user-profile/category paths changed. Review verified-subject and device assignments, website access, category definitions and developer-tool grants below. Restoration publishes v${current.currentVersion+1} with a fresh seven-day expiry. Your unpublished draft will be replaced after successful restoration.`;$('history-review').hidden=false;$('history-approve').disabled=!canWritePolicy()||version===current.currentVersion;$('history-restore').disabled=true;$('history-review').scrollIntoView({block:'nearest',behavior:'smooth'});
}
@@ -338,7 +342,7 @@ $('history-restore').onclick=task(async()=>{if(!historyReview||!$('history-appro
for(const id of ['agent-enabled','agent-hosts','agent-sensitive',...agentScopes.map(scope=>`agent-scope-${scope}`)])$(id).addEventListener('input',()=>{$('agents-json').value=JSON.stringify({enabled:$('agent-enabled').checked,scopes:agentScopes.filter(scope=>$(`agent-scope-${scope}`).checked),allowedHosts:lines('agent-hosts'),allowSensitiveRead:$('agent-sensitive').checked},null,2);});
for(const id of ['agent-enabled','agent-hosts','agent-sensitive',...agentScopes.map(scope=>`agent-scope-${scope}`)])$(id).addEventListener('input',()=>{$('agents-json').value=JSON.stringify({...draft().agents,enabled:$('agent-enabled').checked,scopes:agentScopes.filter(scope=>$(`agent-scope-${scope}`).checked),allowedHosts:lines('agent-hosts'),allowSensitiveRead:$('agent-sensitive').checked},null,2);});