@melihmucuk/leash 1.0.6 → 1.0.8
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/dist/claude-code/leash.js +52 -8
- package/dist/factory/leash.js +52 -8
- package/dist/opencode/leash.js +52 -8
- package/dist/pi/leash.js +61 -19
- package/package.json +3 -3
|
@@ -143,6 +143,27 @@ var CommandAnalyzer = class {
|
|
|
143
143
|
const expanded = this.pathValidator.expand(path);
|
|
144
144
|
return resolveBase ? resolve2(resolveBase, expanded) : expanded;
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Strip heredoc content from command before analyzing redirects.
|
|
148
|
+
* Handles: <<EOF, <<'EOF', <<"EOF", <<-EOF
|
|
149
|
+
*/
|
|
150
|
+
stripHeredocs(command) {
|
|
151
|
+
const heredocStart = /<<-?\s*(['"]?)(\w+)\1/g;
|
|
152
|
+
let result = command;
|
|
153
|
+
let match;
|
|
154
|
+
while ((match = heredocStart.exec(command)) !== null) {
|
|
155
|
+
const delimiter = match[2];
|
|
156
|
+
const endPattern = new RegExp(`\\n\\t*${delimiter}\\s*(?:\\n|$)`);
|
|
157
|
+
const startIndex = match.index;
|
|
158
|
+
const contentAfterStart = command.slice(match.index + match[0].length);
|
|
159
|
+
const endMatch = endPattern.exec(contentAfterStart);
|
|
160
|
+
if (endMatch) {
|
|
161
|
+
const endIndex = match.index + match[0].length + endMatch.index + endMatch[0].length;
|
|
162
|
+
result = result.slice(0, startIndex) + result.slice(endIndex);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
146
167
|
isPathAllowed(path, allowDevicePaths, resolveBase) {
|
|
147
168
|
const resolved = this.resolvePath(path, resolveBase);
|
|
148
169
|
if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
|
|
@@ -269,7 +290,8 @@ var CommandAnalyzer = class {
|
|
|
269
290
|
return commands;
|
|
270
291
|
}
|
|
271
292
|
checkRedirects(command) {
|
|
272
|
-
const
|
|
293
|
+
const strippedCommand = this.stripHeredocs(command);
|
|
294
|
+
const matches = strippedCommand.matchAll(REDIRECT_PATTERN);
|
|
273
295
|
for (const match of matches) {
|
|
274
296
|
const path = match[1] || match[2] || match[3];
|
|
275
297
|
if (!path || path.startsWith("&")) {
|
|
@@ -325,7 +347,11 @@ var CommandAnalyzer = class {
|
|
|
325
347
|
reason: `Command "${name}" targets path outside working directory: ${path}`
|
|
326
348
|
};
|
|
327
349
|
}
|
|
328
|
-
const result = this.checkProtectedPath(
|
|
350
|
+
const result = this.checkProtectedPath(
|
|
351
|
+
path,
|
|
352
|
+
`Command "${name}"`,
|
|
353
|
+
resolveBase
|
|
354
|
+
);
|
|
329
355
|
if (result.blocked) return result;
|
|
330
356
|
}
|
|
331
357
|
}
|
|
@@ -337,7 +363,10 @@ var CommandAnalyzer = class {
|
|
|
337
363
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
338
364
|
return {
|
|
339
365
|
blocked: true,
|
|
340
|
-
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
366
|
+
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
367
|
+
dest,
|
|
368
|
+
resolveBase
|
|
369
|
+
)}`
|
|
341
370
|
};
|
|
342
371
|
}
|
|
343
372
|
return this.checkProtectedPath(dest, 'Command "cp"', resolveBase);
|
|
@@ -349,7 +378,10 @@ var CommandAnalyzer = class {
|
|
|
349
378
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
350
379
|
return {
|
|
351
380
|
blocked: true,
|
|
352
|
-
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
381
|
+
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
382
|
+
dest,
|
|
383
|
+
resolveBase
|
|
384
|
+
)}`
|
|
353
385
|
};
|
|
354
386
|
}
|
|
355
387
|
return this.checkProtectedPath(dest, 'Command "dd"', resolveBase);
|
|
@@ -361,7 +393,10 @@ var CommandAnalyzer = class {
|
|
|
361
393
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
362
394
|
return {
|
|
363
395
|
blocked: true,
|
|
364
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
396
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
397
|
+
dest,
|
|
398
|
+
resolveBase
|
|
399
|
+
)}`
|
|
365
400
|
};
|
|
366
401
|
}
|
|
367
402
|
const destResult = this.checkProtectedPath(
|
|
@@ -374,7 +409,10 @@ var CommandAnalyzer = class {
|
|
|
374
409
|
if (!this.isPathAllowed(source, false, resolveBase)) {
|
|
375
410
|
return {
|
|
376
411
|
blocked: true,
|
|
377
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
412
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
413
|
+
source,
|
|
414
|
+
resolveBase
|
|
415
|
+
)}`
|
|
378
416
|
};
|
|
379
417
|
}
|
|
380
418
|
const sourceResult = this.checkProtectedPath(
|
|
@@ -391,7 +429,10 @@ var CommandAnalyzer = class {
|
|
|
391
429
|
if (!this.isPathAllowed(path, false, resolveBase)) {
|
|
392
430
|
return {
|
|
393
431
|
blocked: true,
|
|
394
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
432
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
433
|
+
path,
|
|
434
|
+
resolveBase
|
|
435
|
+
)}`
|
|
395
436
|
};
|
|
396
437
|
}
|
|
397
438
|
const result = this.checkProtectedPath(
|
|
@@ -409,7 +450,10 @@ var CommandAnalyzer = class {
|
|
|
409
450
|
if (!this.isPathAllowed(path, allowDevicePaths, resolveBase)) {
|
|
410
451
|
return {
|
|
411
452
|
blocked: true,
|
|
412
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
453
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
454
|
+
path,
|
|
455
|
+
resolveBase
|
|
456
|
+
)}`
|
|
413
457
|
};
|
|
414
458
|
}
|
|
415
459
|
const result = this.checkProtectedPath(
|
package/dist/factory/leash.js
CHANGED
|
@@ -143,6 +143,27 @@ var CommandAnalyzer = class {
|
|
|
143
143
|
const expanded = this.pathValidator.expand(path);
|
|
144
144
|
return resolveBase ? resolve2(resolveBase, expanded) : expanded;
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Strip heredoc content from command before analyzing redirects.
|
|
148
|
+
* Handles: <<EOF, <<'EOF', <<"EOF", <<-EOF
|
|
149
|
+
*/
|
|
150
|
+
stripHeredocs(command) {
|
|
151
|
+
const heredocStart = /<<-?\s*(['"]?)(\w+)\1/g;
|
|
152
|
+
let result = command;
|
|
153
|
+
let match;
|
|
154
|
+
while ((match = heredocStart.exec(command)) !== null) {
|
|
155
|
+
const delimiter = match[2];
|
|
156
|
+
const endPattern = new RegExp(`\\n\\t*${delimiter}\\s*(?:\\n|$)`);
|
|
157
|
+
const startIndex = match.index;
|
|
158
|
+
const contentAfterStart = command.slice(match.index + match[0].length);
|
|
159
|
+
const endMatch = endPattern.exec(contentAfterStart);
|
|
160
|
+
if (endMatch) {
|
|
161
|
+
const endIndex = match.index + match[0].length + endMatch.index + endMatch[0].length;
|
|
162
|
+
result = result.slice(0, startIndex) + result.slice(endIndex);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
146
167
|
isPathAllowed(path, allowDevicePaths, resolveBase) {
|
|
147
168
|
const resolved = this.resolvePath(path, resolveBase);
|
|
148
169
|
if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
|
|
@@ -269,7 +290,8 @@ var CommandAnalyzer = class {
|
|
|
269
290
|
return commands;
|
|
270
291
|
}
|
|
271
292
|
checkRedirects(command) {
|
|
272
|
-
const
|
|
293
|
+
const strippedCommand = this.stripHeredocs(command);
|
|
294
|
+
const matches = strippedCommand.matchAll(REDIRECT_PATTERN);
|
|
273
295
|
for (const match of matches) {
|
|
274
296
|
const path = match[1] || match[2] || match[3];
|
|
275
297
|
if (!path || path.startsWith("&")) {
|
|
@@ -325,7 +347,11 @@ var CommandAnalyzer = class {
|
|
|
325
347
|
reason: `Command "${name}" targets path outside working directory: ${path}`
|
|
326
348
|
};
|
|
327
349
|
}
|
|
328
|
-
const result = this.checkProtectedPath(
|
|
350
|
+
const result = this.checkProtectedPath(
|
|
351
|
+
path,
|
|
352
|
+
`Command "${name}"`,
|
|
353
|
+
resolveBase
|
|
354
|
+
);
|
|
329
355
|
if (result.blocked) return result;
|
|
330
356
|
}
|
|
331
357
|
}
|
|
@@ -337,7 +363,10 @@ var CommandAnalyzer = class {
|
|
|
337
363
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
338
364
|
return {
|
|
339
365
|
blocked: true,
|
|
340
|
-
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
366
|
+
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
367
|
+
dest,
|
|
368
|
+
resolveBase
|
|
369
|
+
)}`
|
|
341
370
|
};
|
|
342
371
|
}
|
|
343
372
|
return this.checkProtectedPath(dest, 'Command "cp"', resolveBase);
|
|
@@ -349,7 +378,10 @@ var CommandAnalyzer = class {
|
|
|
349
378
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
350
379
|
return {
|
|
351
380
|
blocked: true,
|
|
352
|
-
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
381
|
+
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
382
|
+
dest,
|
|
383
|
+
resolveBase
|
|
384
|
+
)}`
|
|
353
385
|
};
|
|
354
386
|
}
|
|
355
387
|
return this.checkProtectedPath(dest, 'Command "dd"', resolveBase);
|
|
@@ -361,7 +393,10 @@ var CommandAnalyzer = class {
|
|
|
361
393
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
362
394
|
return {
|
|
363
395
|
blocked: true,
|
|
364
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
396
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
397
|
+
dest,
|
|
398
|
+
resolveBase
|
|
399
|
+
)}`
|
|
365
400
|
};
|
|
366
401
|
}
|
|
367
402
|
const destResult = this.checkProtectedPath(
|
|
@@ -374,7 +409,10 @@ var CommandAnalyzer = class {
|
|
|
374
409
|
if (!this.isPathAllowed(source, false, resolveBase)) {
|
|
375
410
|
return {
|
|
376
411
|
blocked: true,
|
|
377
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
412
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
413
|
+
source,
|
|
414
|
+
resolveBase
|
|
415
|
+
)}`
|
|
378
416
|
};
|
|
379
417
|
}
|
|
380
418
|
const sourceResult = this.checkProtectedPath(
|
|
@@ -391,7 +429,10 @@ var CommandAnalyzer = class {
|
|
|
391
429
|
if (!this.isPathAllowed(path, false, resolveBase)) {
|
|
392
430
|
return {
|
|
393
431
|
blocked: true,
|
|
394
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
432
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
433
|
+
path,
|
|
434
|
+
resolveBase
|
|
435
|
+
)}`
|
|
395
436
|
};
|
|
396
437
|
}
|
|
397
438
|
const result = this.checkProtectedPath(
|
|
@@ -409,7 +450,10 @@ var CommandAnalyzer = class {
|
|
|
409
450
|
if (!this.isPathAllowed(path, allowDevicePaths, resolveBase)) {
|
|
410
451
|
return {
|
|
411
452
|
blocked: true,
|
|
412
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
453
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
454
|
+
path,
|
|
455
|
+
resolveBase
|
|
456
|
+
)}`
|
|
413
457
|
};
|
|
414
458
|
}
|
|
415
459
|
const result = this.checkProtectedPath(
|
package/dist/opencode/leash.js
CHANGED
|
@@ -141,6 +141,27 @@ var CommandAnalyzer = class {
|
|
|
141
141
|
const expanded = this.pathValidator.expand(path);
|
|
142
142
|
return resolveBase ? resolve2(resolveBase, expanded) : expanded;
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Strip heredoc content from command before analyzing redirects.
|
|
146
|
+
* Handles: <<EOF, <<'EOF', <<"EOF", <<-EOF
|
|
147
|
+
*/
|
|
148
|
+
stripHeredocs(command) {
|
|
149
|
+
const heredocStart = /<<-?\s*(['"]?)(\w+)\1/g;
|
|
150
|
+
let result = command;
|
|
151
|
+
let match;
|
|
152
|
+
while ((match = heredocStart.exec(command)) !== null) {
|
|
153
|
+
const delimiter = match[2];
|
|
154
|
+
const endPattern = new RegExp(`\\n\\t*${delimiter}\\s*(?:\\n|$)`);
|
|
155
|
+
const startIndex = match.index;
|
|
156
|
+
const contentAfterStart = command.slice(match.index + match[0].length);
|
|
157
|
+
const endMatch = endPattern.exec(contentAfterStart);
|
|
158
|
+
if (endMatch) {
|
|
159
|
+
const endIndex = match.index + match[0].length + endMatch.index + endMatch[0].length;
|
|
160
|
+
result = result.slice(0, startIndex) + result.slice(endIndex);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
144
165
|
isPathAllowed(path, allowDevicePaths, resolveBase) {
|
|
145
166
|
const resolved = this.resolvePath(path, resolveBase);
|
|
146
167
|
if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
|
|
@@ -267,7 +288,8 @@ var CommandAnalyzer = class {
|
|
|
267
288
|
return commands;
|
|
268
289
|
}
|
|
269
290
|
checkRedirects(command) {
|
|
270
|
-
const
|
|
291
|
+
const strippedCommand = this.stripHeredocs(command);
|
|
292
|
+
const matches = strippedCommand.matchAll(REDIRECT_PATTERN);
|
|
271
293
|
for (const match of matches) {
|
|
272
294
|
const path = match[1] || match[2] || match[3];
|
|
273
295
|
if (!path || path.startsWith("&")) {
|
|
@@ -323,7 +345,11 @@ var CommandAnalyzer = class {
|
|
|
323
345
|
reason: `Command "${name}" targets path outside working directory: ${path}`
|
|
324
346
|
};
|
|
325
347
|
}
|
|
326
|
-
const result = this.checkProtectedPath(
|
|
348
|
+
const result = this.checkProtectedPath(
|
|
349
|
+
path,
|
|
350
|
+
`Command "${name}"`,
|
|
351
|
+
resolveBase
|
|
352
|
+
);
|
|
327
353
|
if (result.blocked) return result;
|
|
328
354
|
}
|
|
329
355
|
}
|
|
@@ -335,7 +361,10 @@ var CommandAnalyzer = class {
|
|
|
335
361
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
336
362
|
return {
|
|
337
363
|
blocked: true,
|
|
338
|
-
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
364
|
+
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
365
|
+
dest,
|
|
366
|
+
resolveBase
|
|
367
|
+
)}`
|
|
339
368
|
};
|
|
340
369
|
}
|
|
341
370
|
return this.checkProtectedPath(dest, 'Command "cp"', resolveBase);
|
|
@@ -347,7 +376,10 @@ var CommandAnalyzer = class {
|
|
|
347
376
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
348
377
|
return {
|
|
349
378
|
blocked: true,
|
|
350
|
-
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
379
|
+
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
380
|
+
dest,
|
|
381
|
+
resolveBase
|
|
382
|
+
)}`
|
|
351
383
|
};
|
|
352
384
|
}
|
|
353
385
|
return this.checkProtectedPath(dest, 'Command "dd"', resolveBase);
|
|
@@ -359,7 +391,10 @@ var CommandAnalyzer = class {
|
|
|
359
391
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
360
392
|
return {
|
|
361
393
|
blocked: true,
|
|
362
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
394
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
395
|
+
dest,
|
|
396
|
+
resolveBase
|
|
397
|
+
)}`
|
|
363
398
|
};
|
|
364
399
|
}
|
|
365
400
|
const destResult = this.checkProtectedPath(
|
|
@@ -372,7 +407,10 @@ var CommandAnalyzer = class {
|
|
|
372
407
|
if (!this.isPathAllowed(source, false, resolveBase)) {
|
|
373
408
|
return {
|
|
374
409
|
blocked: true,
|
|
375
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
410
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
411
|
+
source,
|
|
412
|
+
resolveBase
|
|
413
|
+
)}`
|
|
376
414
|
};
|
|
377
415
|
}
|
|
378
416
|
const sourceResult = this.checkProtectedPath(
|
|
@@ -389,7 +427,10 @@ var CommandAnalyzer = class {
|
|
|
389
427
|
if (!this.isPathAllowed(path, false, resolveBase)) {
|
|
390
428
|
return {
|
|
391
429
|
blocked: true,
|
|
392
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
430
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
431
|
+
path,
|
|
432
|
+
resolveBase
|
|
433
|
+
)}`
|
|
393
434
|
};
|
|
394
435
|
}
|
|
395
436
|
const result = this.checkProtectedPath(
|
|
@@ -407,7 +448,10 @@ var CommandAnalyzer = class {
|
|
|
407
448
|
if (!this.isPathAllowed(path, allowDevicePaths, resolveBase)) {
|
|
408
449
|
return {
|
|
409
450
|
blocked: true,
|
|
410
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
451
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
452
|
+
path,
|
|
453
|
+
resolveBase
|
|
454
|
+
)}`
|
|
411
455
|
};
|
|
412
456
|
}
|
|
413
457
|
const result = this.checkProtectedPath(
|
package/dist/pi/leash.js
CHANGED
|
@@ -141,6 +141,27 @@ var CommandAnalyzer = class {
|
|
|
141
141
|
const expanded = this.pathValidator.expand(path);
|
|
142
142
|
return resolveBase ? resolve2(resolveBase, expanded) : expanded;
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Strip heredoc content from command before analyzing redirects.
|
|
146
|
+
* Handles: <<EOF, <<'EOF', <<"EOF", <<-EOF
|
|
147
|
+
*/
|
|
148
|
+
stripHeredocs(command) {
|
|
149
|
+
const heredocStart = /<<-?\s*(['"]?)(\w+)\1/g;
|
|
150
|
+
let result = command;
|
|
151
|
+
let match;
|
|
152
|
+
while ((match = heredocStart.exec(command)) !== null) {
|
|
153
|
+
const delimiter = match[2];
|
|
154
|
+
const endPattern = new RegExp(`\\n\\t*${delimiter}\\s*(?:\\n|$)`);
|
|
155
|
+
const startIndex = match.index;
|
|
156
|
+
const contentAfterStart = command.slice(match.index + match[0].length);
|
|
157
|
+
const endMatch = endPattern.exec(contentAfterStart);
|
|
158
|
+
if (endMatch) {
|
|
159
|
+
const endIndex = match.index + match[0].length + endMatch.index + endMatch[0].length;
|
|
160
|
+
result = result.slice(0, startIndex) + result.slice(endIndex);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
144
165
|
isPathAllowed(path, allowDevicePaths, resolveBase) {
|
|
145
166
|
const resolved = this.resolvePath(path, resolveBase);
|
|
146
167
|
if (this.pathValidator.isWithinWorkingDir(resolved)) return true;
|
|
@@ -267,7 +288,8 @@ var CommandAnalyzer = class {
|
|
|
267
288
|
return commands;
|
|
268
289
|
}
|
|
269
290
|
checkRedirects(command) {
|
|
270
|
-
const
|
|
291
|
+
const strippedCommand = this.stripHeredocs(command);
|
|
292
|
+
const matches = strippedCommand.matchAll(REDIRECT_PATTERN);
|
|
271
293
|
for (const match of matches) {
|
|
272
294
|
const path = match[1] || match[2] || match[3];
|
|
273
295
|
if (!path || path.startsWith("&")) {
|
|
@@ -323,7 +345,11 @@ var CommandAnalyzer = class {
|
|
|
323
345
|
reason: `Command "${name}" targets path outside working directory: ${path}`
|
|
324
346
|
};
|
|
325
347
|
}
|
|
326
|
-
const result = this.checkProtectedPath(
|
|
348
|
+
const result = this.checkProtectedPath(
|
|
349
|
+
path,
|
|
350
|
+
`Command "${name}"`,
|
|
351
|
+
resolveBase
|
|
352
|
+
);
|
|
327
353
|
if (result.blocked) return result;
|
|
328
354
|
}
|
|
329
355
|
}
|
|
@@ -335,7 +361,10 @@ var CommandAnalyzer = class {
|
|
|
335
361
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
336
362
|
return {
|
|
337
363
|
blocked: true,
|
|
338
|
-
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
364
|
+
reason: `Command "cp" targets path outside working directory: ${this.resolvePath(
|
|
365
|
+
dest,
|
|
366
|
+
resolveBase
|
|
367
|
+
)}`
|
|
339
368
|
};
|
|
340
369
|
}
|
|
341
370
|
return this.checkProtectedPath(dest, 'Command "cp"', resolveBase);
|
|
@@ -347,7 +376,10 @@ var CommandAnalyzer = class {
|
|
|
347
376
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
348
377
|
return {
|
|
349
378
|
blocked: true,
|
|
350
|
-
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
379
|
+
reason: `Command "dd" targets path outside working directory: ${this.resolvePath(
|
|
380
|
+
dest,
|
|
381
|
+
resolveBase
|
|
382
|
+
)}`
|
|
351
383
|
};
|
|
352
384
|
}
|
|
353
385
|
return this.checkProtectedPath(dest, 'Command "dd"', resolveBase);
|
|
@@ -359,7 +391,10 @@ var CommandAnalyzer = class {
|
|
|
359
391
|
if (!this.isPathAllowed(dest, true, resolveBase)) {
|
|
360
392
|
return {
|
|
361
393
|
blocked: true,
|
|
362
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
394
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
395
|
+
dest,
|
|
396
|
+
resolveBase
|
|
397
|
+
)}`
|
|
363
398
|
};
|
|
364
399
|
}
|
|
365
400
|
const destResult = this.checkProtectedPath(
|
|
@@ -372,7 +407,10 @@ var CommandAnalyzer = class {
|
|
|
372
407
|
if (!this.isPathAllowed(source, false, resolveBase)) {
|
|
373
408
|
return {
|
|
374
409
|
blocked: true,
|
|
375
|
-
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
410
|
+
reason: `Command "mv" targets path outside working directory: ${this.resolvePath(
|
|
411
|
+
source,
|
|
412
|
+
resolveBase
|
|
413
|
+
)}`
|
|
376
414
|
};
|
|
377
415
|
}
|
|
378
416
|
const sourceResult = this.checkProtectedPath(
|
|
@@ -389,7 +427,10 @@ var CommandAnalyzer = class {
|
|
|
389
427
|
if (!this.isPathAllowed(path, false, resolveBase)) {
|
|
390
428
|
return {
|
|
391
429
|
blocked: true,
|
|
392
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
430
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
431
|
+
path,
|
|
432
|
+
resolveBase
|
|
433
|
+
)}`
|
|
393
434
|
};
|
|
394
435
|
}
|
|
395
436
|
const result = this.checkProtectedPath(
|
|
@@ -407,7 +448,10 @@ var CommandAnalyzer = class {
|
|
|
407
448
|
if (!this.isPathAllowed(path, allowDevicePaths, resolveBase)) {
|
|
408
449
|
return {
|
|
409
450
|
blocked: true,
|
|
410
|
-
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
451
|
+
reason: `Command "${baseCmd}" targets path outside working directory: ${this.resolvePath(
|
|
452
|
+
path,
|
|
453
|
+
resolveBase
|
|
454
|
+
)}`
|
|
411
455
|
};
|
|
412
456
|
}
|
|
413
457
|
const result = this.checkProtectedPath(
|
|
@@ -522,17 +566,15 @@ async function checkForUpdates() {
|
|
|
522
566
|
// packages/pi/leash.ts
|
|
523
567
|
function leash_default(pi) {
|
|
524
568
|
let analyzer = null;
|
|
525
|
-
pi.on("
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
);
|
|
535
|
-
}
|
|
569
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
570
|
+
analyzer = new CommandAnalyzer(ctx.cwd);
|
|
571
|
+
ctx.ui.notify("\u{1F512} Leash active", "info");
|
|
572
|
+
const update = await checkForUpdates();
|
|
573
|
+
if (update.hasUpdate) {
|
|
574
|
+
ctx.ui.notify(
|
|
575
|
+
`\u{1F504} Leash ${update.latestVersion} available. Run: leash --update (restart required)`,
|
|
576
|
+
"warning"
|
|
577
|
+
);
|
|
536
578
|
}
|
|
537
579
|
});
|
|
538
580
|
pi.on("tool_call", async (event, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melihmucuk/leash",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Security guardrails for AI coding agents",
|
|
6
6
|
"bin": {
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"build:factory": "esbuild packages/factory/leash.ts --bundle --outfile=dist/factory/leash.js --platform=node --format=esm"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
53
|
-
"@opencode-ai/plugin": "^1.0.
|
|
52
|
+
"@mariozechner/pi-coding-agent": "^0.31.0",
|
|
53
|
+
"@opencode-ai/plugin": "^1.0.224",
|
|
54
54
|
"@types/node": "^22.19.3",
|
|
55
55
|
"esbuild": "^0.27.2",
|
|
56
56
|
"typescript": "^5.9.3"
|