@melihmucuk/leash 1.0.5 → 1.0.6

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.
package/README.md CHANGED
@@ -16,6 +16,16 @@ AI agents can hallucinate dangerous commands. Leash sandboxes them:
16
16
 
17
17
  ![Claude Code](assets/claude-code.png)
18
18
 
19
+ ## Example horror stories
20
+
21
+ <img height="400" alt="image" src="https://github.com/user-attachments/assets/db503024-94ca-4443-b80e-b63fbc740367" />
22
+
23
+ <img height="400" alt="image" src="https://github.com/user-attachments/assets/94f0a4e5-db6c-4b14-bddd-b8984c51ed3d" />
24
+
25
+ Links:
26
+ 1. [Claude CLI deleted my entire home directory (Dec 8th 2025)](https://www.reddit.com/r/ClaudeAI/comments/1pgxckk/claude_cli_deleted_my_entire_home_directory_wiped/)
27
+ 2. [Google Antigravity just deleted my drive (Nov 27th 2025)](https://www.reddit.com/r/google_antigravity/comments/1p82or6/google_antigravity_just_deleted_the_contents_of/)
28
+
19
29
  ## Quick Start
20
30
 
21
31
  ```bash
@@ -164,6 +174,8 @@ rm -rf /tmp/build-cache # ✅ Temp directory
164
174
  rm .env.example # ✅ Example files allowed
165
175
  git commit -m "message" # ✅ Safe git commands
166
176
  git push origin main # ✅ Normal push (no --force)
177
+ echo "plan" > ~/.claude/plans/x # ✅ Platform config directories
178
+ rm ~/.pi/agent/old.md # ✅ Platform config directories
167
179
  ```
168
180
 
169
181
  <details>
@@ -277,6 +289,14 @@ rm -rf /tmp/build-cache
277
289
  echo "data" > /tmp/output.txt
278
290
  rsync -av --delete ./src/ /tmp/backup/
279
291
 
292
+ # Platform config directories
293
+ rm ~/.claude/plans/old-plan.md
294
+ echo "config" > ~/.factory/cache.json
295
+ rm ~/.pi/agent/temp.md
296
+ rm ~/.config/opencode/cache.json
297
+ find ~/.claude -name '*.tmp' -delete
298
+ rsync -av --delete ./src/ ~/.pi/backup/
299
+
280
300
  # Device paths
281
301
  echo "x" > /dev/null
282
302
  truncate -s 0 /dev/null
@@ -32,6 +32,12 @@ var DANGEROUS_PATTERNS = [
32
32
  ];
33
33
  var REDIRECT_PATTERN = />{1,2}\s*(?:"([^"]+)"|'([^']+)'|([^\s;|&>]+))/g;
34
34
  var DEVICE_PATHS = ["/dev/null", "/dev/stdin", "/dev/stdout", "/dev/stderr"];
35
+ var PLATFORM_PATHS = [
36
+ ".claude",
37
+ ".factory",
38
+ ".pi",
39
+ ".config/opencode"
40
+ ];
35
41
  var TEMP_PATHS = [
36
42
  "/tmp",
37
43
  "/var/tmp",
@@ -103,6 +109,12 @@ var PathValidator = class {
103
109
  const resolved = this.resolveReal(path);
104
110
  return this.matchesAny(resolved, TEMP_PATHS);
105
111
  }
112
+ isPlatformPath(path) {
113
+ const resolved = this.resolveReal(path);
114
+ const home = homedir();
115
+ const platformPaths = PLATFORM_PATHS.map((p) => `${home}/${p}`);
116
+ return this.matchesAny(resolved, platformPaths);
117
+ }
106
118
  isProtectedPath(path) {
107
119
  if (!this.isWithinWorkingDir(path)) {
108
120
  return { protected: false };
@@ -134,6 +146,7 @@ var CommandAnalyzer = class {
134
146
  isPathAllowed(path, allowDevicePaths, resolveBase) {
135
147
  const resolved = this.resolvePath(path, resolveBase);
136
148
  if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
149
+ if (this.pathValidator.isPlatformPath(resolved)) return true;
137
150
  return allowDevicePaths ? this.pathValidator.isSafeForWrite(resolved) : this.pathValidator.isTempPath(resolved);
138
151
  }
139
152
  checkProtectedPath(path, context, resolveBase) {
@@ -262,7 +275,7 @@ var CommandAnalyzer = class {
262
275
  if (!path || path.startsWith("&")) {
263
276
  continue;
264
277
  }
265
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
278
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
266
279
  return {
267
280
  blocked: true,
268
281
  reason: `Redirect to path outside working directory: ${path}`
@@ -306,7 +319,7 @@ var CommandAnalyzer = class {
306
319
  const paths = this.extractPaths(command);
307
320
  for (const path of paths) {
308
321
  const resolved = this.resolvePath(path, resolveBase);
309
- if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved)) {
322
+ if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved) && !this.pathValidator.isPlatformPath(resolved)) {
310
323
  return {
311
324
  blocked: true,
312
325
  reason: `Command "${name}" targets path outside working directory: ${path}`
@@ -456,7 +469,7 @@ var CommandAnalyzer = class {
456
469
  }
457
470
  validatePath(path) {
458
471
  if (!path) return { blocked: false };
459
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
472
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
460
473
  return {
461
474
  blocked: true,
462
475
  reason: `File operation targets path outside working directory: ${path}`
@@ -32,6 +32,12 @@ var DANGEROUS_PATTERNS = [
32
32
  ];
33
33
  var REDIRECT_PATTERN = />{1,2}\s*(?:"([^"]+)"|'([^']+)'|([^\s;|&>]+))/g;
34
34
  var DEVICE_PATHS = ["/dev/null", "/dev/stdin", "/dev/stdout", "/dev/stderr"];
35
+ var PLATFORM_PATHS = [
36
+ ".claude",
37
+ ".factory",
38
+ ".pi",
39
+ ".config/opencode"
40
+ ];
35
41
  var TEMP_PATHS = [
36
42
  "/tmp",
37
43
  "/var/tmp",
@@ -103,6 +109,12 @@ var PathValidator = class {
103
109
  const resolved = this.resolveReal(path);
104
110
  return this.matchesAny(resolved, TEMP_PATHS);
105
111
  }
112
+ isPlatformPath(path) {
113
+ const resolved = this.resolveReal(path);
114
+ const home = homedir();
115
+ const platformPaths = PLATFORM_PATHS.map((p) => `${home}/${p}`);
116
+ return this.matchesAny(resolved, platformPaths);
117
+ }
106
118
  isProtectedPath(path) {
107
119
  if (!this.isWithinWorkingDir(path)) {
108
120
  return { protected: false };
@@ -134,6 +146,7 @@ var CommandAnalyzer = class {
134
146
  isPathAllowed(path, allowDevicePaths, resolveBase) {
135
147
  const resolved = this.resolvePath(path, resolveBase);
136
148
  if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
149
+ if (this.pathValidator.isPlatformPath(resolved)) return true;
137
150
  return allowDevicePaths ? this.pathValidator.isSafeForWrite(resolved) : this.pathValidator.isTempPath(resolved);
138
151
  }
139
152
  checkProtectedPath(path, context, resolveBase) {
@@ -262,7 +275,7 @@ var CommandAnalyzer = class {
262
275
  if (!path || path.startsWith("&")) {
263
276
  continue;
264
277
  }
265
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
278
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
266
279
  return {
267
280
  blocked: true,
268
281
  reason: `Redirect to path outside working directory: ${path}`
@@ -306,7 +319,7 @@ var CommandAnalyzer = class {
306
319
  const paths = this.extractPaths(command);
307
320
  for (const path of paths) {
308
321
  const resolved = this.resolvePath(path, resolveBase);
309
- if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved)) {
322
+ if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved) && !this.pathValidator.isPlatformPath(resolved)) {
310
323
  return {
311
324
  blocked: true,
312
325
  reason: `Command "${name}" targets path outside working directory: ${path}`
@@ -456,7 +469,7 @@ var CommandAnalyzer = class {
456
469
  }
457
470
  validatePath(path) {
458
471
  if (!path) return { blocked: false };
459
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
472
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
460
473
  return {
461
474
  blocked: true,
462
475
  reason: `File operation targets path outside working directory: ${path}`
@@ -30,6 +30,12 @@ var DANGEROUS_PATTERNS = [
30
30
  ];
31
31
  var REDIRECT_PATTERN = />{1,2}\s*(?:"([^"]+)"|'([^']+)'|([^\s;|&>]+))/g;
32
32
  var DEVICE_PATHS = ["/dev/null", "/dev/stdin", "/dev/stdout", "/dev/stderr"];
33
+ var PLATFORM_PATHS = [
34
+ ".claude",
35
+ ".factory",
36
+ ".pi",
37
+ ".config/opencode"
38
+ ];
33
39
  var TEMP_PATHS = [
34
40
  "/tmp",
35
41
  "/var/tmp",
@@ -101,6 +107,12 @@ var PathValidator = class {
101
107
  const resolved = this.resolveReal(path);
102
108
  return this.matchesAny(resolved, TEMP_PATHS);
103
109
  }
110
+ isPlatformPath(path) {
111
+ const resolved = this.resolveReal(path);
112
+ const home = homedir();
113
+ const platformPaths = PLATFORM_PATHS.map((p) => `${home}/${p}`);
114
+ return this.matchesAny(resolved, platformPaths);
115
+ }
104
116
  isProtectedPath(path) {
105
117
  if (!this.isWithinWorkingDir(path)) {
106
118
  return { protected: false };
@@ -132,6 +144,7 @@ var CommandAnalyzer = class {
132
144
  isPathAllowed(path, allowDevicePaths, resolveBase) {
133
145
  const resolved = this.resolvePath(path, resolveBase);
134
146
  if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
147
+ if (this.pathValidator.isPlatformPath(resolved)) return true;
135
148
  return allowDevicePaths ? this.pathValidator.isSafeForWrite(resolved) : this.pathValidator.isTempPath(resolved);
136
149
  }
137
150
  checkProtectedPath(path, context, resolveBase) {
@@ -260,7 +273,7 @@ var CommandAnalyzer = class {
260
273
  if (!path || path.startsWith("&")) {
261
274
  continue;
262
275
  }
263
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
276
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
264
277
  return {
265
278
  blocked: true,
266
279
  reason: `Redirect to path outside working directory: ${path}`
@@ -304,7 +317,7 @@ var CommandAnalyzer = class {
304
317
  const paths = this.extractPaths(command);
305
318
  for (const path of paths) {
306
319
  const resolved = this.resolvePath(path, resolveBase);
307
- if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved)) {
320
+ if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved) && !this.pathValidator.isPlatformPath(resolved)) {
308
321
  return {
309
322
  blocked: true,
310
323
  reason: `Command "${name}" targets path outside working directory: ${path}`
@@ -454,7 +467,7 @@ var CommandAnalyzer = class {
454
467
  }
455
468
  validatePath(path) {
456
469
  if (!path) return { blocked: false };
457
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
470
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
458
471
  return {
459
472
  blocked: true,
460
473
  reason: `File operation targets path outside working directory: ${path}`
package/dist/pi/leash.js CHANGED
@@ -30,6 +30,12 @@ var DANGEROUS_PATTERNS = [
30
30
  ];
31
31
  var REDIRECT_PATTERN = />{1,2}\s*(?:"([^"]+)"|'([^']+)'|([^\s;|&>]+))/g;
32
32
  var DEVICE_PATHS = ["/dev/null", "/dev/stdin", "/dev/stdout", "/dev/stderr"];
33
+ var PLATFORM_PATHS = [
34
+ ".claude",
35
+ ".factory",
36
+ ".pi",
37
+ ".config/opencode"
38
+ ];
33
39
  var TEMP_PATHS = [
34
40
  "/tmp",
35
41
  "/var/tmp",
@@ -101,6 +107,12 @@ var PathValidator = class {
101
107
  const resolved = this.resolveReal(path);
102
108
  return this.matchesAny(resolved, TEMP_PATHS);
103
109
  }
110
+ isPlatformPath(path) {
111
+ const resolved = this.resolveReal(path);
112
+ const home = homedir();
113
+ const platformPaths = PLATFORM_PATHS.map((p) => `${home}/${p}`);
114
+ return this.matchesAny(resolved, platformPaths);
115
+ }
104
116
  isProtectedPath(path) {
105
117
  if (!this.isWithinWorkingDir(path)) {
106
118
  return { protected: false };
@@ -132,6 +144,7 @@ var CommandAnalyzer = class {
132
144
  isPathAllowed(path, allowDevicePaths, resolveBase) {
133
145
  const resolved = this.resolvePath(path, resolveBase);
134
146
  if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
147
+ if (this.pathValidator.isPlatformPath(resolved)) return true;
135
148
  return allowDevicePaths ? this.pathValidator.isSafeForWrite(resolved) : this.pathValidator.isTempPath(resolved);
136
149
  }
137
150
  checkProtectedPath(path, context, resolveBase) {
@@ -260,7 +273,7 @@ var CommandAnalyzer = class {
260
273
  if (!path || path.startsWith("&")) {
261
274
  continue;
262
275
  }
263
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
276
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
264
277
  return {
265
278
  blocked: true,
266
279
  reason: `Redirect to path outside working directory: ${path}`
@@ -304,7 +317,7 @@ var CommandAnalyzer = class {
304
317
  const paths = this.extractPaths(command);
305
318
  for (const path of paths) {
306
319
  const resolved = this.resolvePath(path, resolveBase);
307
- if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved)) {
320
+ if (!this.pathValidator.isWithinWorkingDir(resolved) && !this.pathValidator.isTempPath(resolved) && !this.pathValidator.isPlatformPath(resolved)) {
308
321
  return {
309
322
  blocked: true,
310
323
  reason: `Command "${name}" targets path outside working directory: ${path}`
@@ -454,7 +467,7 @@ var CommandAnalyzer = class {
454
467
  }
455
468
  validatePath(path) {
456
469
  if (!path) return { blocked: false };
457
- if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path)) {
470
+ if (!this.pathValidator.isSafeForWrite(path) && !this.pathValidator.isWithinWorkingDir(path) && !this.pathValidator.isPlatformPath(path)) {
458
471
  return {
459
472
  blocked: true,
460
473
  reason: `File operation targets path outside working directory: ${path}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melihmucuk/leash",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "Security guardrails for AI coding agents",
6
6
  "bin": {