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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/index.ts +23 -18
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -35,3 +35,6 @@ Use `/sandbox` inside Pi to show the active config.
35
35
 
36
36
  `pi-landstrip` is licensed under `MIT`. See [LICENSE](LICENSE) for more
37
37
  information.
38
+
39
+ The bundled `@jarkkojs/landstrip` package is licensed under
40
+ `Apache-2.0 AND LGPL-2.1-or-later`.
package/index.ts CHANGED
@@ -80,7 +80,7 @@ interface LandstripErrorResponse {
80
80
  source: string;
81
81
  }
82
82
 
83
- const LANDSTRIP_VERSION = [0, 11, 3] as const;
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 normalizeBlockedPath(match[1], cwd);
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 normalizeBlockedPath(match[1], cwd);
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 = canonicalizePath(candidate);
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.3 or newer is required; found: ${version}`, 'error');
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.5.8",
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.3"
34
+ "@jarkkojs/landstrip": "^0.11.5"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@earendil-works/pi-coding-agent": "^0.78.0",