mcp-lint 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +159 -1
- package/dist/cli/commands/preflight.d.ts +2 -0
- package/dist/cli/commands/preflight.js +109 -0
- package/dist/cli/commands/preflight.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/loaders/index.d.ts +3 -0
- package/dist/loaders/index.js +4 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.js +242 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/preflight/engine.d.ts +2 -0
- package/dist/preflight/engine.js +73 -0
- package/dist/preflight/engine.js.map +1 -0
- package/dist/preflight/index.d.ts +4 -0
- package/dist/preflight/index.js +4 -0
- package/dist/preflight/index.js.map +1 -0
- package/dist/preflight/policy.d.ts +4 -0
- package/dist/preflight/policy.js +97 -0
- package/dist/preflight/policy.js.map +1 -0
- package/dist/preflight/scorer.d.ts +7 -0
- package/dist/preflight/scorer.js +121 -0
- package/dist/preflight/scorer.js.map +1 -0
- package/dist/preflight/types.d.ts +57 -0
- package/dist/preflight/types.js +2 -0
- package/dist/preflight/types.js.map +1 -0
- package/examples/actions/destructive-delete.json +11 -0
- package/examples/actions/rewrite-tmp.json +8 -0
- package/examples/actions/safe-read.json +8 -0
- package/examples/github-action.yml +23 -0
- package/examples/policies/default.yml +59 -0
- package/examples/policies/permissive-dev.yml +33 -0
- package/examples/policies/strict.yml +42 -0
- package/package.json +77 -44
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { matchRule } from './policy.js';
|
|
2
|
+
import { scoreAction } from './scorer.js';
|
|
3
|
+
const DEFAULT_APPROVAL_THRESHOLD = 0.7;
|
|
4
|
+
const DEFAULT_BLOCK_THRESHOLD = 0.92;
|
|
5
|
+
export function preflight(action, policy) {
|
|
6
|
+
validateAction(action);
|
|
7
|
+
const { score, reasons, reversibility } = scoreAction(action);
|
|
8
|
+
const approvalThreshold = policy?.defaults?.approval_threshold ?? DEFAULT_APPROVAL_THRESHOLD;
|
|
9
|
+
const blockThreshold = policy?.defaults?.block_threshold ?? DEFAULT_BLOCK_THRESHOLD;
|
|
10
|
+
const matched = [];
|
|
11
|
+
if (policy) {
|
|
12
|
+
for (const rule of policy.rules) {
|
|
13
|
+
if (matchRule(rule, action, score)) {
|
|
14
|
+
matched.push(rule);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
let decision = null;
|
|
19
|
+
let safeAlternative = undefined;
|
|
20
|
+
const policyReasons = [];
|
|
21
|
+
for (const rule of matched) {
|
|
22
|
+
if (rule.reason)
|
|
23
|
+
policyReasons.push(rule.reason);
|
|
24
|
+
if (rule.effect === 'deny') {
|
|
25
|
+
decision = 'deny';
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (rule.effect === 'require_approval') {
|
|
29
|
+
decision = 'require_approval';
|
|
30
|
+
}
|
|
31
|
+
else if (rule.effect === 'allow_with_rewrite' && decision === null) {
|
|
32
|
+
decision = 'allow_with_rewrite';
|
|
33
|
+
safeAlternative = rule.rewrite;
|
|
34
|
+
}
|
|
35
|
+
else if (rule.effect === 'allow' && decision === null) {
|
|
36
|
+
decision = 'allow';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (decision === null) {
|
|
40
|
+
if (score >= blockThreshold) {
|
|
41
|
+
decision = 'deny';
|
|
42
|
+
policyReasons.push('risk_above_block_threshold');
|
|
43
|
+
}
|
|
44
|
+
else if (score >= approvalThreshold) {
|
|
45
|
+
decision = 'require_approval';
|
|
46
|
+
policyReasons.push('risk_above_approval_threshold');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
decision = 'allow';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
decision,
|
|
54
|
+
risk_score: Number(score.toFixed(2)),
|
|
55
|
+
reversibility,
|
|
56
|
+
estimated_cost_usd: action.estimated_cost_usd ?? 0,
|
|
57
|
+
matched_policies: matched.map((r) => r.id),
|
|
58
|
+
reasons: [...reasons, ...policyReasons],
|
|
59
|
+
safe_alternative: safeAlternative,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function validateAction(action) {
|
|
63
|
+
if (!action || typeof action !== 'object') {
|
|
64
|
+
throw new Error('Action must be an object');
|
|
65
|
+
}
|
|
66
|
+
if (!action.tool_type)
|
|
67
|
+
throw new Error('Action missing tool_type');
|
|
68
|
+
if (!action.tool_name)
|
|
69
|
+
throw new Error('Action missing tool_name');
|
|
70
|
+
if (typeof action.action !== 'string')
|
|
71
|
+
throw new Error('Action missing action string');
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/preflight/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { preflight } from './engine.js';
|
|
2
|
+
export { loadPolicy, parsePolicy, matchRule } from './policy.js';
|
|
3
|
+
export { scoreAction } from './scorer.js';
|
|
4
|
+
export type { Action, Decision, DecisionType, Effect, Policy, PolicyRule, Rewrite, Reversibility, ToolType, } from './types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preflight/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Action, Policy, PolicyRule } from './types.js';
|
|
2
|
+
export declare function loadPolicy(path: string): Promise<Policy>;
|
|
3
|
+
export declare function parsePolicy(yaml: string): Policy;
|
|
4
|
+
export declare function matchRule(rule: PolicyRule, action: Action, riskScore: number): boolean;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import YAML from 'yaml';
|
|
3
|
+
export async function loadPolicy(path) {
|
|
4
|
+
const raw = await readFile(path, 'utf8');
|
|
5
|
+
const parsed = YAML.parse(raw);
|
|
6
|
+
return normalizePolicy(parsed);
|
|
7
|
+
}
|
|
8
|
+
export function parsePolicy(yaml) {
|
|
9
|
+
const parsed = YAML.parse(yaml);
|
|
10
|
+
return normalizePolicy(parsed);
|
|
11
|
+
}
|
|
12
|
+
function normalizePolicy(parsed) {
|
|
13
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
14
|
+
throw new Error('Policy file is empty or not an object');
|
|
15
|
+
}
|
|
16
|
+
const rules = Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
17
|
+
for (const [idx, rule] of rules.entries()) {
|
|
18
|
+
if (!rule.id)
|
|
19
|
+
throw new Error(`Policy rule #${idx} missing "id"`);
|
|
20
|
+
if (!rule.when)
|
|
21
|
+
throw new Error(`Policy rule "${rule.id}" missing "when"`);
|
|
22
|
+
if (!rule.effect)
|
|
23
|
+
throw new Error(`Policy rule "${rule.id}" missing "effect"`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
version: parsed.version ?? 1,
|
|
27
|
+
defaults: parsed.defaults ?? {},
|
|
28
|
+
budgets: parsed.budgets ?? {},
|
|
29
|
+
rules,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function matchRule(rule, action, riskScore) {
|
|
33
|
+
const w = rule.when;
|
|
34
|
+
if (w.tool_type) {
|
|
35
|
+
const types = Array.isArray(w.tool_type) ? w.tool_type : [w.tool_type];
|
|
36
|
+
if (!types.includes(action.tool_type))
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (w.tool_name) {
|
|
40
|
+
const names = Array.isArray(w.tool_name) ? w.tool_name : [w.tool_name];
|
|
41
|
+
if (!names.includes(action.tool_name))
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (w.action_matches && w.action_matches.length > 0) {
|
|
45
|
+
const hit = w.action_matches.some((m) => action.action.includes(m));
|
|
46
|
+
if (!hit)
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (w.target_matches && w.target_matches.length > 0) {
|
|
50
|
+
const target = action.target ?? '';
|
|
51
|
+
const hit = w.target_matches.some((m) => matchesGlob(target, m));
|
|
52
|
+
if (!hit)
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (w.domain_not_in_allowlist) {
|
|
56
|
+
const domain = extractDomain(action);
|
|
57
|
+
const allow = w.allowlist ?? [];
|
|
58
|
+
if (!domain)
|
|
59
|
+
return false;
|
|
60
|
+
if (allow.includes(domain))
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (typeof w.min_risk === 'number') {
|
|
64
|
+
if (riskScore < w.min_risk)
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (typeof w.max_cost_usd === 'number') {
|
|
68
|
+
const cost = action.estimated_cost_usd ?? 0;
|
|
69
|
+
if (cost <= w.max_cost_usd)
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
function matchesGlob(value, pattern) {
|
|
75
|
+
if (pattern.includes('**') || pattern.includes('*')) {
|
|
76
|
+
const regex = new RegExp('^' +
|
|
77
|
+
pattern
|
|
78
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
79
|
+
.replace(/\*\*/g, '§§')
|
|
80
|
+
.replace(/\*/g, '[^/]*')
|
|
81
|
+
.replace(/§§/g, '.*') +
|
|
82
|
+
'$');
|
|
83
|
+
return regex.test(value);
|
|
84
|
+
}
|
|
85
|
+
return value.includes(pattern);
|
|
86
|
+
}
|
|
87
|
+
function extractDomain(action) {
|
|
88
|
+
const candidate = action.target ?? action.action;
|
|
89
|
+
const match = candidate.match(/https?:\/\/([^/\s]+)/i);
|
|
90
|
+
if (match)
|
|
91
|
+
return match[1].toLowerCase();
|
|
92
|
+
if (action.context && typeof action.context.domain === 'string') {
|
|
93
|
+
return action.context.domain.toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/preflight/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const BASE_SCORES = {
|
|
2
|
+
file_read: 0.1,
|
|
3
|
+
file_write: 0.35,
|
|
4
|
+
file_delete: 0.75,
|
|
5
|
+
shell: 0.5,
|
|
6
|
+
http: 0.35,
|
|
7
|
+
mcp_tool: 0.3,
|
|
8
|
+
email: 0.6,
|
|
9
|
+
payment: 0.95,
|
|
10
|
+
database: 0.7,
|
|
11
|
+
other: 0.4,
|
|
12
|
+
};
|
|
13
|
+
const DESTRUCTIVE_PATTERNS = [
|
|
14
|
+
/\brm\s+-rf\b/i,
|
|
15
|
+
/\brmdir\s+\/s\b/i,
|
|
16
|
+
/\bdel\s+\/s\b/i,
|
|
17
|
+
/\bdrop\s+table\b/i,
|
|
18
|
+
/\btruncate\s+table\b/i,
|
|
19
|
+
/\bformat\s+\/\w+/i,
|
|
20
|
+
/\bmkfs\b/i,
|
|
21
|
+
/\b:\(\)\{\s*:\|:&\s*\};:/,
|
|
22
|
+
/\bgit\s+push\s+.*--force\b/i,
|
|
23
|
+
/\bgit\s+reset\s+--hard\b/i,
|
|
24
|
+
];
|
|
25
|
+
const SENSITIVE_TARGETS = [
|
|
26
|
+
/\/etc\b/,
|
|
27
|
+
/\/prod\b/,
|
|
28
|
+
/\/production\b/,
|
|
29
|
+
/\/app\/data\b/,
|
|
30
|
+
/\.env(\.|$)/,
|
|
31
|
+
/\bsecrets?\b/i,
|
|
32
|
+
/\bcredentials?\b/i,
|
|
33
|
+
/\/root\b/,
|
|
34
|
+
/\bC:\\Windows\b/i,
|
|
35
|
+
];
|
|
36
|
+
const MASS_OPERATION_PATTERNS = [
|
|
37
|
+
/\*\*\/\*/,
|
|
38
|
+
/\brecursive\b/i,
|
|
39
|
+
/\s+-r\b/,
|
|
40
|
+
/\s+-R\b/,
|
|
41
|
+
/\bALL\b/,
|
|
42
|
+
];
|
|
43
|
+
export function scoreAction(action) {
|
|
44
|
+
const reasons = [];
|
|
45
|
+
let score = BASE_SCORES[action.tool_type] ?? 0.4;
|
|
46
|
+
reasons.push(`base_${action.tool_type}`);
|
|
47
|
+
const actionText = action.action ?? '';
|
|
48
|
+
const targetText = action.target ?? '';
|
|
49
|
+
const haystack = `${actionText} ${targetText}`;
|
|
50
|
+
let destructive = false;
|
|
51
|
+
for (const pattern of DESTRUCTIVE_PATTERNS) {
|
|
52
|
+
if (pattern.test(haystack)) {
|
|
53
|
+
destructive = true;
|
|
54
|
+
score += 0.25;
|
|
55
|
+
reasons.push('destructive_pattern');
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (action.tool_type === 'file_delete') {
|
|
60
|
+
destructive = true;
|
|
61
|
+
}
|
|
62
|
+
for (const pattern of SENSITIVE_TARGETS) {
|
|
63
|
+
if (pattern.test(haystack)) {
|
|
64
|
+
score += 0.2;
|
|
65
|
+
reasons.push('sensitive_target');
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const pattern of MASS_OPERATION_PATTERNS) {
|
|
70
|
+
if (pattern.test(haystack)) {
|
|
71
|
+
score += 0.1;
|
|
72
|
+
reasons.push('mass_operation');
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (action.context?.environment === 'prod' || action.context?.environment === 'production') {
|
|
77
|
+
score += 0.15;
|
|
78
|
+
reasons.push('prod_environment');
|
|
79
|
+
}
|
|
80
|
+
const cost = action.estimated_cost_usd ?? 0;
|
|
81
|
+
if (cost >= 10) {
|
|
82
|
+
score += 0.15;
|
|
83
|
+
reasons.push('high_cost');
|
|
84
|
+
}
|
|
85
|
+
else if (cost >= 1) {
|
|
86
|
+
score += 0.05;
|
|
87
|
+
reasons.push('nontrivial_cost');
|
|
88
|
+
}
|
|
89
|
+
score = Math.min(1, Math.max(0, score));
|
|
90
|
+
const reversibility = computeReversibility(action, destructive);
|
|
91
|
+
if (reversibility === 'irreversible') {
|
|
92
|
+
reasons.push('irreversible_operation');
|
|
93
|
+
}
|
|
94
|
+
return { score, reasons, reversibility };
|
|
95
|
+
}
|
|
96
|
+
function computeReversibility(action, destructive) {
|
|
97
|
+
if (destructive)
|
|
98
|
+
return 'irreversible';
|
|
99
|
+
switch (action.tool_type) {
|
|
100
|
+
case 'file_read':
|
|
101
|
+
return 'reversible';
|
|
102
|
+
case 'file_write':
|
|
103
|
+
return 'partially_reversible';
|
|
104
|
+
case 'file_delete':
|
|
105
|
+
return 'irreversible';
|
|
106
|
+
case 'payment':
|
|
107
|
+
case 'email':
|
|
108
|
+
return 'irreversible';
|
|
109
|
+
case 'http':
|
|
110
|
+
return 'partially_reversible';
|
|
111
|
+
case 'database':
|
|
112
|
+
return 'partially_reversible';
|
|
113
|
+
case 'shell':
|
|
114
|
+
return 'partially_reversible';
|
|
115
|
+
case 'mcp_tool':
|
|
116
|
+
return 'partially_reversible';
|
|
117
|
+
default:
|
|
118
|
+
return 'partially_reversible';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/preflight/scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type ToolType = 'shell' | 'file_read' | 'file_write' | 'file_delete' | 'http' | 'mcp_tool' | 'email' | 'payment' | 'database' | 'other';
|
|
2
|
+
export type Reversibility = 'reversible' | 'partially_reversible' | 'irreversible';
|
|
3
|
+
export type DecisionType = 'allow' | 'deny' | 'require_approval' | 'allow_with_rewrite';
|
|
4
|
+
export type Effect = 'allow' | 'deny' | 'require_approval' | 'allow_with_rewrite';
|
|
5
|
+
export interface Action {
|
|
6
|
+
actor_id?: string;
|
|
7
|
+
workspace_id?: string;
|
|
8
|
+
tool_type: ToolType;
|
|
9
|
+
tool_name: string;
|
|
10
|
+
action: string;
|
|
11
|
+
target?: string;
|
|
12
|
+
context?: Record<string, unknown>;
|
|
13
|
+
estimated_cost_usd?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface Rewrite {
|
|
16
|
+
tool_type: ToolType;
|
|
17
|
+
tool_name?: string;
|
|
18
|
+
action: string;
|
|
19
|
+
target?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PolicyRule {
|
|
22
|
+
id: string;
|
|
23
|
+
when: {
|
|
24
|
+
tool_type?: ToolType | ToolType[];
|
|
25
|
+
tool_name?: string | string[];
|
|
26
|
+
action_matches?: string[];
|
|
27
|
+
target_matches?: string[];
|
|
28
|
+
domain_not_in_allowlist?: boolean;
|
|
29
|
+
allowlist?: string[];
|
|
30
|
+
min_risk?: number;
|
|
31
|
+
max_cost_usd?: number;
|
|
32
|
+
};
|
|
33
|
+
effect: Effect;
|
|
34
|
+
rewrite?: Rewrite;
|
|
35
|
+
reason?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Policy {
|
|
38
|
+
version: number;
|
|
39
|
+
defaults?: {
|
|
40
|
+
approval_threshold?: number;
|
|
41
|
+
block_threshold?: number;
|
|
42
|
+
};
|
|
43
|
+
budgets?: {
|
|
44
|
+
max_http_cost_usd_per_action?: number;
|
|
45
|
+
max_model_cost_usd_per_action?: number;
|
|
46
|
+
};
|
|
47
|
+
rules: PolicyRule[];
|
|
48
|
+
}
|
|
49
|
+
export interface Decision {
|
|
50
|
+
decision: DecisionType;
|
|
51
|
+
risk_score: number;
|
|
52
|
+
reversibility: Reversibility;
|
|
53
|
+
estimated_cost_usd: number;
|
|
54
|
+
matched_policies: string[];
|
|
55
|
+
reasons: string[];
|
|
56
|
+
safe_alternative?: Rewrite;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/preflight/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: MCP Schema Lint (example)
|
|
2
|
+
|
|
3
|
+
# Example workflow showing how to use mcp-lint in CI.
|
|
4
|
+
# Copy this to your own repo and adjust the `input` path.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
pull_request:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Lint MCP tool schemas
|
|
18
|
+
uses: robert19001-cmyk/mcp-lint@v0.3.0
|
|
19
|
+
with:
|
|
20
|
+
input: tools.json
|
|
21
|
+
severity: warning
|
|
22
|
+
score: 'true'
|
|
23
|
+
fail_on: error
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
|
|
3
|
+
defaults:
|
|
4
|
+
approval_threshold: 0.70
|
|
5
|
+
block_threshold: 0.92
|
|
6
|
+
|
|
7
|
+
budgets:
|
|
8
|
+
max_http_cost_usd_per_action: 2
|
|
9
|
+
max_model_cost_usd_per_action: 1
|
|
10
|
+
|
|
11
|
+
rules:
|
|
12
|
+
- id: block-destructive-prod
|
|
13
|
+
when:
|
|
14
|
+
tool_type: shell
|
|
15
|
+
action_matches: ["rm -rf", "rmdir /s", "del /s"]
|
|
16
|
+
target_matches: ["/prod", "/etc", "/app/data", "C:\\Windows"]
|
|
17
|
+
effect: deny
|
|
18
|
+
reason: Destructive operations on production paths are forbidden
|
|
19
|
+
|
|
20
|
+
- id: approval-sensitive-write
|
|
21
|
+
when:
|
|
22
|
+
tool_type: file_write
|
|
23
|
+
target_matches: ["**/.env*", "**/secrets*", "**/credentials*"]
|
|
24
|
+
effect: require_approval
|
|
25
|
+
reason: Writing to sensitive files requires approval
|
|
26
|
+
|
|
27
|
+
- id: rewrite-tmp-delete
|
|
28
|
+
when:
|
|
29
|
+
tool_type: shell
|
|
30
|
+
action_matches: ["rm -rf ./tmp", "rm -rf ./cache", "rm -rf ./build"]
|
|
31
|
+
effect: allow_with_rewrite
|
|
32
|
+
rewrite:
|
|
33
|
+
tool_type: file_delete
|
|
34
|
+
action: move_to_trash
|
|
35
|
+
target: ./tmp
|
|
36
|
+
reason: Prefer moving to trash over hard delete
|
|
37
|
+
|
|
38
|
+
- id: allow-workspace-writes
|
|
39
|
+
when:
|
|
40
|
+
tool_type: file_write
|
|
41
|
+
target_matches: ["./workspace/**", "./src/**", "./tests/**"]
|
|
42
|
+
effect: allow
|
|
43
|
+
|
|
44
|
+
- id: allow-local-reads
|
|
45
|
+
when:
|
|
46
|
+
tool_type: file_read
|
|
47
|
+
effect: allow
|
|
48
|
+
|
|
49
|
+
- id: approval-payments
|
|
50
|
+
when:
|
|
51
|
+
tool_type: payment
|
|
52
|
+
effect: require_approval
|
|
53
|
+
reason: All payments require human approval
|
|
54
|
+
|
|
55
|
+
- id: approval-external-email
|
|
56
|
+
when:
|
|
57
|
+
tool_type: email
|
|
58
|
+
effect: require_approval
|
|
59
|
+
reason: Outgoing email requires approval
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
|
|
3
|
+
defaults:
|
|
4
|
+
approval_threshold: 0.85
|
|
5
|
+
block_threshold: 0.98
|
|
6
|
+
|
|
7
|
+
rules:
|
|
8
|
+
- id: block-prod-paths
|
|
9
|
+
when:
|
|
10
|
+
target_matches: ["/prod/**", "/production/**"]
|
|
11
|
+
effect: deny
|
|
12
|
+
reason: Never touch production paths from dev
|
|
13
|
+
|
|
14
|
+
- id: allow-all-reads
|
|
15
|
+
when:
|
|
16
|
+
tool_type: file_read
|
|
17
|
+
effect: allow
|
|
18
|
+
|
|
19
|
+
- id: allow-workspace
|
|
20
|
+
when:
|
|
21
|
+
tool_type: file_write
|
|
22
|
+
target_matches: ["./**"]
|
|
23
|
+
effect: allow
|
|
24
|
+
|
|
25
|
+
- id: rewrite-destructive-delete
|
|
26
|
+
when:
|
|
27
|
+
tool_type: shell
|
|
28
|
+
action_matches: ["rm -rf"]
|
|
29
|
+
effect: allow_with_rewrite
|
|
30
|
+
rewrite:
|
|
31
|
+
tool_type: file_delete
|
|
32
|
+
action: move_to_trash
|
|
33
|
+
reason: Use trash instead of hard delete even in dev
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
|
|
3
|
+
defaults:
|
|
4
|
+
approval_threshold: 0.40
|
|
5
|
+
block_threshold: 0.80
|
|
6
|
+
|
|
7
|
+
rules:
|
|
8
|
+
- id: approval-all-shell
|
|
9
|
+
when:
|
|
10
|
+
tool_type: shell
|
|
11
|
+
effect: require_approval
|
|
12
|
+
reason: All shell commands require approval under strict policy
|
|
13
|
+
|
|
14
|
+
- id: approval-all-http
|
|
15
|
+
when:
|
|
16
|
+
tool_type: http
|
|
17
|
+
domain_not_in_allowlist: true
|
|
18
|
+
allowlist:
|
|
19
|
+
- api.github.com
|
|
20
|
+
- api.anthropic.com
|
|
21
|
+
- api.openai.com
|
|
22
|
+
effect: require_approval
|
|
23
|
+
reason: HTTP calls outside the allowlist require approval
|
|
24
|
+
|
|
25
|
+
- id: block-destructive
|
|
26
|
+
when:
|
|
27
|
+
tool_type: shell
|
|
28
|
+
action_matches: ["rm -rf", "drop table", "truncate"]
|
|
29
|
+
effect: deny
|
|
30
|
+
reason: Destructive commands are never allowed
|
|
31
|
+
|
|
32
|
+
- id: block-secrets
|
|
33
|
+
when:
|
|
34
|
+
action_matches: [".env", "secrets", "credentials", "id_rsa"]
|
|
35
|
+
effect: deny
|
|
36
|
+
reason: Secrets must never be touched by an agent
|
|
37
|
+
|
|
38
|
+
- id: approval-payment
|
|
39
|
+
when:
|
|
40
|
+
tool_type: payment
|
|
41
|
+
effect: deny
|
|
42
|
+
reason: Agents cannot make payments in strict mode
|