pi-landstrip 0.16.24 → 0.16.26

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 (2) hide show
  1. package/index.ts +31 -2
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -416,6 +416,36 @@ export function sessionScopeFor(filePath: string, baseDirectory: string): string
416
416
  return dir;
417
417
  }
418
418
 
419
+ // Length of the longest entry in `patterns` that matches `path`, or -1 for no
420
+ // match. Canonicalized so the value reflects how specific the rule is.
421
+ function longestPrefixMatch(path: string, patterns: string[], cwd: string): number {
422
+ let best = -1;
423
+ for (const pattern of patterns) {
424
+ if (!matchesPattern(path, [pattern], cwd)) continue;
425
+ const canonical = pattern.includes('*')
426
+ ? expandPath(pattern, cwd)
427
+ : canonicalizePath(pattern, cwd);
428
+ if (canonical.length > best) best = canonical.length;
429
+ }
430
+ return best;
431
+ }
432
+
433
+ // Most-specific-match wins: a read is allowed when its longest matching
434
+ // allowRead entry is at least as specific as its longest matching denyRead
435
+ // entry. So an explicit allow (e.g. a granted `~/.cache`) overrides the broad
436
+ // `denyRead` gate (`/home`), while a narrow denyRead carve-out still beats a
437
+ // broad allow. Ties favor allow. denyWrite stays an absolute block elsewhere.
438
+ export function readAllowed(
439
+ path: string,
440
+ allowRead: string[],
441
+ denyRead: string[],
442
+ cwd: string,
443
+ ): boolean {
444
+ const allow = longestPrefixMatch(path, allowRead, cwd);
445
+ if (allow < 0) return false;
446
+ return allow >= longestPrefixMatch(path, denyRead, cwd);
447
+ }
448
+
419
449
  function isPathLike(value: string): boolean {
420
450
  const trimmed = value.trim();
421
451
  return (
@@ -1343,8 +1373,7 @@ export function createLandstripIntegration(
1343
1373
  const config = loadConfig(cwd);
1344
1374
  const isAllowed = (cfg: SandboxConfig): boolean =>
1345
1375
  operation === 'read'
1346
- ? !matchesPattern(path, cfg.filesystem.denyRead, cwd) &&
1347
- matchesPattern(path, getEffectiveAllowRead(cfg), cwd)
1376
+ ? readAllowed(path, getEffectiveAllowRead(cfg), cfg.filesystem.denyRead, cwd)
1348
1377
  : !matchesPattern(path, cfg.filesystem.denyWrite, cwd) &&
1349
1378
  !shouldPromptForWrite(path, getEffectiveAllowWrite(cfg), cwd);
1350
1379
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.16.24",
3
+ "version": "0.16.26",
4
4
  "description": "Landlock-based sandboxing for pi with interactive permission prompts",
5
5
  "keywords": [
6
6
  "landstrip",