Files
agent-police-department-cla…/lib/detectors.js
T
2026-06-14 13:18:56 +00:00

147 lines
4.9 KiB
JavaScript

// 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;
}