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