// lib/detectors.js — 6 violation detectors for Agent Police Department export const DETECTOR_NAMES = [ 'permission_bypass', 'out_of_scope', 'self_permissioning', 'disallowed_tools', 'redundant_operations', 'off_task' ]; export async function detectViolations(event, policy) { const violations = []; const rules = policy?.rules || []; for (const detector of DETECTOR_NAMES) { const result = runDetector(detector, event, rules); if (result) violations.push(result); } return violations; } function runDetector(name, event, rules) { switch (name) { case 'permission_bypass': return detectPermissionBypass(event, rules); case 'out_of_scope': return detectOutOfScope(event, rules); case 'self_permissioning': return detectSelfPermissioning(event, rules); case 'disallowed_tools': return detectDisallowedTools(event, rules); case 'redundant_operations': return detectRedundantOperations(event, rules); case 'off_task': return detectOffTask(event, rules); default: return null; } } function detectPermissionBypass(event, rules) { const rule = rules.find(r => r.type === 'allowed_domains' && r.enabled); if (!rule) return null; const allowed = rule.params?.domains || []; if (event.type === 'api_call' || event.type === 'tool_use') { const url = event.target || event.url || ''; try { const hostname = new URL(url).hostname; const isAllowed = allowed.some(d => hostname.includes(d)); if (!isAllowed) { return { id: crypto.randomUUID(), event_id: event.id, detector: 'permission_bypass', rule_triggered: rule.id, severity: 'high', message: `Agent accessed disallowed domain: ${hostname}`, timestamp: new Date().toISOString() }; } } catch { /* invalid URL, skip */ } } return null; } function detectOutOfScope(event, rules) { const rule = rules.find(r => r.type === 'allowed_operations' && r.enabled); if (!rule) return null; const allowedOps = rule.params?.operations || ['read', 'chat', 'search']; const opType = event.type || ''; const isAllowed = allowedOps.some(op => opType.includes(op)); if (!isAllowed && (event.type === 'api_call' || event.type === 'tool_use')) { return { id: crypto.randomUUID(), event_id: event.id, detector: 'out_of_scope', rule_triggered: rule.id, severity: 'medium', message: `Agent performed out-of-scope operation: ${opType}`, timestamp: new Date().toISOString() }; } return null; } function detectSelfPermissioning(event, rules) { const rule = rules.find(r => r.type === 'permission_change' && r.enabled); if (!rule) return null; const context = (event.context || '') + (event.target || '') + (event.url || ''); const permissionKeywords = ['permission', 'grant', 'access control', 'role', 'privilege', 'acl', 'rbac']; if (permissionKeywords.some(kw => context.toLowerCase().includes(kw))) { return { id: crypto.randomUUID(), event_id: event.id, detector: 'self_permissioning', rule_triggered: rule.id, severity: 'critical', message: `Agent attempted to modify permissions or access controls`, timestamp: new Date().toISOString() }; } return null; } function detectDisallowedTools(event, rules) { const rule = rules.find(r => r.type === 'disallowed_tools' && r.enabled); if (!rule) return null; const disallowed = rule.params?.tools || ['eval', 'exec', 'shell', 'system', 'rm']; const toolName = event.tool || ''; if (disallowed.some(dt => toolName.toLowerCase().includes(dt.toLowerCase()))) { return { id: crypto.randomUUID(), event_id: event.id, detector: 'disallowed_tools', rule_triggered: rule.id, severity: 'critical', message: `Agent used disallowed tool: ${toolName}`, timestamp: new Date().toISOString() }; } return null; } function detectRedundantOperations(event, rules) { const rule = rules.find(r => r.type === 'redundant_rate' && r.enabled); if (!rule) return null; const maxRate = rule.params?.max_per_minute || 10; // We would need history for real detection; for now, flag if the event itself is flagged return null; } function detectOffTask(event, rules) { const rule = rules.find(r => r.type === 'task_scope' && r.enabled); if (!rule) return null; const taskKeywords = rule.params?.task_keywords || []; if (taskKeywords.length === 0) return null; const context = (event.context || '') + (event.target || ''); const onTask = taskKeywords.some(kw => context.toLowerCase().includes(kw.toLowerCase())); if (!onTask && event.type === 'tool_use') { return { id: crypto.randomUUID(), event_id: event.id, detector: 'off_task', rule_triggered: rule.id, severity: 'low', message: `Agent action may be off-task: ${event.tool || event.type}`, timestamp: new Date().toISOString() }; } return null; }