2026-10-13 10:33:31 +02:00
import { z } from 'zod' ;
import { isIP } from 'node:net' ;
2026-10-13 23:19:22 +02:00
export const AgentScopes = [ 'network:background' , 'vault:prepare-submit' , 'sessions:save' , 'sessions:list' , 'sessions:restore' , 'vault:status' , 'vault:list' , 'vault:fill' , 'history:list' , 'history:go' , 'window:state' , 'window:control' , 'sessions' , 'navigate' , 'back' , 'forward' , 'reload' , 'read' , 'inspect' , 'scroll' , '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' ];
2026-10-13 10:33:31 +02:00
const host = z . string (). max ( 253 ). regex ( /^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i );
export const VisualOriginSchema = z . string (). max ( 2048 ). refine ( value =>{ try { const u = new URL ( value ); return u . protocol === 'https:' &&! u . username &&! u . password &&! u . search &&! u . hash && u . origin === value &&! u . hostname . includes ( '*' ) &&! u . hostname . endsWith ( '.' ) &&! isIP ( u . hostname . replace ( /^\[|\]$/g , '' )) && /^[a-z0-9.-]+$/ . test ( u . hostname );} catch { return false ;}}, 'Expected a canonical exact HTTPS domain origin' );
export const VisualViewportSchema = z . object ({ width : z . number (). int (). min ( 16 ). max ( 8192 ), height : z . number (). int (). min ( 16 ). max ( 8192 )}). strict ();
export const VisualReferenceSchema = z . object ({ hash : z . string (). regex ( /^[a-f0-9]{16}$/ ), algorithm : z . literal ( 'phash-dct32-v1' ), scope : z . enum ([ 'viewport' , 'login-form' ]), viewport : VisualViewportSchema . optional ()}). strict ();
export const VisualBrandSchema = z . object ({ id : z . string (). min ( 1 ). max ( 80 ), label : z . string (). min ( 1 ). max ( 100 ), allowedOrigins : z . array ( VisualOriginSchema ). min ( 1 ). max ( 50 ), references : z . array ( VisualReferenceSchema ). min ( 1 ). max ( 20 ), maxDistance : z . number (). int (). min ( 0 ). max ( 10 ). default ( 6 ), requiredText : z . array ( z . string (). min ( 1 ). max ( 100 )). max ( 20 ). default ([])}). strict ();
export const SigmaRuleSchema = z . object ({ id : z . string (). min ( 1 ). max ( 80 ), title : z . string (). min ( 1 ). max ( 100 ), level : z . enum ([ 'low' , 'medium' , 'high' , 'critical' ]), detection : z . object ({ selection : z . partialRecord ( z . enum ([ 'kind' , 'ruleId' , 'host' , 'decision' , 'policyVersion' ]), z . union ([ z . string (). max ( 253 ), z . number (). int (), z . boolean ()])). refine ( v => Object . keys ( v ). length > 0 , 'Nonempty metadata selection required' ), condition : z . literal ( 'selection' )}). strict ()}). strict ();
export const SecuritySchema = z . object ({
clipboard : z . object ({ enabled : z . boolean (). default ( true ), allowCopy : z . boolean (). default ( true ), allowCut : z . boolean (). default ( true ), allowPaste : z . boolean (). default ( true ), blockSuspiciousCommands : z . boolean (). default ( true ), failClosed : z . literal ( true ). default ( true ), maxTextBytes : z . number (). int (). min ( 1024 ). max ( 1048576 ). default ( 1048576 )}). strict (). prefault ({}),
dns : z . object ({ enabled : z . boolean (). default ( true ), blockedHosts : z . array ( host ). max ( 10000 ). default ([]), blockedIPs : z . array ( z . string (). refine ( v => isIP ( v ) > 0 , 'Expected exact IPv4 or IPv6 address' )). max ( 10000 ). default ([]), failClosed : z . literal ( true ). default ( true )}). prefault ({}),
2026-10-13 16:58:15 +02:00
yara : z . object ({ enabled : z . boolean (). default ( false ), scanOfficeMacros : z . boolean (). default ( true ), rules : z . array ( z . object ({ id : z . string (). min ( 1 ). max ( 80 ), name : z . string (). min ( 1 ). max ( 100 ), source : z . string (). min ( 1 ). max ( 65536 )}). strict ()). max ( 100 ). default ([]), maxScanBytes : z . number (). int (). min ( 1024 ). max ( 20 * 1024 * 1024 ). default ( 20 * 1024 * 1024 )}). prefault ({}),
2026-10-13 10:33:31 +02:00
content : z . object ({ blockedPhrases : z . array ( z . string (). min ( 1 ). max ( 4096 )). max ( 500 ). default ([])}). prefault ({}),
sigma : z . object ({ rules : z . array ( SigmaRuleSchema ). max ( 100 ). default ([])}). prefault ({}),
visualPhishing : z . object ({ enabled : z . boolean (). default ( false ), failClosed : z . literal ( true ). default ( true ), brands : z . array ( VisualBrandSchema ). max ( 50 ). default ([])}). strict (). prefault ({})
}). prefault ({});
2026-10-13 16:58:15 +02:00
export const AgentGrantSchema = z . object ({ id : z . string (). regex ( /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/ ), name : z . string (). trim (). min ( 1 ). max ( 100 ), enabled : z . boolean (), expiresAt : z . iso . datetime (), scopes : z . array ( z . enum ( AgentScopes )). max ( AgentScopes . length ). refine ( v => new Set ( v ). size === v . length , 'Duplicate agent scopes' ), allowedHosts : z . array ( host ). max ( 200 ), generation : z . number (). int (). min ( 1 ). max ( Number . MAX_SAFE_INTEGER )}). strict ();
export const AgentsSchema = z . object ({ enabled : z . boolean (). default ( false ), identityMode : z . enum ([ 'legacy' , 'individual' ]). default ( 'legacy' ), grants : z . array ( AgentGrantSchema ). max ( 32 ). refine ( v => new Set ( v . map ( g => g . id )). size === v . length , 'Duplicate agent grant identifiers' ). default ([]), scopes : z . array ( z . enum ( AgentScopes )). max ( AgentScopes . length ). default ([ 'sessions' ]), allowedHosts : z . array ( host ). max ( 200 ). default ([]), allowSensitiveRead : z . boolean (). default ( false )}). prefault ({});
2026-10-13 10:33:31 +02:00
export function dnsAddressBlocked ( address , policy ){
const normal = v =>{
let result ; try { result = new URL ( `http://[ ${ v } ]/` ). hostname . toLowerCase (). replace ( /^\[|\]$/g , '' );} catch { return v . toLowerCase ();}
const mapped = /^::ffff:([a-f0-9]{1,4}):([a-f0-9]{1,4})$/ . exec ( result );
if ( mapped ){ const upper = parseInt ( mapped [ 1 ], 16 ), lower = parseInt ( mapped [ 2 ], 16 ); return ` ${ upper >>> 8 } . ${ upper & 255 } . ${ lower >>> 8 } . ${ lower & 255 } ` ;}
return result ;
};
return policy . security ? . dns ? . enabled !== false && ( policy . security ? . dns ? . blockedIPs || []). some ( v => normal ( v ) === normal ( address ));
}
export function evaluateSigma ( event , rules ){ return rules . map ( r => SigmaRuleSchema . parse ( r )). filter ( r => Object . entries ( r . detection . selection ). every (([ k , v ])=>( k === 'kind' ? event . kind : event . metadata ? .[ k ]) === v )). map ( r =>({ ruleId : r . id , title : r . title , level : r . level }));}