Agent Police Department v1.0.0 MVP
This commit is contained in:
+164
@@ -0,0 +1,164 @@
|
||||
// lib/policy.js — Policy loader and rule engine
|
||||
|
||||
const DEFAULT_POLICY = {
|
||||
version: 1,
|
||||
rules: [
|
||||
{
|
||||
id: 'rule_allowed_domains',
|
||||
type: 'allowed_domains',
|
||||
enabled: true,
|
||||
params: {
|
||||
domains: ['github.com', 'claude.ai', 'api.openai.com', 'chatgpt.com', 'cursor.sh']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rule_allowed_operations',
|
||||
type: 'allowed_operations',
|
||||
enabled: true,
|
||||
params: {
|
||||
operations: ['read', 'chat', 'search', 'code_generation', 'page_visit']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rule_disallowed_tools',
|
||||
type: 'disallowed_tools',
|
||||
enabled: true,
|
||||
params: {
|
||||
tools: ['eval', 'exec', 'shell', 'system', 'rm', 'sudo', 'chmod']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rule_permission_change',
|
||||
type: 'permission_change',
|
||||
enabled: true,
|
||||
params: {}
|
||||
},
|
||||
{
|
||||
id: 'rule_redundant_rate',
|
||||
type: 'redundant_rate',
|
||||
enabled: true,
|
||||
params: {
|
||||
max_per_minute: 10
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rule_task_scope',
|
||||
type: 'task_scope',
|
||||
enabled: false,
|
||||
params: {
|
||||
task_keywords: []
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export async function loadPolicy() {
|
||||
try {
|
||||
const result = await chrome.storage.local.get(['policy']);
|
||||
if (result.policy) return result.policy;
|
||||
await chrome.storage.local.set({ policy: DEFAULT_POLICY });
|
||||
return DEFAULT_POLICY;
|
||||
} catch (err) {
|
||||
console.error('Policy load error:', err);
|
||||
return DEFAULT_POLICY;
|
||||
}
|
||||
}
|
||||
|
||||
export async function savePolicy(policy) {
|
||||
policy.version = (policy.version || 0) + 1;
|
||||
await chrome.storage.local.set({ policy });
|
||||
return policy;
|
||||
}
|
||||
|
||||
export function checkPolicy(event, policy) {
|
||||
const rules = policy?.rules || [];
|
||||
let outcome = 'allowed';
|
||||
let severity = 'info';
|
||||
let ruleId = null;
|
||||
|
||||
for (const rule of rules) {
|
||||
if (!rule.enabled) continue;
|
||||
const result = evaluateRule(event, rule);
|
||||
if (result.violation) {
|
||||
outcome = 'violation';
|
||||
severity = result.severity;
|
||||
ruleId = rule.id;
|
||||
break;
|
||||
}
|
||||
if (result.warning) {
|
||||
if (outcome !== 'violation') {
|
||||
outcome = 'warning';
|
||||
severity = result.severity;
|
||||
ruleId = rule.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { outcome, severity, ruleId };
|
||||
}
|
||||
|
||||
function evaluateRule(event, rule) {
|
||||
switch (rule.type) {
|
||||
case 'allowed_domains': return evaluateAllowedDomains(event, rule);
|
||||
case 'allowed_operations': return evaluateAllowedOperations(event, rule);
|
||||
case 'disallowed_tools': return evaluateDisallowedTools(event, rule);
|
||||
case 'permission_change': return evaluatePermissionChange(event, rule);
|
||||
case 'redundant_rate': return { violation: false };
|
||||
case 'task_scope': return evaluateTaskScope(event, rule);
|
||||
default: return { violation: false };
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateAllowedDomains(event, rule) {
|
||||
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 { violation: true, severity: 'high' };
|
||||
}
|
||||
} catch { /* invalid URL */ }
|
||||
}
|
||||
return { violation: false };
|
||||
}
|
||||
|
||||
function evaluateAllowedOperations(event, rule) {
|
||||
const allowed = rule.params?.operations || [];
|
||||
const opType = event.type || '';
|
||||
const isAllowed = allowed.some(op => opType.includes(op));
|
||||
if (!isAllowed && (event.type === 'api_call' || event.type === 'tool_use')) {
|
||||
return { violation: true, severity: 'medium' };
|
||||
}
|
||||
return { violation: false };
|
||||
}
|
||||
|
||||
function evaluateDisallowedTools(event, rule) {
|
||||
const disallowed = rule.params?.tools || [];
|
||||
const toolName = event.tool || '';
|
||||
if (disallowed.some(dt => toolName.toLowerCase().includes(dt.toLowerCase()))) {
|
||||
return { violation: true, severity: 'critical' };
|
||||
}
|
||||
return { violation: false };
|
||||
}
|
||||
|
||||
function evaluatePermissionChange(event, rule) {
|
||||
const context = (event.context || '') + (event.target || '') + (event.url || '');
|
||||
const keywords = ['permission', 'grant', 'access control', 'role', 'privilege', 'acl', 'rbac'];
|
||||
if (keywords.some(kw => context.toLowerCase().includes(kw))) {
|
||||
return { violation: true, severity: 'critical' };
|
||||
}
|
||||
return { violation: false };
|
||||
}
|
||||
|
||||
function evaluateTaskScope(event, rule) {
|
||||
const keywords = rule.params?.task_keywords || [];
|
||||
if (keywords.length === 0) return { violation: false };
|
||||
const context = (event.context || '') + (event.target || '');
|
||||
const onTask = keywords.some(kw => context.toLowerCase().includes(kw.toLowerCase()));
|
||||
if (!onTask && event.type === 'tool_use') {
|
||||
return { violation: true, severity: 'low' };
|
||||
}
|
||||
return { violation: false };
|
||||
}
|
||||
Reference in New Issue
Block a user