hardstop 1.4.7 → 1.4.9

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.
@@ -1,272 +1,273 @@
1
- #!/usr/bin/env python3
2
- """
3
- Hardstop Plugin — Slash Command Handler
4
-
5
- Commands:
6
- /hs on Enable protection (default)
7
- /hs off Disable protection
8
- /hs skip [n] Skip next n commands (default: 1)
9
- /hs status Show current state
10
- /hs log Show recent audit log entries
11
- /hs help Show this help
12
- """
13
-
14
- import sys
15
- import io
16
- import json
17
- from pathlib import Path
18
- from datetime import datetime
19
- import os
20
- import tempfile
21
-
22
- # Fix Windows console encoding for Unicode output
23
- if sys.platform == "win32":
24
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
25
- sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
26
-
27
- STATE_DIR = Path.home() / ".hardstop"
28
- STATE_FILE = STATE_DIR / "state.json"
29
- SKIP_FILE = STATE_DIR / "skip_next"
30
- LOG_FILE = STATE_DIR / "audit.log"
31
- PLUGIN_DIR = Path.home() / ".claude" / "plugins" / "hs"
32
-
33
-
34
- def get_version() -> str:
35
- """Read version from plugin.json (single source of truth)."""
36
- plugin_json = PLUGIN_DIR / ".claude-plugin" / "plugin.json"
37
- try:
38
- if plugin_json.exists():
39
- data = json.loads(plugin_json.read_text())
40
- return data.get("version", "unknown")
41
- except (json.JSONDecodeError, IOError):
42
- pass
43
- return "unknown"
44
-
45
-
46
- def load_state() -> dict:
47
- try:
48
- if STATE_FILE.exists():
49
- return json.loads(STATE_FILE.read_text())
50
- except json.JSONDecodeError:
51
- pass
52
- except (IOError, OSError):
53
- pass
54
- return {"enabled": True}
55
-
56
-
57
- def save_state(state: dict):
58
- try:
59
- STATE_DIR.mkdir(parents=True, exist_ok=True)
60
- payload = json.dumps({"enabled": bool(state.get("enabled", True))}, indent=2)
61
- with tempfile.NamedTemporaryFile(
62
- mode="w",
63
- encoding="utf-8",
64
- delete=False,
65
- dir=str(STATE_DIR),
66
- prefix="state.",
67
- suffix=".tmp",
68
- ) as tf:
69
- tf.write(payload)
70
- tmp_path = tf.name
71
- os.replace(tmp_path, STATE_FILE)
72
- except Exception as e:
73
- print(f"Error saving state: {e}", file=sys.stderr)
74
-
75
-
76
- def cmd_on():
77
- state = load_state()
78
- state["enabled"] = True
79
- save_state(state)
80
- print("✅ Hardstop enabled")
81
-
82
-
83
- def cmd_off():
84
- state = load_state()
85
- state["enabled"] = False
86
- save_state(state)
87
- print("⚠️ Hardstop disabled")
88
- print(" Dangerous commands will NOT be blocked.")
89
- print(" Note: Credential file protection (Read hook) remains active.")
90
- print(" Use '/hs on' to re-enable, or '/hs skip' to bypass read protection.")
91
-
92
-
93
- def cmd_skip(count: int = 1):
94
- """Set skip counter for next N commands."""
95
- if count < 1:
96
- print("❌ Skip count must be at least 1")
97
- return
98
- if count > 10:
99
- print("❌ Skip count cannot exceed 10 (safety limit)")
100
- return
101
-
102
- try:
103
- STATE_DIR.mkdir(parents=True, exist_ok=True)
104
- SKIP_FILE.write_text(str(count))
105
- except Exception as e:
106
- print(f"Error setting skip flag: {e}", file=sys.stderr)
107
- return
108
-
109
- if count == 1:
110
- print("⏭️ Next command will skip safety check")
111
- print(" One-time bypass protection resumes after.")
112
- else:
113
- print(f"⏭️ Next {count} commands will skip safety check")
114
- print(" Multi-skip bypass protection resumes after.")
115
-
116
-
117
- def cmd_status():
118
- state = load_state()
119
- enabled = state.get("enabled", True)
120
-
121
- # Check skip count
122
- skip_count = 0
123
- if SKIP_FILE.exists():
124
- try:
125
- skip_count = int(SKIP_FILE.read_text().strip())
126
- except (ValueError, IOError):
127
- skip_count = 1 # Fallback for old format
128
-
129
- print(f"Hardstop v{get_version()}")
130
- print()
131
- print(f" Status: {'🟢 Enabled' if enabled else '🔴 Disabled'}")
132
- if skip_count > 0:
133
- print(f" Skip next: {skip_count} command{'s' if skip_count > 1 else ''}")
134
- else:
135
- print(f" Skip next: No")
136
- print(f" Fail mode: Fail-closed (errors block commands)")
137
- print()
138
- print(f" State file: {STATE_FILE}")
139
- print(f" Skip file: {SKIP_FILE}")
140
- print(f" Audit log: {LOG_FILE}")
141
-
142
- # Show recent stats if log exists
143
- if LOG_FILE.exists():
144
- try:
145
- lines = LOG_FILE.read_text().strip().split('\n')
146
- recent = lines[-100:] # Last 100 entries
147
- blocks = sum(1 for l in recent if '"verdict": "BLOCK"' in l)
148
- allows = sum(1 for l in recent if '"verdict": "ALLOW"' in l)
149
- print()
150
- print(f" Recent stats (last {len(recent)} commands):")
151
- print(f" Blocked: {blocks}")
152
- print(f" Allowed: {allows}")
153
- except Exception:
154
- pass
155
-
156
- # GitHub star CTA
157
- print()
158
- print(" ⭐ Enjoying Hardstop? Star us on GitHub!")
159
- print(" https://github.com/frmoretto/hardstop")
160
-
161
-
162
- def cmd_log():
163
- """Show recent audit log entries."""
164
- if not LOG_FILE.exists():
165
- print("No audit log found yet.")
166
- print(f"Log will be created at: {LOG_FILE}")
167
- return
168
-
169
- try:
170
- lines = LOG_FILE.read_text().strip().split('\n')
171
- recent = lines[-20:] # Last 20 entries
172
-
173
- print(f"Hardstop Audit Log (last {len(recent)} entries)")
174
- print("=" * 60)
175
-
176
- for line in recent:
177
- try:
178
- entry = json.loads(line)
179
- ts = entry.get("timestamp", "")[:19] # Trim microseconds
180
- verdict = entry.get("verdict", "?")
181
- layer = entry.get("layer", "?")
182
- cmd = entry.get("command", "")[:50]
183
- reason = entry.get("reason", "")[:30]
184
-
185
- icon = "🛑" if verdict == "BLOCK" else "✅"
186
- print(f"{ts} {icon} [{layer:7}] {cmd}")
187
- if reason:
188
- print(f" └─ {reason}")
189
- except json.JSONDecodeError:
190
- continue
191
-
192
- print()
193
- print(f"Full log: {LOG_FILE}")
194
-
195
- except Exception as e:
196
- print(f"Error reading log: {e}")
197
-
198
-
199
- def cmd_help():
200
- print(f"""
201
- Hardstop v{get_version()}
202
- The mechanical brake for AI-generated commands
203
-
204
- Commands:
205
- /hs on Enable protection (default)
206
- /hs off Disable protection temporarily
207
- /hs skip [n] Skip safety check for next n commands (default: 1)
208
- /hs status Show current state and stats
209
- /hs log Show recent audit log entries
210
- /hs help Show this help
211
-
212
- Aliases: /hardstop, /hard, /hs
213
-
214
- What it catches:
215
- 🛑 Instant block: rm -rf ~/, fork bombs, reverse shells, credential exfil
216
- 🤖 LLM analysis: Obfuscated commands, novel attacks, context-dependent risks
217
-
218
- Design:
219
- • Fail-closed: If safety check fails, command is blocked (not allowed)
220
- Command chaining: Analyzes all parts of piped/chained commands
221
- Audit logging: All decisions logged to ~/.hardstop/audit.log
222
-
223
- Works independently — no skill required.
224
- """)
225
-
226
-
227
- def main():
228
- # Parse command
229
- if len(sys.argv) < 2:
230
- cmd_help()
231
- return
232
-
233
- subcommand = sys.argv[1].lower()
234
-
235
- # Handle skip with optional count argument
236
- if subcommand in ("skip", "bypass"):
237
- count = 1
238
- if len(sys.argv) >= 3:
239
- try:
240
- count = int(sys.argv[2])
241
- except ValueError:
242
- print(f"❌ Invalid skip count: {sys.argv[2]}")
243
- print(" Usage: /hs skip [count]")
244
- return
245
- cmd_skip(count)
246
- return
247
-
248
- commands = {
249
- "on": cmd_on,
250
- "enable": cmd_on,
251
- "off": cmd_off,
252
- "disable": cmd_off,
253
- "status": cmd_status,
254
- "state": cmd_status,
255
- "log": cmd_log,
256
- "logs": cmd_log,
257
- "audit": cmd_log,
258
- "help": cmd_help,
259
- "-h": cmd_help,
260
- "--help": cmd_help,
261
- }
262
-
263
- handler = commands.get(subcommand)
264
- if handler:
265
- handler()
266
- else:
267
- print(f"Unknown command: {subcommand}")
268
- print("Use '/hs help' for available commands.")
269
-
270
-
271
- if __name__ == "__main__":
272
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hardstop Plugin — Slash Command Handler
4
+
5
+ Commands:
6
+ /hs on Enable protection (default)
7
+ /hs off Disable protection
8
+ /hs skip [n] Skip next n commands (default: 1)
9
+ /hs status Show current state
10
+ /hs log Show recent audit log entries
11
+ /hs help Show this help
12
+ """
13
+
14
+ import sys
15
+ import io
16
+ import json
17
+ from pathlib import Path
18
+ from datetime import datetime
19
+ import os
20
+ import tempfile
21
+
22
+ # Fix Windows console encoding for Unicode output
23
+ if sys.platform == "win32":
24
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
25
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
26
+
27
+ STATE_DIR = Path.home() / ".hardstop"
28
+ STATE_FILE = STATE_DIR / "state.json"
29
+ SKIP_FILE = STATE_DIR / "skip_next"
30
+ LOG_FILE = STATE_DIR / "audit.log"
31
+ # Derive plugin dir from installed location: commands/ -> hs/
32
+ PLUGIN_DIR = Path(__file__).absolute().parent.parent
33
+
34
+
35
+ def get_version() -> str:
36
+ """Read version from plugin.json (single source of truth)."""
37
+ plugin_json = PLUGIN_DIR / ".claude-plugin" / "plugin.json"
38
+ try:
39
+ if plugin_json.exists():
40
+ data = json.loads(plugin_json.read_text())
41
+ return data.get("version", "unknown")
42
+ except (json.JSONDecodeError, IOError):
43
+ pass
44
+ return "unknown"
45
+
46
+
47
+ def load_state() -> dict:
48
+ try:
49
+ if STATE_FILE.exists():
50
+ return json.loads(STATE_FILE.read_text())
51
+ except json.JSONDecodeError:
52
+ pass
53
+ except (IOError, OSError):
54
+ pass
55
+ return {"enabled": True}
56
+
57
+
58
+ def save_state(state: dict):
59
+ try:
60
+ STATE_DIR.mkdir(parents=True, exist_ok=True)
61
+ payload = json.dumps({"enabled": bool(state.get("enabled", True))}, indent=2)
62
+ with tempfile.NamedTemporaryFile(
63
+ mode="w",
64
+ encoding="utf-8",
65
+ delete=False,
66
+ dir=str(STATE_DIR),
67
+ prefix="state.",
68
+ suffix=".tmp",
69
+ ) as tf:
70
+ tf.write(payload)
71
+ tmp_path = tf.name
72
+ os.replace(tmp_path, STATE_FILE)
73
+ except Exception as e:
74
+ print(f"Error saving state: {e}", file=sys.stderr)
75
+
76
+
77
+ def cmd_on():
78
+ state = load_state()
79
+ state["enabled"] = True
80
+ save_state(state)
81
+ print("✅ Hardstop enabled")
82
+
83
+
84
+ def cmd_off():
85
+ state = load_state()
86
+ state["enabled"] = False
87
+ save_state(state)
88
+ print("⚠️ Hardstop disabled")
89
+ print(" Dangerous commands will NOT be blocked.")
90
+ print(" Note: Credential file protection (Read hook) remains active.")
91
+ print(" Use '/hs on' to re-enable, or '/hs skip' to bypass read protection.")
92
+
93
+
94
+ def cmd_skip(count: int = 1):
95
+ """Set skip counter for next N commands."""
96
+ if count < 1:
97
+ print("❌ Skip count must be at least 1")
98
+ return
99
+ if count > 10:
100
+ print("❌ Skip count cannot exceed 10 (safety limit)")
101
+ return
102
+
103
+ try:
104
+ STATE_DIR.mkdir(parents=True, exist_ok=True)
105
+ SKIP_FILE.write_text(str(count))
106
+ except Exception as e:
107
+ print(f"Error setting skip flag: {e}", file=sys.stderr)
108
+ return
109
+
110
+ if count == 1:
111
+ print("⏭️ Next command will skip safety check")
112
+ print(" One-time bypass — protection resumes after.")
113
+ else:
114
+ print(f"⏭️ Next {count} commands will skip safety check")
115
+ print(" Multi-skip bypass — protection resumes after.")
116
+
117
+
118
+ def cmd_status():
119
+ state = load_state()
120
+ enabled = state.get("enabled", True)
121
+
122
+ # Check skip count
123
+ skip_count = 0
124
+ if SKIP_FILE.exists():
125
+ try:
126
+ skip_count = int(SKIP_FILE.read_text().strip())
127
+ except (ValueError, IOError):
128
+ skip_count = 1 # Fallback for old format
129
+
130
+ print(f"Hardstop v{get_version()}")
131
+ print()
132
+ print(f" Status: {'🟢 Enabled' if enabled else '🔴 Disabled'}")
133
+ if skip_count > 0:
134
+ print(f" Skip next: {skip_count} command{'s' if skip_count > 1 else ''}")
135
+ else:
136
+ print(f" Skip next: No")
137
+ print(f" Fail mode: Fail-closed (errors block commands)")
138
+ print()
139
+ print(f" State file: {STATE_FILE}")
140
+ print(f" Skip file: {SKIP_FILE}")
141
+ print(f" Audit log: {LOG_FILE}")
142
+
143
+ # Show recent stats if log exists
144
+ if LOG_FILE.exists():
145
+ try:
146
+ lines = LOG_FILE.read_text().strip().split('\n')
147
+ recent = lines[-100:] # Last 100 entries
148
+ blocks = sum(1 for l in recent if '"verdict": "BLOCK"' in l)
149
+ allows = sum(1 for l in recent if '"verdict": "ALLOW"' in l)
150
+ print()
151
+ print(f" Recent stats (last {len(recent)} commands):")
152
+ print(f" Blocked: {blocks}")
153
+ print(f" Allowed: {allows}")
154
+ except Exception:
155
+ pass
156
+
157
+ # GitHub star CTA
158
+ print()
159
+ print(" ⭐ Enjoying Hardstop? Star us on GitHub!")
160
+ print(" https://github.com/frmoretto/hardstop")
161
+
162
+
163
+ def cmd_log():
164
+ """Show recent audit log entries."""
165
+ if not LOG_FILE.exists():
166
+ print("No audit log found yet.")
167
+ print(f"Log will be created at: {LOG_FILE}")
168
+ return
169
+
170
+ try:
171
+ lines = LOG_FILE.read_text().strip().split('\n')
172
+ recent = lines[-20:] # Last 20 entries
173
+
174
+ print(f"Hardstop Audit Log (last {len(recent)} entries)")
175
+ print("=" * 60)
176
+
177
+ for line in recent:
178
+ try:
179
+ entry = json.loads(line)
180
+ ts = entry.get("timestamp", "")[:19] # Trim microseconds
181
+ verdict = entry.get("verdict", "?")
182
+ layer = entry.get("layer", "?")
183
+ cmd = entry.get("command", "")[:50]
184
+ reason = entry.get("reason", "")[:30]
185
+
186
+ icon = "🛑" if verdict == "BLOCK" else "✅"
187
+ print(f"{ts} {icon} [{layer:7}] {cmd}")
188
+ if reason:
189
+ print(f" └─ {reason}")
190
+ except json.JSONDecodeError:
191
+ continue
192
+
193
+ print()
194
+ print(f"Full log: {LOG_FILE}")
195
+
196
+ except Exception as e:
197
+ print(f"Error reading log: {e}")
198
+
199
+
200
+ def cmd_help():
201
+ print(f"""
202
+ Hardstop v{get_version()}
203
+ The mechanical brake for AI-generated commands
204
+
205
+ Commands:
206
+ /hs on Enable protection (default)
207
+ /hs off Disable protection temporarily
208
+ /hs skip [n] Skip safety check for next n commands (default: 1)
209
+ /hs status Show current state and stats
210
+ /hs log Show recent audit log entries
211
+ /hs help Show this help
212
+
213
+ Aliases: /hardstop, /hard, /hs
214
+
215
+ What it catches:
216
+ 🛑 Instant block: rm -rf ~/, fork bombs, reverse shells, credential exfil
217
+ 🤖 LLM analysis: Obfuscated commands, novel attacks, context-dependent risks
218
+
219
+ Design:
220
+ Fail-closed: If safety check fails, command is blocked (not allowed)
221
+ Command chaining: Analyzes all parts of piped/chained commands
222
+ • Audit logging: All decisions logged to ~/.hardstop/audit.log
223
+
224
+ Works independently — no skill required.
225
+ """)
226
+
227
+
228
+ def main():
229
+ # Parse command
230
+ if len(sys.argv) < 2:
231
+ cmd_help()
232
+ return
233
+
234
+ subcommand = sys.argv[1].lower()
235
+
236
+ # Handle skip with optional count argument
237
+ if subcommand in ("skip", "bypass"):
238
+ count = 1
239
+ if len(sys.argv) >= 3:
240
+ try:
241
+ count = int(sys.argv[2])
242
+ except ValueError:
243
+ print(f" Invalid skip count: {sys.argv[2]}")
244
+ print(" Usage: /hs skip [count]")
245
+ return
246
+ cmd_skip(count)
247
+ return
248
+
249
+ commands = {
250
+ "on": cmd_on,
251
+ "enable": cmd_on,
252
+ "off": cmd_off,
253
+ "disable": cmd_off,
254
+ "status": cmd_status,
255
+ "state": cmd_status,
256
+ "log": cmd_log,
257
+ "logs": cmd_log,
258
+ "audit": cmd_log,
259
+ "help": cmd_help,
260
+ "-h": cmd_help,
261
+ "--help": cmd_help,
262
+ }
263
+
264
+ handler = commands.get(subcommand)
265
+ if handler:
266
+ handler()
267
+ else:
268
+ print(f"Unknown command: {subcommand}")
269
+ print("Use '/hs help' for available commands.")
270
+
271
+
272
+ if __name__ == "__main__":
273
+ main()
@@ -102,14 +102,18 @@ DANGEROUS_PATTERNS = _load_dangerous_patterns()
102
102
 
103
103
  # Note: Legacy hardcoded patterns removed in v1.4.0 (now in patterns/dangerous_commands.yaml)
104
104
 
105
+ # Derive config dir from installed location: hooks/ -> hs/ -> plugins/ -> config dir
106
+ _CLAUDE_DIR = str(Path(__file__).absolute().parent.parent.parent.parent)
107
+ _CLAUDE_DIR_RE = re.escape(_CLAUDE_DIR)
108
+
105
109
  SAFE_PATTERNS = [
106
110
  # Hardstop's own operations (must be able to manage itself)
107
- r"^python\s+.*[/\\]\.claude[/\\]plugins[/\\]hs[/\\].*\.py(?:\s+.*)?$",
111
+ rf"^python\s+.*{_CLAUDE_DIR_RE}[/\\]plugins[/\\]hs[/\\].*\.py(?:\s+.*)?$",
108
112
  r"^python\s+.*\.hardstop.*$",
109
113
  r"^cat\s+.*\.hardstop[/\\].*$",
110
- r"^cat\s+.*\.claude[/\\]plugins[/\\]hs[/\\].*$",
114
+ rf"^cat\s+.*{_CLAUDE_DIR_RE}[/\\]plugins[/\\]hs[/\\].*$",
111
115
  r"^rm\s+(-f\s+)?.*\.hardstop[/\\](skip_next|hook_debug\.log)$",
112
- r"^grep\s+.*\.claude[/\\]plugins[/\\]hs[/\\].*$",
116
+ rf"^grep\s+.*{_CLAUDE_DIR_RE}[/\\]plugins[/\\]hs[/\\].*$",
113
117
 
114
118
  # Read-only operations
115
119
  r"^ls(?:\s+.*)?$",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hardstop",
3
- "version": "1.4.7",
3
+ "version": "1.4.9",
4
4
  "description": "Pre-execution safety layer for Claude Code - blocks dangerous commands before they run. Part of the Hardstop ecosystem.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -18,8 +18,8 @@ triggers:
18
18
 
19
19
  When this skill is activated via the Skill tool, check the ARGUMENTS field immediately:
20
20
 
21
- | Argument | Action run this Bash command FIRST before anything else |
22
- |----------|----------------------------------------------------------|
21
+ | Argument | Action (user-requested via /hs) |
22
+ |----------|----------------------------------|
23
23
  | `skip [n]` | `python ~/.claude/plugins/hs/commands/hs_cmd.py skip [n]` |
24
24
  | `on` / `enable` | `python ~/.claude/plugins/hs/commands/hs_cmd.py on` |
25
25
  | `off` / `disable` | `python ~/.claude/plugins/hs/commands/hs_cmd.py off` |
@@ -27,11 +27,15 @@ When this skill is activated via the Skill tool, check the ARGUMENTS field immed
27
27
  | `log` | `python ~/.claude/plugins/hs/commands/hs_cmd.py log` |
28
28
  | *(none)* | Apply the safety protocol below to evaluate the pending command |
29
29
 
30
- **Execute the Bash command immediately.** The hook reads `~/.hardstop/skip_next` if that file is not written, skips have no effect.
30
+ Run the corresponding command the user has explicitly requested this action via `/hs`. The hook reads `~/.hardstop/skip_next`; if that file is not written, skips have no effect.
31
31
 
32
32
  ---
33
33
 
34
- > **Note:** This skill complements the Hardstop plugin. The plugin provides deterministic protection via hooks; this skill provides LLM-level awareness for platforms without hook support.
34
+ > **Security Architecture:** This skill is the **instruction layer** for the [Hardstop plugin](https://github.com/frmoretto/hardstop). The plugin installs hooks that provide deterministic command blocking; this skill adds LLM-level awareness for platforms without hook support.
35
+ >
36
+ > - The `hs_cmd.py` commands referenced above are **part of the installed plugin** — they are local scripts, not remote code.
37
+ > - Credential paths (`~/.ssh`, `~/.aws`, `.env`, etc.) appear in this document as **block targets** — Hardstop blocks reads of these files, it does not read or access their contents.
38
+ > - The `skip` bypass requires explicit user invocation (`/hs skip`), is scoped to the next N commands only (default 1), and the hook still runs — it just honors the user-set skip counter.
35
39
 
36
40
  **Purpose:** Protect users from dangerous AI-initiated actions. The mechanical brake for AI-generated commands.
37
41
 
@@ -78,7 +82,7 @@ User: No
78
82
  Claude: Good call. What were you trying to do? I can suggest a safer approach.
79
83
  ```
80
84
 
81
- **Never bypass safety checks without user permission.**
85
+ **Never bypass safety checks without user permission.** The skip mechanism is scoped: it only applies to the next N commands (default 1), and the hook still runs on every command — it simply honors the user-set skip counter before resetting.
82
86
 
83
87
  ---
84
88
 
@@ -416,7 +420,7 @@ Instead, let me [safer approach].
416
420
 
417
421
  ## 9. Read Tool Protection (v1.3)
418
422
 
419
- **Hardstop now monitors file reads to prevent secrets exposure.**
423
+ **Hardstop monitors file reads to prevent secrets exposure.** Note: Hardstop **blocks** reads of these paths — it does not read or access their contents.
420
424
 
421
425
  ### DANGEROUS Reads (Blocked)
422
426