Files
2026-06-14 13:18:56 +00:00

165 lines
4.6 KiB
JavaScript

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