aiblueprint-cli 1.4.12 → 1.4.13

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.
Files changed (54) hide show
  1. package/claude-code-config/scripts/.claude/commands/fix-on-my-computer.md +87 -0
  2. package/claude-code-config/scripts/command-validator/CLAUDE.md +112 -0
  3. package/claude-code-config/scripts/command-validator/src/__tests__/validator.test.ts +62 -111
  4. package/claude-code-config/scripts/command-validator/src/cli.ts +5 -3
  5. package/claude-code-config/scripts/command-validator/src/lib/security-rules.ts +3 -4
  6. package/claude-code-config/scripts/command-validator/src/lib/types.ts +1 -0
  7. package/claude-code-config/scripts/command-validator/src/lib/validator.ts +47 -317
  8. package/claude-code-config/scripts/statusline/CLAUDE.md +29 -7
  9. package/claude-code-config/scripts/statusline/README.md +89 -1
  10. package/claude-code-config/scripts/statusline/defaults.json +75 -0
  11. package/claude-code-config/scripts/statusline/src/index.ts +101 -24
  12. package/claude-code-config/scripts/statusline/src/lib/config-types.ts +100 -0
  13. package/claude-code-config/scripts/statusline/src/lib/config.ts +21 -0
  14. package/claude-code-config/scripts/statusline/src/lib/context.ts +32 -11
  15. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +360 -22
  16. package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
  17. package/claude-code-config/scripts/statusline/src/lib/render-pure.ts +177 -0
  18. package/claude-code-config/scripts/statusline/src/lib/types.ts +11 -0
  19. package/claude-code-config/scripts/statusline/statusline.config.json +93 -0
  20. package/claude-code-config/skills/claude-memory/SKILL.md +689 -0
  21. package/claude-code-config/skills/claude-memory/references/comprehensive-example.md +175 -0
  22. package/claude-code-config/skills/claude-memory/references/project-patterns.md +334 -0
  23. package/claude-code-config/skills/claude-memory/references/prompting-techniques.md +411 -0
  24. package/claude-code-config/skills/claude-memory/references/section-templates.md +347 -0
  25. package/claude-code-config/skills/create-slash-commands/SKILL.md +1110 -0
  26. package/claude-code-config/skills/create-slash-commands/references/arguments.md +273 -0
  27. package/claude-code-config/skills/create-slash-commands/references/patterns.md +947 -0
  28. package/claude-code-config/skills/create-slash-commands/references/prompt-examples.md +656 -0
  29. package/claude-code-config/skills/create-slash-commands/references/tool-restrictions.md +389 -0
  30. package/claude-code-config/skills/create-subagents/SKILL.md +425 -0
  31. package/claude-code-config/skills/create-subagents/references/context-management.md +567 -0
  32. package/claude-code-config/skills/create-subagents/references/debugging-agents.md +714 -0
  33. package/claude-code-config/skills/create-subagents/references/error-handling-and-recovery.md +502 -0
  34. package/claude-code-config/skills/create-subagents/references/evaluation-and-testing.md +374 -0
  35. package/claude-code-config/skills/create-subagents/references/orchestration-patterns.md +591 -0
  36. package/claude-code-config/skills/create-subagents/references/subagents.md +599 -0
  37. package/claude-code-config/skills/create-subagents/references/writing-subagent-prompts.md +513 -0
  38. package/package.json +1 -1
  39. package/claude-code-config/commands/apex.md +0 -109
  40. package/claude-code-config/commands/tasks/run-task.md +0 -220
  41. package/claude-code-config/commands/utils/watch-ci.md +0 -47
  42. package/claude-code-config/scripts/command-validator/biome.json +0 -29
  43. package/claude-code-config/scripts/command-validator/bun.lockb +0 -0
  44. package/claude-code-config/scripts/command-validator/package.json +0 -27
  45. package/claude-code-config/scripts/command-validator/vitest.config.ts +0 -7
  46. package/claude-code-config/scripts/hook-post-file.ts +0 -162
  47. package/claude-code-config/scripts/statusline/biome.json +0 -34
  48. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  49. package/claude-code-config/scripts/statusline/fixtures/test-input.json +0 -25
  50. package/claude-code-config/scripts/statusline/package.json +0 -19
  51. package/claude-code-config/scripts/statusline/statusline.config.ts +0 -25
  52. package/claude-code-config/scripts/statusline/test.ts +0 -20
  53. package/claude-code-config/scripts/validate-command.js +0 -712
  54. package/claude-code-config/scripts/validate-command.readme.md +0 -283
