main
1/**
2 * Utility functions and rule definitions for Guardrails extension
3 *
4 * Extracted for testability.
5 */
6
7// ── Command rule types ────────────────────────────────────────
8
9export interface CommandRule {
10 pattern: RegExp;
11 desc: string;
12 action: "confirm" | "block";
13 suggestion?: string;
14}
15
16// ── Dangerous & guarded command patterns ──────────────────────
17
18export const commandRules: CommandRule[] = [
19 // General dangerous commands (confirm)
20 { pattern: /\brm\s+(-[^\s]*r|--recursive)/, desc: "recursive delete", action: "confirm" },
21 { pattern: /\bsudo\b/, desc: "sudo command", action: "confirm" },
22 { pattern: /\b(chmod|chown)\b.*777/, desc: "dangerous permissions", action: "confirm" },
23 { pattern: /\bmkfs\b/, desc: "filesystem format", action: "confirm" },
24 { pattern: /\bdd\b.*\bof=\/dev\//, desc: "raw device write", action: "confirm" },
25 { pattern: />\s*\/dev\/sd[a-z]/, desc: "raw device overwrite", action: "confirm" },
26 { pattern: /\bkill\s+-9\s+-1\b/, desc: "kill all processes", action: "confirm" },
27 { pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/, desc: "fork bomb", action: "confirm" },
28 { pattern: /\bkubectl\b/, desc: "kubectl command", action: "confirm" },
29
30 // Git: block broad staging (use explicit file paths)
31 { pattern: /\bgit\s+add\s+(-[Aau]|--all|--update)\b/, desc: "broad git add (use explicit file paths)", action: "block", suggestion: "Use 'git add <file>...' with explicit paths to avoid staging unrelated changes" },
32
33 // Nix commands (block: must use make targets)
34 { pattern: /\bnixos-rebuild\s+(switch|boot|test)/, desc: "direct nixos-rebuild", action: "block", suggestion: "Use 'make switch', 'make boot', or 'make host/<hostname>/switch' instead" },
35 { pattern: /\bhome-manager\s+switch\b/, desc: "direct home-manager switch", action: "block", suggestion: "Use 'make switch' or appropriate make target instead" },
36
37 // gh CLI (confirm: use the github tool instead)
38 { pattern: /\bgh\s+(pr|issue|run|release|repo)\b/, desc: "gh CLI (use the `github` tool instead for structured output and approval gates)", action: "confirm", suggestion: "Use the `github` tool with the appropriate resource/action instead of direct gh CLI" },
39
40 // Nix commands (confirm)
41 { pattern: /\bnix\s+eval\b/, desc: "nix eval (arbitrary code execution)", action: "confirm" },
42 { pattern: /\bnix-build\b/, desc: "nix-build (builds derivations)", action: "confirm" },
43 { pattern: /\bnix\s+store\s+(delete|gc|optimise)\b/, desc: "nix store modification", action: "confirm" },
44 { pattern: /\bnix-collect-garbage\b/, desc: "nix garbage collection", action: "confirm" },
45 { pattern: /\bnix-channel\s+(--add|--remove|--update)\b/, desc: "nix-channel modification", action: "confirm" },
46 { pattern: /\bnix\s+flake\s+update\b/, desc: "nix flake update", action: "confirm" },
47 { pattern: /\bnix\s+flake\s+lock\b/, desc: "nix flake lock", action: "confirm" },
48 { pattern: /\bnix\s+(copy|push)\b/, desc: "nix store transfer", action: "confirm" },
49 { pattern: /\bnix\s+(develop|shell)\b/, desc: "nix development shell", action: "confirm" },
50 { pattern: /\bnix\s+run\b/, desc: "nix run (execute package)", action: "confirm" },
51 { pattern: /\bnix-env\s+-[ie]/, desc: "nix-env package installation", action: "confirm", suggestion: "Consider using home-manager or system configuration instead" },
52 { pattern: /\bnix\s+profile\s+(install|remove|upgrade)\b/, desc: "nix profile modification", action: "confirm", suggestion: "Consider using home-manager or system configuration instead" },
53];
54
55// ── Protected path patterns (hard block via bash) ─────────────
56
57export const dangerousBashWrites = [
58 />\s*\.env/,
59 />\s*\.dev\.vars/,
60 />\s*.*\.pem/,
61 />\s*.*\.key/,
62 /tee\s+.*\.env/,
63 /tee\s+.*\.dev\.vars/,
64 /cp\s+.*\s+\.env/,
65 /mv\s+.*\s+\.env/,
66];
67
68// ── Protected paths for write/edit tools ──────────────────────
69
70export const protectedPaths = [
71 { pattern: /\.env($|\.(?!example))/, desc: "environment file" },
72 { pattern: /\.dev\.vars($|\.[^/]+$)/, desc: "dev vars file" },
73 { pattern: /node_modules\//, desc: "node_modules" },
74 { pattern: /^\.git\/|\/\.git\//, desc: "git directory" },
75 { pattern: /\.pem$|\.key$/, desc: "private key file" },
76 { pattern: /id_rsa|id_ed25519|id_ecdsa/, desc: "SSH key" },
77 { pattern: /\.ssh\//, desc: ".ssh directory" },
78 { pattern: /secrets?\.(json|ya?ml|toml)$/i, desc: "secrets file" },
79 { pattern: /credentials/i, desc: "credentials file" },
80];
81
82export const softProtectedPaths = [
83 { pattern: /package-lock\.json$/, desc: "package-lock.json" },
84 { pattern: /yarn\.lock$/, desc: "yarn.lock" },
85 { pattern: /pnpm-lock\.yaml$/, desc: "pnpm-lock.yaml" },
86];
87
88// ── Helpers ───────────────────────────────────────────────────
89
90/** Strip content inside quotes to avoid false positives on e.g. commit messages */
91export function stripQuotedContent(command: string): string {
92 return command
93 .replace(/"(?:[^"\\]|\\.)*"/g, '""')
94 .replace(/'[^']*'/g, "''");
95}
96
97/**
98 * Match a command string against the command rules.
99 * Returns the first matching rule or null.
100 * Apply stripQuotedContent before calling this for best results.
101 */
102export function matchCommandRule(command: string): CommandRule | null {
103 for (const rule of commandRules) {
104 if (rule.pattern.test(command)) {
105 return rule;
106 }
107 }
108 return null;
109}
110
111// ── Approval gate reason builders ─────────────────────────────
112
113/**
114 * Build the block reason when user wants to modify the command.
115 * Tells the LLM to ask the user what to change and retry.
116 */
117export function buildModifyReason(desc: string): string {
118 return `User wants to modify this command (${desc}). Ask the user what they would like to change, then retry with the updated command.`;
119}
120
121/**
122 * Build the block reason when user rejects the command.
123 * Tells the LLM NOT to retry.
124 */
125export function buildRejectReason(desc: string): string {
126 return `User rejected this command (${desc}). Do NOT retry or attempt this command again.`;
127}
128
129/**
130 * Build the block reason when user rejects a file write.
131 * Tells the LLM NOT to retry.
132 */
133export function buildWriteModifyReason(desc: string, filePath: string): string {
134 return `User wants to modify this write to ${desc} (${filePath}). Ask the user what they would like to change, then retry with the updated parameters.`;
135}
136
137/**
138 * Build the block reason when user rejects a file write.
139 * Tells the LLM NOT to retry.
140 */
141export function buildWriteRejectReason(desc: string, filePath: string): string {
142 return `User rejected write to ${desc} (${filePath}). Do NOT retry or attempt this write again.`;
143}