@agent-wall/core 0.1.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/.turbo/turbo-build.log +17 -0
- package/.turbo/turbo-test.log +30 -0
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/index.d.ts +1297 -0
- package/dist/index.js +3067 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/src/audit-logger-security.test.ts +225 -0
- package/src/audit-logger.test.ts +93 -0
- package/src/audit-logger.ts +458 -0
- package/src/chain-detector.test.ts +100 -0
- package/src/chain-detector.ts +269 -0
- package/src/dashboard-server.test.ts +362 -0
- package/src/dashboard-server.ts +454 -0
- package/src/egress-control.test.ts +177 -0
- package/src/egress-control.ts +274 -0
- package/src/index.ts +137 -0
- package/src/injection-detector.test.ts +207 -0
- package/src/injection-detector.ts +397 -0
- package/src/kill-switch.test.ts +119 -0
- package/src/kill-switch.ts +198 -0
- package/src/policy-engine-security.test.ts +227 -0
- package/src/policy-engine.test.ts +453 -0
- package/src/policy-engine.ts +414 -0
- package/src/policy-loader.test.ts +202 -0
- package/src/policy-loader.ts +485 -0
- package/src/proxy.ts +786 -0
- package/src/read-buffer-security.test.ts +59 -0
- package/src/read-buffer.test.ts +135 -0
- package/src/read-buffer.ts +126 -0
- package/src/response-scanner.test.ts +464 -0
- package/src/response-scanner.ts +587 -0
- package/src/types.test.ts +152 -0
- package/src/types.ts +146 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +9 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Wall Policy Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads and validates policy configuration from YAML files.
|
|
5
|
+
* Supports loading from a specific path or auto-discovering
|
|
6
|
+
* agent-wall.yaml in the current directory or parent directories.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as yaml from "js-yaml";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import type { PolicyConfig, PolicyRule, RuleAction, SecurityConfig } from "./policy-engine.js";
|
|
14
|
+
|
|
15
|
+
// ── YAML Schema Validation ──────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const RateLimitSchema = z.object({
|
|
18
|
+
maxCalls: z.number().int().positive(),
|
|
19
|
+
windowSeconds: z.number().positive(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const RuleMatchSchema = z.object({
|
|
23
|
+
arguments: z.record(z.string()).optional(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const PolicyRuleSchema = z.object({
|
|
27
|
+
name: z.string().min(1),
|
|
28
|
+
tool: z.string().min(1),
|
|
29
|
+
match: RuleMatchSchema.optional(),
|
|
30
|
+
action: z.enum(["allow", "deny", "prompt"]),
|
|
31
|
+
message: z.string().optional(),
|
|
32
|
+
rateLimit: RateLimitSchema.optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const ResponsePatternSchema = z.object({
|
|
36
|
+
name: z.string().min(1),
|
|
37
|
+
pattern: z.string().min(1),
|
|
38
|
+
flags: z.string().optional(),
|
|
39
|
+
action: z.enum(["pass", "redact", "block"]),
|
|
40
|
+
message: z.string().optional(),
|
|
41
|
+
category: z.string().optional(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const ResponseScanningSchema = z.object({
|
|
45
|
+
enabled: z.boolean().optional(),
|
|
46
|
+
maxResponseSize: z.number().int().nonnegative().optional(),
|
|
47
|
+
oversizeAction: z.enum(["block", "redact"]).optional(),
|
|
48
|
+
detectSecrets: z.boolean().optional(),
|
|
49
|
+
detectPII: z.boolean().optional(),
|
|
50
|
+
base64Action: z.enum(["pass", "redact", "block"]).optional(),
|
|
51
|
+
maxPatterns: z.number().int().positive().optional(),
|
|
52
|
+
patterns: z.array(ResponsePatternSchema).optional(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ── Security Config Schemas ──────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const InjectionDetectionSchema = z.object({
|
|
58
|
+
enabled: z.boolean().optional(),
|
|
59
|
+
sensitivity: z.enum(["low", "medium", "high"]).optional(),
|
|
60
|
+
customPatterns: z.array(z.string()).optional(),
|
|
61
|
+
excludeTools: z.array(z.string()).optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const EgressControlSchema = z.object({
|
|
65
|
+
enabled: z.boolean().optional(),
|
|
66
|
+
allowedDomains: z.array(z.string()).optional(),
|
|
67
|
+
blockedDomains: z.array(z.string()).optional(),
|
|
68
|
+
blockPrivateIPs: z.boolean().optional(),
|
|
69
|
+
blockMetadataEndpoints: z.boolean().optional(),
|
|
70
|
+
excludeTools: z.array(z.string()).optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const KillSwitchSchema = z.object({
|
|
74
|
+
enabled: z.boolean().optional(),
|
|
75
|
+
checkFile: z.boolean().optional(),
|
|
76
|
+
killFileNames: z.array(z.string()).optional(),
|
|
77
|
+
pollIntervalMs: z.number().int().positive().optional(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const ChainDetectionSchema = z.object({
|
|
81
|
+
enabled: z.boolean().optional(),
|
|
82
|
+
windowSize: z.number().int().positive().optional(),
|
|
83
|
+
windowMs: z.number().int().positive().optional(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const SecuritySchema = z.object({
|
|
87
|
+
injectionDetection: InjectionDetectionSchema.optional(),
|
|
88
|
+
egressControl: EgressControlSchema.optional(),
|
|
89
|
+
killSwitch: KillSwitchSchema.optional(),
|
|
90
|
+
chainDetection: ChainDetectionSchema.optional(),
|
|
91
|
+
signing: z.boolean().optional(),
|
|
92
|
+
signingKey: z.string().optional(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const PolicyConfigSchema = z.object({
|
|
96
|
+
version: z.number().int().min(1),
|
|
97
|
+
mode: z.enum(["standard", "strict"]).optional(),
|
|
98
|
+
defaultAction: z.enum(["allow", "deny", "prompt"]).optional(),
|
|
99
|
+
globalRateLimit: RateLimitSchema.optional(),
|
|
100
|
+
responseScanning: ResponseScanningSchema.optional(),
|
|
101
|
+
security: SecuritySchema.optional(),
|
|
102
|
+
rules: z.array(PolicyRuleSchema),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ── Config File Names ───────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
const CONFIG_FILENAMES = [
|
|
108
|
+
"agent-wall.yaml",
|
|
109
|
+
"agent-wall.yml",
|
|
110
|
+
".agent-wall.yaml",
|
|
111
|
+
".agent-wall.yml",
|
|
112
|
+
// Legacy support
|
|
113
|
+
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// ── Loader Functions ────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Load a policy config from a specific file path.
|
|
120
|
+
*/
|
|
121
|
+
export function loadPolicyFile(filePath: string): PolicyConfig {
|
|
122
|
+
if (!fs.existsSync(filePath)) {
|
|
123
|
+
throw new Error(`Policy file not found: ${filePath}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
127
|
+
return parsePolicyYaml(content);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parse a YAML string into a validated PolicyConfig.
|
|
132
|
+
*/
|
|
133
|
+
export function parsePolicyYaml(yamlContent: string): PolicyConfig {
|
|
134
|
+
const raw = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
135
|
+
const validated = PolicyConfigSchema.parse(raw);
|
|
136
|
+
return validated as PolicyConfig;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Auto-discover the nearest agent-wall.yaml by walking up
|
|
141
|
+
* from the given directory (defaults to cwd).
|
|
142
|
+
* Returns the file path if found, null otherwise.
|
|
143
|
+
*/
|
|
144
|
+
export function discoverPolicyFile(
|
|
145
|
+
startDir: string = process.cwd()
|
|
146
|
+
): string | null {
|
|
147
|
+
let dir = path.resolve(startDir);
|
|
148
|
+
|
|
149
|
+
while (true) {
|
|
150
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
151
|
+
const candidate = path.join(dir, filename);
|
|
152
|
+
if (fs.existsSync(candidate)) {
|
|
153
|
+
return candidate;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const parent = path.dirname(dir);
|
|
158
|
+
if (parent === dir) break; // Reached filesystem root
|
|
159
|
+
dir = parent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Load the policy config by auto-discovering the config file.
|
|
167
|
+
* Falls back to default policy if no config file found.
|
|
168
|
+
*/
|
|
169
|
+
export function loadPolicy(configPath?: string): {
|
|
170
|
+
config: PolicyConfig;
|
|
171
|
+
filePath: string | null;
|
|
172
|
+
} {
|
|
173
|
+
if (configPath) {
|
|
174
|
+
return {
|
|
175
|
+
config: loadPolicyFile(configPath),
|
|
176
|
+
filePath: configPath,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const discovered = discoverPolicyFile();
|
|
181
|
+
if (discovered) {
|
|
182
|
+
return {
|
|
183
|
+
config: loadPolicyFile(discovered),
|
|
184
|
+
filePath: discovered,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// No config found — return sensible defaults
|
|
189
|
+
return {
|
|
190
|
+
config: getDefaultPolicy(),
|
|
191
|
+
filePath: null,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get the default policy config.
|
|
197
|
+
* Ships with Agent Wall — provides reasonable security out of the box.
|
|
198
|
+
*/
|
|
199
|
+
export function getDefaultPolicy(): PolicyConfig {
|
|
200
|
+
return {
|
|
201
|
+
version: 1,
|
|
202
|
+
defaultAction: "prompt",
|
|
203
|
+
globalRateLimit: {
|
|
204
|
+
maxCalls: 200,
|
|
205
|
+
windowSeconds: 60,
|
|
206
|
+
},
|
|
207
|
+
responseScanning: {
|
|
208
|
+
enabled: true,
|
|
209
|
+
maxResponseSize: 5 * 1024 * 1024, // 5MB
|
|
210
|
+
oversizeAction: "redact",
|
|
211
|
+
detectSecrets: true,
|
|
212
|
+
detectPII: false,
|
|
213
|
+
},
|
|
214
|
+
security: {
|
|
215
|
+
injectionDetection: { enabled: true, sensitivity: "medium" },
|
|
216
|
+
egressControl: { enabled: true, blockPrivateIPs: true, blockMetadataEndpoints: true },
|
|
217
|
+
killSwitch: { enabled: true, checkFile: true },
|
|
218
|
+
chainDetection: { enabled: true },
|
|
219
|
+
signing: false,
|
|
220
|
+
},
|
|
221
|
+
rules: [
|
|
222
|
+
// ── Always block: credential access ──
|
|
223
|
+
{
|
|
224
|
+
name: "block-ssh-keys",
|
|
225
|
+
tool: "*",
|
|
226
|
+
match: { arguments: { path: "**/.ssh/**|**/.ssh" } },
|
|
227
|
+
action: "deny" as RuleAction,
|
|
228
|
+
message: "Access to SSH keys is blocked by default policy",
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "block-env-files",
|
|
232
|
+
tool: "*",
|
|
233
|
+
match: { arguments: { path: "**/.env*" } },
|
|
234
|
+
action: "deny" as RuleAction,
|
|
235
|
+
message: "Access to .env files is blocked by default policy",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "block-credential-files",
|
|
239
|
+
tool: "*",
|
|
240
|
+
match: {
|
|
241
|
+
arguments: { path: "*credentials*|**/*.pem|**/*.key|**/*.pfx|**/*.p12" },
|
|
242
|
+
},
|
|
243
|
+
action: "deny" as RuleAction,
|
|
244
|
+
message: "Access to credential files is blocked by default policy",
|
|
245
|
+
},
|
|
246
|
+
// ── Always block: exfiltration patterns ──
|
|
247
|
+
{
|
|
248
|
+
name: "block-curl-exfil",
|
|
249
|
+
tool: "shell_exec|run_command|execute_command",
|
|
250
|
+
match: { arguments: { command: "*curl *" } },
|
|
251
|
+
action: "deny" as RuleAction,
|
|
252
|
+
message:
|
|
253
|
+
"Shell commands with curl are blocked — potential data exfiltration",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "block-wget-exfil",
|
|
257
|
+
tool: "shell_exec|run_command|execute_command",
|
|
258
|
+
match: { arguments: { command: "*wget *" } },
|
|
259
|
+
action: "deny" as RuleAction,
|
|
260
|
+
message:
|
|
261
|
+
"Shell commands with wget are blocked — potential data exfiltration",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "block-netcat-exfil",
|
|
265
|
+
tool: "shell_exec|run_command|execute_command",
|
|
266
|
+
match: { arguments: { command: "*nc *|*ncat *|*netcat *" } },
|
|
267
|
+
action: "deny" as RuleAction,
|
|
268
|
+
message:
|
|
269
|
+
"Shell commands with netcat are blocked — potential data exfiltration",
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "block-powershell-exfil",
|
|
273
|
+
tool: "shell_exec|run_command|execute_command|bash",
|
|
274
|
+
match: { arguments: { command: "*powershell*|*pwsh*|*Invoke-WebRequest*|*Invoke-RestMethod*|*DownloadString*|*DownloadFile*|*Start-BitsTransfer*" } },
|
|
275
|
+
action: "deny" as RuleAction,
|
|
276
|
+
message:
|
|
277
|
+
"PowerShell command blocked — potential data exfiltration",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "block-dns-exfil",
|
|
281
|
+
tool: "shell_exec|run_command|execute_command|bash",
|
|
282
|
+
match: { arguments: { command: "*nslookup *|*dig *|*host *" } },
|
|
283
|
+
action: "deny" as RuleAction,
|
|
284
|
+
message:
|
|
285
|
+
"DNS lookup command blocked — potential DNS exfiltration vector",
|
|
286
|
+
},
|
|
287
|
+
// ── Require approval: scripting language one-liners ──
|
|
288
|
+
{
|
|
289
|
+
name: "approve-script-exec",
|
|
290
|
+
tool: "shell_exec|run_command|execute_command|bash",
|
|
291
|
+
match: { arguments: { command: "*python* -c *|*python3* -c *|*ruby* -e *|*perl* -e *|*node* -e *|*node* --eval*" } },
|
|
292
|
+
action: "prompt" as RuleAction,
|
|
293
|
+
message:
|
|
294
|
+
"Inline script execution requires approval — may be used for exfiltration",
|
|
295
|
+
},
|
|
296
|
+
// ── Require approval: destructive operations ──
|
|
297
|
+
{
|
|
298
|
+
name: "approve-file-delete",
|
|
299
|
+
tool: "*delete*|*remove*|*unlink*",
|
|
300
|
+
action: "prompt" as RuleAction,
|
|
301
|
+
message: "File deletion requires approval",
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "approve-shell-exec",
|
|
305
|
+
tool: "shell_exec|run_command|execute_command|bash",
|
|
306
|
+
action: "prompt" as RuleAction,
|
|
307
|
+
message: "Shell command execution requires approval",
|
|
308
|
+
},
|
|
309
|
+
// ── Allow: safe read operations ──
|
|
310
|
+
{
|
|
311
|
+
name: "allow-read-file",
|
|
312
|
+
tool: "read_file|get_file_contents|view_file",
|
|
313
|
+
action: "allow" as RuleAction,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: "allow-list-dir",
|
|
317
|
+
tool: "list_directory|list_dir|ls",
|
|
318
|
+
action: "allow" as RuleAction,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: "allow-search",
|
|
322
|
+
tool: "search_files|grep|find_files|ripgrep",
|
|
323
|
+
action: "allow" as RuleAction,
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Generate the default agent-wall.yaml content for `agent-wall init`.
|
|
331
|
+
*/
|
|
332
|
+
export function generateDefaultConfigYaml(): string {
|
|
333
|
+
return `# Agent Wall Policy Configuration
|
|
334
|
+
# Docs: https://github.com/agent-wall/agent-wall
|
|
335
|
+
#
|
|
336
|
+
# Rules are evaluated in order — first match wins.
|
|
337
|
+
# Actions: allow, deny, prompt (ask human for approval)
|
|
338
|
+
|
|
339
|
+
version: 1
|
|
340
|
+
|
|
341
|
+
# Default action when no rule matches
|
|
342
|
+
defaultAction: prompt
|
|
343
|
+
|
|
344
|
+
# Global rate limit across all tools
|
|
345
|
+
globalRateLimit:
|
|
346
|
+
maxCalls: 200
|
|
347
|
+
windowSeconds: 60
|
|
348
|
+
|
|
349
|
+
# Response scanning — inspect what the MCP server returns
|
|
350
|
+
# before it reaches the AI agent
|
|
351
|
+
responseScanning:
|
|
352
|
+
enabled: true
|
|
353
|
+
maxResponseSize: 5242880 # 5MB
|
|
354
|
+
oversizeAction: redact # "block" or "redact" (truncate)
|
|
355
|
+
detectSecrets: true # API keys, tokens, private keys
|
|
356
|
+
detectPII: false # Email, phone, SSN, credit cards (opt-in)
|
|
357
|
+
# Custom patterns (optional):
|
|
358
|
+
# patterns:
|
|
359
|
+
# - name: internal-urls
|
|
360
|
+
# pattern: "https?://internal\\.[a-z]+\\.corp"
|
|
361
|
+
# action: redact
|
|
362
|
+
# message: "Internal URL detected"
|
|
363
|
+
# category: custom
|
|
364
|
+
|
|
365
|
+
# Security modules
|
|
366
|
+
security:
|
|
367
|
+
injectionDetection:
|
|
368
|
+
enabled: true
|
|
369
|
+
sensitivity: medium # low, medium, high
|
|
370
|
+
egressControl:
|
|
371
|
+
enabled: true
|
|
372
|
+
blockPrivateIPs: true # Block RFC1918, loopback, link-local IPs
|
|
373
|
+
blockMetadataEndpoints: true # Block cloud metadata (169.254.169.254)
|
|
374
|
+
killSwitch:
|
|
375
|
+
enabled: true
|
|
376
|
+
checkFile: true # Watch for .agent-wall-kill file
|
|
377
|
+
chainDetection:
|
|
378
|
+
enabled: true # Detect exfiltration chains (read→curl, etc.)
|
|
379
|
+
signing: false # HMAC-SHA256 audit log signing
|
|
380
|
+
|
|
381
|
+
rules:
|
|
382
|
+
# ── Block: Credential Access ────────────────────────────
|
|
383
|
+
- name: block-ssh-keys
|
|
384
|
+
tool: "*"
|
|
385
|
+
match:
|
|
386
|
+
arguments:
|
|
387
|
+
path: "**/.ssh/**|**/.ssh"
|
|
388
|
+
action: deny
|
|
389
|
+
message: "Access to SSH keys is blocked"
|
|
390
|
+
|
|
391
|
+
- name: block-env-files
|
|
392
|
+
tool: "*"
|
|
393
|
+
match:
|
|
394
|
+
arguments:
|
|
395
|
+
path: "**/.env*"
|
|
396
|
+
action: deny
|
|
397
|
+
message: "Access to .env files is blocked"
|
|
398
|
+
|
|
399
|
+
- name: block-credential-files
|
|
400
|
+
tool: "*"
|
|
401
|
+
match:
|
|
402
|
+
arguments:
|
|
403
|
+
path: "*credentials*|**/*.pem|**/*.key|**/*.pfx|**/*.p12"
|
|
404
|
+
action: deny
|
|
405
|
+
message: "Access to credential files is blocked"
|
|
406
|
+
|
|
407
|
+
# ── Block: Exfiltration Patterns ────────────────────────
|
|
408
|
+
- name: block-curl-exfil
|
|
409
|
+
tool: "shell_exec|run_command|execute_command"
|
|
410
|
+
match:
|
|
411
|
+
arguments:
|
|
412
|
+
command: "*curl *"
|
|
413
|
+
action: deny
|
|
414
|
+
message: "Shell commands with curl are blocked (potential exfiltration)"
|
|
415
|
+
|
|
416
|
+
- name: block-wget-exfil
|
|
417
|
+
tool: "shell_exec|run_command|execute_command"
|
|
418
|
+
match:
|
|
419
|
+
arguments:
|
|
420
|
+
command: "*wget *"
|
|
421
|
+
action: deny
|
|
422
|
+
message: "Shell commands with wget are blocked (potential exfiltration)"
|
|
423
|
+
|
|
424
|
+
- name: block-netcat-exfil
|
|
425
|
+
tool: "shell_exec|run_command|execute_command"
|
|
426
|
+
match:
|
|
427
|
+
arguments:
|
|
428
|
+
command: "*nc *|*ncat *|*netcat *"
|
|
429
|
+
action: deny
|
|
430
|
+
message: "Shell commands with netcat are blocked (potential exfiltration)"
|
|
431
|
+
|
|
432
|
+
- name: block-powershell-exfil
|
|
433
|
+
tool: "shell_exec|run_command|execute_command|bash"
|
|
434
|
+
match:
|
|
435
|
+
arguments:
|
|
436
|
+
command: "*powershell*|*pwsh*|*Invoke-WebRequest*|*Invoke-RestMethod*|*DownloadString*|*DownloadFile*|*Start-BitsTransfer*"
|
|
437
|
+
action: deny
|
|
438
|
+
message: "PowerShell command blocked (potential exfiltration)"
|
|
439
|
+
|
|
440
|
+
- name: block-dns-exfil
|
|
441
|
+
tool: "shell_exec|run_command|execute_command|bash"
|
|
442
|
+
match:
|
|
443
|
+
arguments:
|
|
444
|
+
command: "*nslookup *|*dig *|*host *"
|
|
445
|
+
action: deny
|
|
446
|
+
message: "DNS lookup blocked (potential DNS exfiltration)"
|
|
447
|
+
|
|
448
|
+
# ── Prompt: Scripting One-Liners ────────────────────
|
|
449
|
+
- name: approve-script-exec
|
|
450
|
+
tool: "shell_exec|run_command|execute_command|bash"
|
|
451
|
+
match:
|
|
452
|
+
arguments:
|
|
453
|
+
command: "*python* -c *|*python3* -c *|*ruby* -e *|*perl* -e *|*node* -e *|*node* --eval*"
|
|
454
|
+
action: prompt
|
|
455
|
+
message: "Inline script execution requires approval"
|
|
456
|
+
|
|
457
|
+
# ── Prompt: Destructive Operations ──────────────────────
|
|
458
|
+
- name: approve-file-delete
|
|
459
|
+
tool: "*delete*|*remove*|*unlink*"
|
|
460
|
+
action: prompt
|
|
461
|
+
message: "File deletion requires approval"
|
|
462
|
+
|
|
463
|
+
- name: approve-shell-exec
|
|
464
|
+
tool: "shell_exec|run_command|execute_command|bash"
|
|
465
|
+
action: prompt
|
|
466
|
+
message: "Shell command execution requires approval"
|
|
467
|
+
|
|
468
|
+
# ── Allow: Safe Read Operations ─────────────────────────
|
|
469
|
+
- name: allow-read-file
|
|
470
|
+
tool: "read_file|get_file_contents|view_file"
|
|
471
|
+
action: allow
|
|
472
|
+
|
|
473
|
+
- name: allow-list-dir
|
|
474
|
+
tool: "list_directory|list_dir|ls"
|
|
475
|
+
action: allow
|
|
476
|
+
|
|
477
|
+
- name: allow-search
|
|
478
|
+
tool: "search_files|grep|find_files|ripgrep"
|
|
479
|
+
action: allow
|
|
480
|
+
|
|
481
|
+
# ────────────────────────────────────────────────────────
|
|
482
|
+
# Add your own rules below. Remember: first match wins!
|
|
483
|
+
# ────────────────────────────────────────────────────────
|
|
484
|
+
`;
|
|
485
|
+
}
|