165 lines
4.6 KiB
JavaScript
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 };
|
|
}
|