@@ -1,712 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * Claude Code "Before Tools" Hook - Command Validation Script
5
- *
6
- * This script validates commands before execution to prevent harmful operations.
7
- * It receives command data via stdin and returns exit code 0 (allow) or 1 (block).
8
- *
9
- * Usage: Called automatically by Claude Code PreToolUse hook
10
- * Manual test: echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | bun validate-command.js
11
- */
12
-
13
- import { homedir } from "os";
14
- import { join } from "path";
15
-
16
- const HOME = homedir();
17
-
18
- // Comprehensive dangerous command patterns database
19
- const SECURITY_RULES = {
20
- // Critical system destruction commands
21
- CRITICAL_COMMANDS: [
22
- "del",
23
- "format",
24
- "mkfs",
25
- "shred",
26
- "dd",
27
- "fdisk",
28
- "parted",
29
- "gparted",
30
- "cfdisk",
31
- ],
32
-
33
- // Privilege escalation and system access
34
- PRIVILEGE_COMMANDS: [
35
- "sudo",
36
- "su",
37
- "passwd",
38
- "chpasswd",
39
- "usermod",
40
- "chmod",
41
- "chown",
42
- "chgrp",
43
- "setuid",
44
- "setgid",
45
- ],
46
-
47
- // Network and remote access tools
48
- NETWORK_COMMANDS: [
49
- "nc",
50
- "netcat",
51
- "nmap",
52
- "telnet",
53
- "ssh-keygen",
54
- "iptables",
55
- "ufw",
56
- "firewall-cmd",
57
- "ipfw",
58
- ],
59
-
60
- // System service and process manipulation
61
- SYSTEM_COMMANDS: [
62
- "systemctl",
63
- "service",
64
- "kill",
65
- "killall",
66
- "pkill",
67
- "mount",
68
- "umount",
69
- "swapon",
70
- "swapoff",
71
- ],
72
-
73
- // Dangerous regex patterns
74
- DANGEROUS_PATTERNS: [
75
- // File system destruction - block rm -rf with absolute paths (checked separately)
76
- /rm\s+.*-rf\s*\/\s*$/i, // rm -rf ending at root directory
77
- /rm\s+.*-rf\s*\/etc/i, // rm -rf in /etc
78
- /rm\s+.*-rf\s*\/usr/i, // rm -rf in /usr
79
- /rm\s+.*-rf\s*\/bin/i, // rm -rf in /bin
80
- /rm\s+.*-rf\s*\/sys/i, // rm -rf in /sys
81
- /rm\s+.*-rf\s*\/proc/i, // rm -rf in /proc
82
- /rm\s+.*-rf\s*\/boot/i, // rm -rf in /boot
83
- /rm\s+.*-rf\s*\/home\/[^\/]*\s*$/i, // rm -rf entire home directory
84
- /rm\s+.*-rf\s*\.\.+\//i, // rm -rf with parent directory traversal
85
- /rm\s+.*-rf\s*\*.*\*/i, // rm -rf with multiple wildcards
86
- /rm\s+.*-rf\s*\$\w+/i, // rm -rf with variables (could be dangerous)
87
- />\s*\/dev\/(sda|hda|nvme)/i,
88
- /dd\s+.*of=\/dev\//i,
89
- /shred\s+.*\/dev\//i,
90
- /mkfs\.\w+\s+\/dev\//i,
91
-
92
- // Fork bomb and resource exhaustion
93
- /:\(\)\{\s*:\|:&\s*\};:/,
94
- /while\s+true\s*;\s*do.*done/i,
95
- /for\s*\(\(\s*;\s*;\s*\)\)/i,
96
-
97
- // Command injection (but allow general chaining - we'll validate each command separately)
98
- // /;\s*(rm|dd|mkfs|format)/i, // Commented out - handled by individual command validation
99
- // /&&\s*(rm|dd|mkfs|format)/i, // Commented out - handled by individual command validation
100
- // /\|\|\s*(rm|dd|mkfs|format)/i, // Commented out - handled by individual command validation
101
-
102
- // Remote code execution
103
- /\|\s*(sh|bash|zsh|fish)$/i,
104
- /(wget|curl)\s+.*\|\s*(sh|bash)/i,
105
- /(wget|curl)\s+.*-O-.*\|\s*(sh|bash)/i,
106
-
107
- // Command substitution with dangerous commands
108
- /`.*rm.*`/i,
109
- /\$\(.*rm.*\)/i,
110
- /`.*dd.*`/i,
111
- /\$\(.*dd.*\)/i,
112
-
113
- // Sensitive file access
114
- /cat\s+\/etc\/(passwd|shadow|sudoers)/i,
115
- />\s*\/etc\/(passwd|shadow|sudoers)/i,
116
- /echo\s+.*>>\s*\/etc\/(passwd|shadow|sudoers)/i,
117
-
118
- // Network exfiltration
119
- /\|\s*nc\s+\S+\s+\d+/i,
120
- /curl\s+.*-d.*\$\(/i,
121
- /wget\s+.*--post-data.*\$\(/i,
122
-
123
- // Log manipulation
124
- />\s*\/var\/log\//i,
125
- /rm\s+\/var\/log\//i,
126
- /echo\s+.*>\s*~?\/?\.bash_history/i,
127
-
128
- // Backdoor creation
129
- /nc\s+.*-l.*-e/i,
130
- /nc\s+.*-e.*-l/i,
131
- /ncat\s+.*--exec/i,
132
- /ssh-keygen.*authorized_keys/i,
133
-
134
- // Crypto mining and malicious downloads
135
- /(wget|curl).*\.(sh|py|pl|exe|bin).*\|\s*(sh|bash|python)/i,
136
- /(xmrig|ccminer|cgminer|bfgminer)/i,
137
-
138
- // Hardware direct access
139
- /cat\s+\/dev\/(mem|kmem)/i,
140
- /echo\s+.*>\s*\/dev\/(mem|kmem)/i,
141
-
142
- // Kernel module manipulation
143
- /(insmod|rmmod|modprobe)\s+/i,
144
-
145
- // Cron job manipulation
146
- /crontab\s+-e/i,
147
- /echo\s+.*>>\s*\/var\/spool\/cron/i,
148
-
149
- // Environment variable exposure
150
- /env\s*\|\s*grep.*PASSWORD/i,
151
- /printenv.*PASSWORD/i,
152
- ],
153
-
154
- // Paths that should never be written to
155
- PROTECTED_PATHS: [
156
- "/etc/",
157
- "/usr/",
158
- "/bin/",
159
- "/sbin/",
160
- "/boot/",
161
- "/sys/",
162
- "/proc/",
163
- "/dev/",
164
- "/root/",
165
- ],
166
-
167
- // Safe paths where rm -rf is allowed
168
- SAFE_RM_PATHS: [
169
- `${HOME}/Developer/`,
170
- "/tmp/",
171
- "/var/tmp/",
172
- process.cwd() + "/", // Current working directory
173
- ],
174
- };
175
-
176
- // Allowlist of safe commands (when used appropriately)
177
- const SAFE_COMMANDS = [
178
- "ls",
179
- "dir",
180
- "pwd",
181
- "whoami",
182
- "date",
183
- "echo",
184
- "cat",
185
- "head",
186
- "tail",
187
- "grep",
188
- "find",
189
- "wc",
190
- "sort",
191
- "uniq",
192
- "cut",
193
- "awk",
194
- "sed",
195
- "git",
196
- "npm",
197
- "pnpm",
198
- "node",
199
- "bun",
200
- "python",
201
- "pip",
202
- "source",
203
- "cd",
204
- "cp",
205
- "mv",
206
- "mkdir",
207
- "touch",
208
- "ln",
209
- ];
210
-
211
- class CommandValidator {
212
- constructor() {
213
- this.logFile = join(HOME, ".claude", "security.log");
214
- }
215
-
216
- /**
217
- * Main validation function
218
- */
219
- validate(command, toolName = "Unknown") {
220
- const result = {
221
- isValid: true,
222
- severity: "LOW",
223
- violations: [],
224
- sanitizedCommand: command,
225
- };
226
-
227
- if (!command || typeof command !== "string") {
228
- result.isValid = false;
229
- result.violations.push("Invalid command format");
230
- return result;
231
- }
232
-
233
- // Normalize command for analysis
234
- const normalizedCmd = command.trim().toLowerCase();
235
- const cmdParts = normalizedCmd.split(/\s+/);
236
- const mainCommand = cmdParts[0];
237
-
238
- // Allow source and python commands unconditionally
239
- if (mainCommand === "source" || mainCommand === "python") {
240
- return result; // Always allow
241
- }
242
-
243
- // Check against critical commands
244
- if (SECURITY_RULES.CRITICAL_COMMANDS.includes(mainCommand)) {
245
- result.isValid = false;
246
- result.severity = "CRITICAL";
247
- result.violations.push(`Critical dangerous command: ${mainCommand}`);
248
- }
249
-
250
- // Check privilege escalation commands
251
- if (SECURITY_RULES.PRIVILEGE_COMMANDS.includes(mainCommand)) {
252
- result.isValid = false;
253
- result.severity = "HIGH";
254
- result.violations.push(`Privilege escalation command: ${mainCommand}`);
255
- }
256
-
257
- // Check network commands
258
- if (SECURITY_RULES.NETWORK_COMMANDS.includes(mainCommand)) {
259
- result.isValid = false;
260
- result.severity = "HIGH";
261
- result.violations.push(`Network/remote access command: ${mainCommand}`);
262
- }
263
-
264
- // Check system commands
265
- if (SECURITY_RULES.SYSTEM_COMMANDS.includes(mainCommand)) {
266
- result.isValid = false;
267
- result.severity = "HIGH";
268
- result.violations.push(`System manipulation command: ${mainCommand}`);
269
- }
270
-
271
- // Check for rm -rf commands first (special handling)
272
- if (/rm\s+.*-rf\s/.test(command)) {
273
- const isRmRfSafe = this.isRmRfCommandSafe(command);
274
- if (!isRmRfSafe) {
275
- result.isValid = false;
276
- result.severity = "CRITICAL";
277
- result.violations.push("rm -rf command targeting unsafe path");
278
- }
279
- }
280
-
281
- // Check dangerous patterns (skip rm -rf patterns as they're handled above)
282
- for (const pattern of SECURITY_RULES.DANGEROUS_PATTERNS) {
283
- if (pattern.test(command) && !/rm\s+.*-rf/.test(pattern.source)) {
284
- result.isValid = false;
285
- result.severity = "CRITICAL";
286
- result.violations.push(`Dangerous pattern detected: ${pattern.source}`);
287
- }
288
- }
289
-
290
- // Allow && chaining for safe commands like source and python
291
- if (command.includes("&&")) {
292
- const chainedCommands = this.splitCommandChain(command);
293
- let allSafe = true;
294
- for (const chainedCmd of chainedCommands) {
295
- const trimmedCmd = chainedCmd.trim();
296
- const cmdParts = trimmedCmd.split(/\s+/);
297
- const mainCommand = cmdParts[0];
298
-
299
- // Allow source and python commands in && chains
300
- if (
301
- mainCommand === "source" ||
302
- mainCommand === "python" ||
303
- SAFE_COMMANDS.includes(mainCommand)
304
- ) {
305
- continue;
306
- }
307
-
308
- const chainResult = this.validateSingleCommand(trimmedCmd, toolName);
309
- if (!chainResult.isValid) {
310
- result.isValid = false;
311
- result.severity = chainResult.severity;
312
- result.violations.push(
313
- `Chained command violation: ${trimmedCmd} - ${chainResult.violations.join(
314
- ", "
315
- )}`
316
- );
317
- allSafe = false;
318
- }
319
- }
320
- if (allSafe) {
321
- return result; // Allow safe && chains
322
- }
323
- }
324
-
325
- // Check other command chaining (; and ||)
326
- if (command.includes(";") || command.includes("||")) {
327
- const chainedCommands = this.splitCommandChain(command);
328
- for (const chainedCmd of chainedCommands) {
329
- const chainResult = this.validateSingleCommand(
330
- chainedCmd.trim(),
331
- toolName
332
- );
333
- if (!chainResult.isValid) {
334
- result.isValid = false;
335
- result.severity = chainResult.severity;
336
- result.violations.push(
337
- `Chained command violation: ${chainedCmd.trim()} - ${chainResult.violations.join(
338
- ", "
339
- )}`
340
- );
341
- }
342
- }
343
- return result;
344
- }
345
-
346
- // Check for protected path access (but allow common redirections like /dev/null)
347
- for (const path of SECURITY_RULES.PROTECTED_PATHS) {
348
- if (command.includes(path)) {
349
- // Allow common safe redirections
350
- if (
351
- path === "/dev/" &&
352
- (command.includes("/dev/null") ||
353
- command.includes("/dev/stderr") ||
354
- command.includes("/dev/stdout"))
355
- ) {
356
- continue;
357
- }
358
- result.isValid = false;
359
- result.severity = "HIGH";
360
- result.violations.push(`Access to protected path: ${path}`);
361
- }
362
- }
363
-
364
- // Additional safety checks
365
- if (command.length > 2000) {
366
- result.isValid = false;
367
- result.severity = "MEDIUM";
368
- result.violations.push("Command too long (potential buffer overflow)");
369
- }
370
-
371
- // Check for binary/encoded content
372
- if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]/.test(command)) {
373
- result.isValid = false;
374
- result.severity = "HIGH";
375
- result.violations.push("Binary or encoded content detected");
376
- }
377
-
378
- return result;
379
- }
380
-
381
- /**
382
- * Validate a single command (without chaining logic to avoid recursion)
383
- */
384
- validateSingleCommand(command, toolName = "Unknown") {
385
- const result = {
386
- isValid: true,
387
- severity: "LOW",
388
- violations: [],
389
- sanitizedCommand: command,
390
- };
391
-
392
- if (!command || typeof command !== "string") {
393
- result.isValid = false;
394
- result.violations.push("Invalid command format");
395
- return result;
396
- }
397
-
398
- // Normalize command for analysis
399
- const normalizedCmd = command.trim().toLowerCase();
400
- const cmdParts = normalizedCmd.split(/\s+/);
401
- const mainCommand = cmdParts[0];
402
-
403
- // Allow source and python commands unconditionally in single command validation too
404
- if (mainCommand === "source" || mainCommand === "python") {
405
- return result; // Always allow
406
- }
407
-
408
- // Check against critical commands
409
- if (SECURITY_RULES.CRITICAL_COMMANDS.includes(mainCommand)) {
410
- result.isValid = false;
411
- result.severity = "CRITICAL";
412
- result.violations.push(`Critical dangerous command: ${mainCommand}`);
413
- }
414
-
415
- // Check privilege escalation commands
416
- if (SECURITY_RULES.PRIVILEGE_COMMANDS.includes(mainCommand)) {
417
- result.isValid = false;
418
- result.severity = "HIGH";
419
- result.violations.push(`Privilege escalation command: ${mainCommand}`);
420
- }
421
-
422
- // Check network commands
423
- if (SECURITY_RULES.NETWORK_COMMANDS.includes(mainCommand)) {
424
- result.isValid = false;
425
- result.severity = "HIGH";
426
- result.violations.push(`Network/remote access command: ${mainCommand}`);
427
- }
428
-
429
- // Check system commands
430
- if (SECURITY_RULES.SYSTEM_COMMANDS.includes(mainCommand)) {
431
- result.isValid = false;
432
- result.severity = "HIGH";
433
- result.violations.push(`System manipulation command: ${mainCommand}`);
434
- }
435
-
436
- // Check for rm -rf commands first (special handling)
437
- if (/rm\s+.*-rf\s/.test(command)) {
438
- const isRmRfSafe = this.isRmRfCommandSafe(command);
439
- if (!isRmRfSafe) {
440
- result.isValid = false;
441
- result.severity = "CRITICAL";
442
- result.violations.push("rm -rf command targeting unsafe path");
443
- }
444
- }
445
-
446
- // Check dangerous patterns (skip rm -rf patterns as they're handled above)
447
- for (const pattern of SECURITY_RULES.DANGEROUS_PATTERNS) {
448
- if (pattern.test(command) && !/rm\s+.*-rf/.test(pattern.source)) {
449
- result.isValid = false;
450
- result.severity = "CRITICAL";
451
- result.violations.push(`Dangerous pattern detected: ${pattern.source}`);
452
- }
453
- }
454
-
455
- // Check for protected path access
456
- for (const path of SECURITY_RULES.PROTECTED_PATHS) {
457
- if (command.includes(path)) {
458
- if (
459
- path === "/dev/" &&
460
- (command.includes("/dev/null") ||
461
- command.includes("/dev/stderr") ||
462
- command.includes("/dev/stdout"))
463
- ) {
464
- continue;
465
- }
466
- result.isValid = false;
467
- result.severity = "HIGH";
468
- result.violations.push(`Access to protected path: ${path}`);
469
- }
470
- }
471
-
472
- // Additional safety checks
473
- if (command.length > 2000) {
474
- result.isValid = false;
475
- result.severity = "MEDIUM";
476
- result.violations.push("Command too long (potential buffer overflow)");
477
- }
478
-
479
- // Check for binary/encoded content
480
- if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]/.test(command)) {
481
- result.isValid = false;
482
- result.severity = "HIGH";
483
- result.violations.push("Binary or encoded content detected");
484
- }
485
-
486
- return result;
487
- }
488
-
489
- /**
490
- * Split command chain into individual commands
491
- */
492
- splitCommandChain(command) {
493
- // Simple splitting on && ; ||
494
- // This is basic - doesn't handle complex quoting, but good enough for basic validation
495
- const commands = [];
496
- let current = "";
497
- let inQuotes = false;
498
- let quoteChar = "";
499
-
500
- for (let i = 0; i < command.length; i++) {
501
- const char = command[i];
502
- const nextChar = command[i + 1];
503
-
504
- // Handle quotes
505
- if ((char === '"' || char === "'") && !inQuotes) {
506
- inQuotes = true;
507
- quoteChar = char;
508
- current += char;
509
- } else if (char === quoteChar && inQuotes) {
510
- inQuotes = false;
511
- quoteChar = "";
512
- current += char;
513
- } else if (inQuotes) {
514
- current += char;
515
- } else if (char === "&" && nextChar === "&") {
516
- commands.push(current.trim());
517
- current = "";
518
- i++; // skip next &
519
- } else if (char === "|" && nextChar === "|") {
520
- commands.push(current.trim());
521
- current = "";
522
- i++; // skip next |
523
- } else if (char === ";") {
524
- commands.push(current.trim());
525
- current = "";
526
- } else {
527
- current += char;
528
- }
529
- }
530
-
531
- if (current.trim()) {
532
- commands.push(current.trim());
533
- }
534
-
535
- return commands.filter((cmd) => cmd.length > 0);
536
- }
537
-
538
- /**
539
- * Log security events
540
- */
541
- async logSecurityEvent(command, toolName, result, sessionId = null) {
542
- const timestamp = new Date().toISOString();
543
- const logEntry = {
544
- timestamp,
545
- sessionId,
546
- toolName,
547
- command: command.substring(0, 500), // Truncate for logs
548
- blocked: !result.isValid,
549
- severity: result.severity,
550
- violations: result.violations,
551
- source: "claude-code-hook",
552
- };
553
-
554
- try {
555
- // Write to log file
556
- const logLine = JSON.stringify(logEntry) + "\n";
557
- await Bun.write(this.logFile, logLine, { createPath: true, flag: "a" });
558
-
559
- // Also output to stderr for immediate visibility
560
- console.error(
561
- `[SECURITY] ${
562
- result.isValid ? "ALLOWED" : "BLOCKED"
563
- }: ${command.substring(0, 100)}`
564
- );
565
- } catch (error) {
566
- console.error("Failed to write security log:", error);
567
- }
568
- }
569
-
570
- /**
571
- * Check if rm -rf command targets a safe path
572
- */
573
- isRmRfCommandSafe(command) {
574
- // Extract the path from rm -rf command
575
- const rmRfMatch = command.match(/rm\s+.*-rf\s+([^\s;&|]+)/);
576
- if (!rmRfMatch) {
577
- return false; // Couldn't parse path, block for safety
578
- }
579
-
580
- const targetPath = rmRfMatch[1];
581
-
582
- // Block if targeting root or ending at root
583
- if (targetPath === "/" || targetPath.endsWith("/")) {
584
- return false;
585
- }
586
-
587
- // Check if path starts with any safe prefix
588
- for (const safePath of SECURITY_RULES.SAFE_RM_PATHS) {
589
- if (targetPath.startsWith(safePath)) {
590
- return true;
591
- }
592
- }
593
-
594
- // Check if it's a relative path (safer)
595
- if (!targetPath.startsWith("/")) {
596
- return true;
597
- }
598
-
599
- // Block all other absolute paths
600
- return false;
601
- }
602
-
603
- /**
604
- * Check if command matches any allowed patterns from settings
605
- */
606
- isExplicitlyAllowed(command, allowedPatterns = []) {
607
- for (const pattern of allowedPatterns) {
608
- // Convert Claude Code permission pattern to regex
609
- // e.g., "Bash(git *)" becomes /^git\s+.*$/
610
- if (pattern.startsWith("Bash(") && pattern.endsWith(")")) {
611
- const cmdPattern = pattern.slice(5, -1); // Remove "Bash(" and ")"
612
- const regex = new RegExp(
613
- "^" + cmdPattern.replace(/\*/g, ".*") + "$",
614
- "i"
615
- );
616
- if (regex.test(command)) {
617
- return true;
618
- }
619
- }
620
- }
621
- return false;
622
- }
623
- }
624
-
625
- /**
626
- * Main execution function
627
- */
628
- async function main() {
629
- const validator = new CommandValidator();
630
-
631
- try {
632
- // Read hook input from stdin
633
- const stdin = process.stdin;
634
- const chunks = [];
635
-
636
- for await (const chunk of stdin) {
637
- chunks.push(chunk);
638
- }
639
-
640
- const input = Buffer.concat(chunks).toString();
641
-
642
- if (!input.trim()) {
643
- console.error("No input received from stdin");
644
- process.exit(1);
645
- }
646
-
647
- // Parse Claude Code hook JSON format
648
- let hookData;
649
- try {
650
- hookData = JSON.parse(input);
651
- } catch (error) {
652
- console.error("Invalid JSON input:", error.message);
653
- process.exit(1);
654
- }
655
-
656
- const toolName = hookData.tool_name || "Unknown";
657
- const toolInput = hookData.tool_input || {};
658
- const sessionId = hookData.session_id || null;
659
-
660
- // Only validate Bash commands for now
661
- if (toolName !== "Bash") {
662
- console.log(`Skipping validation for tool: ${toolName}`);
663
- process.exit(0);
664
- }
665
-
666
- const command = toolInput.command;
667
- if (!command) {
668
- console.error("No command found in tool input");
669
- process.exit(1);
670
- }
671
-
672
- // Validate the command
673
- const result = validator.validate(command, toolName);
674
-
675
- // Log the security event
676
- await validator.logSecurityEvent(command, toolName, result, sessionId);
677
-
678
- // Output result and exit with appropriate code
679
- if (result.isValid) {
680
- console.log("Command validation passed");
681
- process.exit(0); // Allow execution
682
- } else {
683
- // Instead of blocking, ask user for confirmation
684
- const confirmationMessage = `⚠️ Potentially dangerous command detected!\n\nCommand: ${command}\nViolations: ${result.violations.join(
685
- ", "
686
- )}\nSeverity: ${
687
- result.severity
688
- }\n\nDo you want to proceed with this command?`;
689
-
690
- const hookOutput = {
691
- hookSpecificOutput: {
692
- hookEventName: "PreToolUse",
693
- permissionDecision: "ask",
694
- permissionDecisionReason: confirmationMessage,
695
- },
696
- };
697
-
698
- console.log(JSON.stringify(hookOutput));
699
- process.exit(0); // Exit with 0 to trigger user prompt
700
- }
701
- } catch (error) {
702
- console.error("Validation script error:", error);
703
- // Fail safe - block execution on any script error
704
- process.exit(2);
705
- }
706
- }
707
-
708
- // Execute main function
709
- main().catch((error) => {
710
- console.error("Fatal error:", error);
711
- process.exit(2);
712
- });