agileflow 2.78.0 → 2.79.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.
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * edit-tool-damage-control.js - Enforce path protection for Edit tool
5
+ *
6
+ * This PreToolUse hook runs before every Edit tool execution.
7
+ * It checks the file path against patterns.yaml to block edits
8
+ * to protected paths.
9
+ *
10
+ * Path protection levels:
11
+ * zeroAccessPaths: Cannot read, write, edit, or delete
12
+ * readOnlyPaths: Can read, cannot modify or delete
13
+ * noDeletePaths: Can read and modify, cannot delete (Edit is allowed)
14
+ *
15
+ * Exit codes:
16
+ * 0 = Allow edit to proceed
17
+ * 2 = Block edit
18
+ *
19
+ * Usage (as PreToolUse hook):
20
+ * node .claude/hooks/damage-control/edit-tool-damage-control.js
21
+ *
22
+ * Environment:
23
+ * CLAUDE_TOOL_INPUT - JSON string with tool input (contains "file_path")
24
+ * CLAUDE_PROJECT_DIR - Project root directory
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ // ANSI colors for output
31
+ const c = {
32
+ reset: '\x1b[0m',
33
+ bold: '\x1b[1m',
34
+ red: '\x1b[31m',
35
+ yellow: '\x1b[33m',
36
+ cyan: '\x1b[36m',
37
+ };
38
+
39
+ // Exit codes
40
+ const EXIT_ALLOW = 0;
41
+ const EXIT_BLOCK = 2;
42
+
43
+ /**
44
+ * Load path protection rules from patterns.yaml
45
+ */
46
+ function loadPathRules(projectDir) {
47
+ const locations = [
48
+ path.join(projectDir, '.claude/hooks/damage-control/patterns.yaml'),
49
+ path.join(projectDir, '.agileflow/hooks/damage-control/patterns.yaml'),
50
+ path.join(projectDir, 'patterns.yaml'),
51
+ ];
52
+
53
+ for (const loc of locations) {
54
+ if (fs.existsSync(loc)) {
55
+ try {
56
+ const content = fs.readFileSync(loc, 'utf8');
57
+ return parsePathRules(content);
58
+ } catch (e) {
59
+ console.error(`Warning: Could not parse ${loc}: ${e.message}`);
60
+ }
61
+ }
62
+ }
63
+
64
+ return getDefaultPathRules();
65
+ }
66
+
67
+ /**
68
+ * Parse path rules from YAML content
69
+ */
70
+ function parsePathRules(content) {
71
+ const rules = {
72
+ zeroAccessPaths: [],
73
+ readOnlyPaths: [],
74
+ noDeletePaths: [],
75
+ };
76
+
77
+ let currentSection = null;
78
+
79
+ const lines = content.split('\n');
80
+
81
+ for (const line of lines) {
82
+ if (line.trim().startsWith('#') || line.trim() === '') continue;
83
+
84
+ if (line.match(/^zeroAccessPaths:/)) {
85
+ currentSection = 'zeroAccessPaths';
86
+ continue;
87
+ }
88
+ if (line.match(/^readOnlyPaths:/)) {
89
+ currentSection = 'readOnlyPaths';
90
+ continue;
91
+ }
92
+ if (line.match(/^noDeletePaths:/)) {
93
+ currentSection = 'noDeletePaths';
94
+ continue;
95
+ }
96
+ if (line.match(/^(bashToolPatterns|askPatterns|agileflowPatterns|config):/)) {
97
+ currentSection = null;
98
+ continue;
99
+ }
100
+
101
+ if (currentSection && rules[currentSection]) {
102
+ const pathMatch = line.match(/^\s+-\s*['"]?(.+?)['"]?\s*$/);
103
+ if (pathMatch) {
104
+ rules[currentSection].push(pathMatch[1]);
105
+ }
106
+ }
107
+ }
108
+
109
+ return rules;
110
+ }
111
+
112
+ /**
113
+ * Default path rules if patterns.yaml not found
114
+ */
115
+ function getDefaultPathRules() {
116
+ return {
117
+ zeroAccessPaths: [
118
+ '~/.ssh/',
119
+ '~/.aws/credentials',
120
+ '.env',
121
+ '.env.local',
122
+ '.env.production',
123
+ ],
124
+ readOnlyPaths: [
125
+ '/etc/',
126
+ '~/.bashrc',
127
+ '~/.zshrc',
128
+ 'package-lock.json',
129
+ 'yarn.lock',
130
+ '.git/',
131
+ ],
132
+ noDeletePaths: [
133
+ '.agileflow/',
134
+ '.claude/',
135
+ 'docs/09-agents/status.json',
136
+ 'CLAUDE.md',
137
+ ],
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Expand home directory in path
143
+ */
144
+ function expandHome(filePath) {
145
+ if (filePath.startsWith('~/')) {
146
+ return path.join(process.env.HOME || '', filePath.slice(2));
147
+ }
148
+ return filePath;
149
+ }
150
+
151
+ /**
152
+ * Check if a file path matches a pattern
153
+ * Supports:
154
+ * - Exact paths
155
+ * - Directory prefixes (ending with /)
156
+ * - Glob wildcards (**)
157
+ */
158
+ function pathMatches(filePath, pattern) {
159
+ const expandedPattern = expandHome(pattern);
160
+ const normalizedFile = path.normalize(filePath);
161
+ const normalizedPattern = path.normalize(expandedPattern);
162
+
163
+ // Exact match
164
+ if (normalizedFile === normalizedPattern) return true;
165
+
166
+ // Directory prefix match (pattern ends with /)
167
+ if (pattern.endsWith('/')) {
168
+ if (normalizedFile.startsWith(normalizedPattern)) return true;
169
+ }
170
+
171
+ // Glob pattern (**/)
172
+ if (pattern.includes('**/')) {
173
+ const globPart = pattern.split('**/')[1];
174
+ if (normalizedFile.includes(globPart)) return true;
175
+ }
176
+
177
+ // Simple wildcard at end
178
+ if (pattern.endsWith('*')) {
179
+ const prefix = normalizedPattern.slice(0, -1);
180
+ if (normalizedFile.startsWith(prefix)) return true;
181
+ }
182
+
183
+ // Basename match (for patterns like .env)
184
+ if (!pattern.includes('/') && !pattern.includes(path.sep)) {
185
+ const basename = path.basename(normalizedFile);
186
+ if (basename === pattern) return true;
187
+ // Pattern like .env* matching .env.local
188
+ if (pattern.endsWith('*')) {
189
+ const patternBase = pattern.slice(0, -1);
190
+ if (basename.startsWith(patternBase)) return true;
191
+ }
192
+ }
193
+
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * Check if file path is protected
199
+ * Returns: { blocked: boolean, reason: string, level: string }
200
+ */
201
+ function checkPath(filePath, rules) {
202
+ // Check zero access paths (blocked completely)
203
+ for (const pattern of rules.zeroAccessPaths) {
204
+ if (pathMatches(filePath, pattern)) {
205
+ return {
206
+ blocked: true,
207
+ reason: `Path is in zero-access protected list: ${pattern}`,
208
+ level: 'zero-access',
209
+ };
210
+ }
211
+ }
212
+
213
+ // Check read-only paths (cannot edit)
214
+ for (const pattern of rules.readOnlyPaths) {
215
+ if (pathMatches(filePath, pattern)) {
216
+ return {
217
+ blocked: true,
218
+ reason: `Path is read-only: ${pattern}`,
219
+ level: 'read-only',
220
+ };
221
+ }
222
+ }
223
+
224
+ // noDeletePaths allows editing, so we don't block here
225
+ // (deletion is handled by a different mechanism or file watcher)
226
+
227
+ return { blocked: false, reason: null, level: null };
228
+ }
229
+
230
+ /**
231
+ * Main entry point
232
+ */
233
+ function main() {
234
+ const toolInput = process.env.CLAUDE_TOOL_INPUT;
235
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
236
+
237
+ if (!toolInput) {
238
+ process.exit(EXIT_ALLOW);
239
+ }
240
+
241
+ let input;
242
+ try {
243
+ input = JSON.parse(toolInput);
244
+ } catch (e) {
245
+ console.error('Error parsing CLAUDE_TOOL_INPUT:', e.message);
246
+ process.exit(EXIT_ALLOW);
247
+ }
248
+
249
+ const filePath = input.file_path;
250
+ if (!filePath) {
251
+ process.exit(EXIT_ALLOW);
252
+ }
253
+
254
+ // Resolve to absolute path
255
+ const absolutePath = path.isAbsolute(filePath)
256
+ ? filePath
257
+ : path.join(projectDir, filePath);
258
+
259
+ // Load rules
260
+ const rules = loadPathRules(projectDir);
261
+
262
+ // Check path
263
+ const result = checkPath(absolutePath, rules);
264
+
265
+ if (result.blocked) {
266
+ console.error(`${c.red}${c.bold}BLOCKED${c.reset}: ${result.reason}`);
267
+ console.error(`${c.yellow}File: ${filePath}${c.reset}`);
268
+ console.error(`${c.cyan}This file is protected by damage control (${result.level}).${c.reset}`);
269
+ process.exit(EXIT_BLOCK);
270
+ }
271
+
272
+ process.exit(EXIT_ALLOW);
273
+ }
274
+
275
+ if (require.main === module) {
276
+ main();
277
+ }
278
+
279
+ module.exports = { checkPath, loadPathRules, pathMatches };
@@ -0,0 +1,227 @@
1
+ # AgileFlow Damage Control - Security Patterns
2
+ #
3
+ # This file defines patterns for blocking destructive commands and protecting
4
+ # sensitive paths. The damage control hooks read this file to determine
5
+ # whether to allow, block, or ask for confirmation before executing commands.
6
+ #
7
+ # Exit codes from hooks:
8
+ # 0 = Allow command to proceed
9
+ # 2 = Block command (show error to user)
10
+ #
11
+ # To ask for confirmation, hooks output JSON: {"result": "ask", "message": "..."}
12
+
13
+ # ============================================================================
14
+ # BASH TOOL PATTERNS
15
+ # ============================================================================
16
+ # Regex patterns to match against bash commands
17
+ # If matched with ask: false (or no ask key), command is BLOCKED
18
+ # If matched with ask: true, user is asked for confirmation
19
+
20
+ bashToolPatterns:
21
+ # Recursive/force deletion
22
+ - pattern: '\brm\s+-[rRf]'
23
+ reason: "rm with recursive or force flags can destroy entire directories"
24
+
25
+ - pattern: '\brm\s+.*--no-preserve-root'
26
+ reason: "rm with --no-preserve-root is catastrophically dangerous"
27
+
28
+ - pattern: '\brm\s+-rf\s+/'
29
+ reason: "rm -rf on root directory would destroy the entire system"
30
+
31
+ # SQL destructive commands without WHERE clause
32
+ - pattern: 'DELETE\s+FROM\s+\w+\s*;'
33
+ reason: "DELETE without WHERE clause would delete all records"
34
+
35
+ - pattern: 'TRUNCATE\s+(TABLE\s+)?\w+'
36
+ reason: "TRUNCATE removes all data from table"
37
+
38
+ - pattern: 'DROP\s+(TABLE|DATABASE|SCHEMA|INDEX)'
39
+ reason: "DROP commands permanently destroy database objects"
40
+
41
+ # Git force operations
42
+ - pattern: 'git\s+push\s+.*--force'
43
+ reason: "Force push can overwrite remote history"
44
+ ask: true
45
+
46
+ - pattern: 'git\s+push\s+.*-f\b'
47
+ reason: "Force push can overwrite remote history"
48
+ ask: true
49
+
50
+ - pattern: 'git\s+reset\s+--hard'
51
+ reason: "Hard reset discards uncommitted changes"
52
+ ask: true
53
+
54
+ # Format/wipe operations
55
+ - pattern: '\bmkfs\b'
56
+ reason: "mkfs formats filesystems, destroying all data"
57
+
58
+ - pattern: '\bdd\s+.*of=/dev/'
59
+ reason: "dd writing to device can destroy disk data"
60
+
61
+ - pattern: '\bshred\b'
62
+ reason: "shred permanently destroys file data"
63
+
64
+ # Credential/secret exposure
65
+ - pattern: 'cat\s+.*\.env'
66
+ reason: "Displaying .env may expose secrets"
67
+ ask: true
68
+
69
+ - pattern: 'cat\s+.*/\.ssh/'
70
+ reason: "Displaying SSH keys is a security risk"
71
+
72
+ - pattern: 'cat\s+.*/credentials'
73
+ reason: "Displaying credentials files is a security risk"
74
+
75
+ # Cloud CLI destructive operations
76
+ - pattern: 'aws\s+s3\s+rm\s+--recursive'
77
+ reason: "Recursive S3 delete can destroy entire buckets"
78
+ ask: true
79
+
80
+ - pattern: 'aws\s+ec2\s+terminate-instances'
81
+ reason: "Terminating EC2 instances is irreversible"
82
+ ask: true
83
+
84
+ - pattern: 'gcloud\s+.*delete'
85
+ reason: "GCloud delete operations may be destructive"
86
+ ask: true
87
+
88
+ # Docker cleanup commands
89
+ - pattern: 'docker\s+system\s+prune\s+-a'
90
+ reason: "Docker prune -a removes all unused images"
91
+ ask: true
92
+
93
+ - pattern: 'docker\s+volume\s+rm'
94
+ reason: "Docker volume removal may delete persistent data"
95
+ ask: true
96
+
97
+ # npm/package manager dangerous commands
98
+ - pattern: 'npm\s+unpublish'
99
+ reason: "npm unpublish can break dependent packages"
100
+ ask: true
101
+
102
+ - pattern: 'npm\s+deprecate'
103
+ reason: "npm deprecate affects package visibility"
104
+ ask: true
105
+
106
+ # ============================================================================
107
+ # ASK PATTERNS (CONFIRMATION REQUIRED)
108
+ # ============================================================================
109
+ # These patterns trigger a confirmation prompt but don't block by default
110
+
111
+ askPatterns:
112
+ - pattern: 'DELETE\s+FROM\s+\w+\s+WHERE'
113
+ reason: "Deleting specific records - confirm data is correct"
114
+
115
+ - pattern: 'UPDATE\s+\w+\s+SET'
116
+ reason: "Updating records - confirm scope is correct"
117
+
118
+ - pattern: 'npm\s+publish'
119
+ reason: "Publishing to npm is permanent"
120
+
121
+ - pattern: 'git\s+tag\s+-d'
122
+ reason: "Deleting git tags"
123
+
124
+ - pattern: 'kubectl\s+delete'
125
+ reason: "Kubernetes delete operations"
126
+
127
+ # ============================================================================
128
+ # PATH PROTECTION
129
+ # ============================================================================
130
+ # Three protection levels:
131
+ # zeroAccessPaths: Cannot read, write, edit, or delete
132
+ # readOnlyPaths: Can read, cannot modify or delete
133
+ # noDeletePaths: Can read and modify, cannot delete
134
+
135
+ zeroAccessPaths:
136
+ # System credentials
137
+ - ~/.ssh/
138
+ - ~/.gnupg/
139
+ - ~/.aws/credentials
140
+ - ~/.config/gcloud/
141
+
142
+ # Environment secrets
143
+ - .env
144
+ - .env.local
145
+ - .env.production
146
+ - .env.*.local
147
+
148
+ # Secret files
149
+ - '**/secrets/**'
150
+ - '**/credentials/**'
151
+ - '**/*.pem'
152
+ - '**/*.key'
153
+ - '**/id_rsa'
154
+ - '**/id_ed25519'
155
+
156
+ readOnlyPaths:
157
+ # System config
158
+ - /etc/
159
+ - ~/.bashrc
160
+ - ~/.zshrc
161
+ - ~/.profile
162
+
163
+ # Package locks (should use npm install, not edit directly)
164
+ - package-lock.json
165
+ - yarn.lock
166
+ - pnpm-lock.yaml
167
+
168
+ # Git internals
169
+ - .git/
170
+
171
+ noDeletePaths:
172
+ # AgileFlow core
173
+ - .agileflow/
174
+ - .agileflow/config.json
175
+ - .agileflow/scripts/
176
+
177
+ # Claude Code hooks and commands
178
+ - .claude/
179
+ - .claude/hooks/
180
+ - .claude/commands/
181
+ - .claude/settings.json
182
+
183
+ # Project documentation (can edit, can't delete)
184
+ - docs/09-agents/status.json
185
+ - CLAUDE.md
186
+ - AGENTS.md
187
+
188
+ # ============================================================================
189
+ # AGILEFLOW-SPECIFIC PROTECTION
190
+ # ============================================================================
191
+ # Additional patterns specific to AgileFlow projects
192
+
193
+ agileflowPatterns:
194
+ # Protect AgileFlow infrastructure
195
+ - pattern: 'rm.*\.agileflow'
196
+ reason: "Deleting .agileflow would break AgileFlow installation"
197
+
198
+ - pattern: 'rm.*\.claude'
199
+ reason: "Deleting .claude would break Claude Code configuration"
200
+
201
+ - pattern: 'rm.*status\.json'
202
+ reason: "Deleting status.json would lose story tracking data"
203
+
204
+ # Dangerous npm operations in AgileFlow context
205
+ - pattern: 'npm\s+uninstall\s+agileflow'
206
+ reason: "Uninstalling AgileFlow - confirm this is intentional"
207
+ ask: true
208
+
209
+ # ============================================================================
210
+ # CONFIGURATION
211
+ # ============================================================================
212
+ # Settings for the damage control system
213
+
214
+ config:
215
+ # Log blocked commands for pattern refinement
216
+ logBlocked: true
217
+ logPath: .agileflow/logs/damage-control.log
218
+
219
+ # Show detailed reason when blocking
220
+ showBlockReason: true
221
+
222
+ # Timeout for hook execution (seconds)
223
+ hookTimeout: 5
224
+
225
+ # Enable/disable prompt hooks (AI-based evaluation)
226
+ promptHooksEnabled: false
227
+ promptHookMessage: "Evaluate if this command could cause irreversible damage. Block if dangerous."