pi-landstrip 0.5.8 → 0.11.1
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 +3 -0
- package/index.ts +23 -18
- package/package.json +2 -2
package/README.md
CHANGED
package/index.ts
CHANGED
|
@@ -80,7 +80,7 @@ interface LandstripErrorResponse {
|
|
|
80
80
|
source: string;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const LANDSTRIP_VERSION = [0, 11,
|
|
83
|
+
const LANDSTRIP_VERSION = [0, 11, 5] as const;
|
|
84
84
|
const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
|
|
85
85
|
|
|
86
86
|
const DEFAULT_CONFIG: SandboxConfig = {
|
|
@@ -226,9 +226,6 @@ function addReadPathToConfig(configPath: string, pathToAdd: string): void {
|
|
|
226
226
|
config.filesystem = {
|
|
227
227
|
...config.filesystem,
|
|
228
228
|
allowRead: [...existing, pathToAdd],
|
|
229
|
-
denyRead: config.filesystem?.denyRead ?? [],
|
|
230
|
-
allowWrite: config.filesystem?.allowWrite ?? [],
|
|
231
|
-
denyWrite: config.filesystem?.denyWrite ?? [],
|
|
232
229
|
} as SandboxFilesystemConfig;
|
|
233
230
|
writeConfigFile(configPath, config);
|
|
234
231
|
}
|
|
@@ -241,9 +238,6 @@ function addWritePathToConfig(configPath: string, pathToAdd: string): void {
|
|
|
241
238
|
config.filesystem = {
|
|
242
239
|
...config.filesystem,
|
|
243
240
|
allowWrite: [...existing, pathToAdd],
|
|
244
|
-
denyRead: config.filesystem?.denyRead ?? [],
|
|
245
|
-
allowRead: config.filesystem?.allowRead ?? [],
|
|
246
|
-
denyWrite: config.filesystem?.denyWrite ?? [],
|
|
247
241
|
} as SandboxFilesystemConfig;
|
|
248
242
|
writeConfigFile(configPath, config);
|
|
249
243
|
}
|
|
@@ -337,19 +331,30 @@ function normalizeBlockedPath(path: string, cwd: string): string {
|
|
|
337
331
|
return canonicalizePath(isAbsolute(path) ? path : join(cwd, path));
|
|
338
332
|
}
|
|
339
333
|
|
|
334
|
+
function isPathLike(value: string): boolean {
|
|
335
|
+
const trimmed = value.trim();
|
|
336
|
+
return (
|
|
337
|
+
trimmed === '~' ||
|
|
338
|
+
trimmed.startsWith('/') ||
|
|
339
|
+
trimmed.startsWith('~/') ||
|
|
340
|
+
trimmed.startsWith('./') ||
|
|
341
|
+
trimmed.startsWith('../') ||
|
|
342
|
+
trimmed.startsWith('.') ||
|
|
343
|
+
trimmed.includes('/')
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function normalizePathMatch(value: string, cwd: string): string | null {
|
|
348
|
+
return isPathLike(value) ? normalizeBlockedPath(value, cwd) : null;
|
|
349
|
+
}
|
|
350
|
+
|
|
340
351
|
function extractCandidatePaths(command: string): string[] {
|
|
341
352
|
const paths: string[] = [];
|
|
342
353
|
// Split on whitespace, preserving quoted strings minimally
|
|
343
354
|
const tokens = command.match(/[^\s"']+|"[^"]*"|'[^']*'/g) ?? [];
|
|
344
355
|
for (const token of tokens) {
|
|
345
356
|
const clean = token.replace(/^["']|["']$/g, '').replace(/[,;]$/, '');
|
|
346
|
-
if (
|
|
347
|
-
clean.startsWith('/') ||
|
|
348
|
-
clean.startsWith('~/') ||
|
|
349
|
-
clean === '~' ||
|
|
350
|
-
clean.startsWith('./') ||
|
|
351
|
-
clean.startsWith('../')
|
|
352
|
-
) {
|
|
357
|
+
if (isPathLike(clean)) {
|
|
353
358
|
paths.push(clean);
|
|
354
359
|
}
|
|
355
360
|
}
|
|
@@ -361,13 +366,13 @@ function extractBlockedPath(output: string, cwd: string, command?: string): stri
|
|
|
361
366
|
let match = output.match(
|
|
362
367
|
/(?:\/bin\/bash|bash|sh): (?:line \d+: )?([^:\n]+): (?:Operation not permitted|Permission denied)/,
|
|
363
368
|
);
|
|
364
|
-
if (match) return
|
|
369
|
+
if (match) return normalizePathMatch(match[1], cwd);
|
|
365
370
|
|
|
366
371
|
// ls/cat/cp: cannot open/access/stat '/path': Permission denied
|
|
367
372
|
match = output.match(
|
|
368
373
|
/^[a-zA-Z0-9_-]+: cannot (?:open|access|stat|create)(?: directory)? '?([^'\n]+?)'?(?: for (?:reading|writing))?: Permission denied$/m,
|
|
369
374
|
);
|
|
370
|
-
if (match) return
|
|
375
|
+
if (match) return normalizePathMatch(match[1], cwd);
|
|
371
376
|
|
|
372
377
|
// Generic: cmd: /absolute/path: Permission denied or Operation not permitted
|
|
373
378
|
match = output.match(
|
|
@@ -386,7 +391,7 @@ function extractBlockedPath(output: string, cwd: string, command?: string): stri
|
|
|
386
391
|
if (landstripErrors.length > 0 && command) {
|
|
387
392
|
const config = loadConfig(cwd);
|
|
388
393
|
for (const candidate of extractCandidatePaths(command)) {
|
|
389
|
-
const resolved =
|
|
394
|
+
const resolved = normalizeBlockedPath(candidate, cwd);
|
|
390
395
|
if (
|
|
391
396
|
matchesPattern(resolved, config.filesystem.denyRead) ||
|
|
392
397
|
!matchesPattern(resolved, config.filesystem.allowRead)
|
|
@@ -1251,7 +1256,7 @@ export function createLandstripIntegration(
|
|
|
1251
1256
|
if (!hasMinimumVersion(version, LANDSTRIP_VERSION)) {
|
|
1252
1257
|
sandboxEnabled = false;
|
|
1253
1258
|
sandboxReady = false;
|
|
1254
|
-
ctx.ui.notify(`landstrip 0.11.
|
|
1259
|
+
ctx.ui.notify(`landstrip 0.11.5 or newer is required; found: ${version}`, 'error');
|
|
1255
1260
|
return false;
|
|
1256
1261
|
}
|
|
1257
1262
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Landlock-based sandboxing for pi with interactive permission prompts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@earendil-works/pi-tui": "^0.78.0",
|
|
34
|
-
"@jarkkojs/landstrip": "^0.11.
|
|
34
|
+
"@jarkkojs/landstrip": "^0.11.5"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@earendil-works/pi-coding-agent": "^0.78.0",
|