opencode-landstrip 0.17.33 → 0.17.35

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/index.ts +16 -9
  2. package/package.json +2 -2
  3. package/shared.ts +19 -13
package/index.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  type Socket,
16
16
  } from 'node:net';
17
17
  import { homedir, tmpdir } from 'node:os';
18
- import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
18
+ import { basename, dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';
19
19
  import { URL } from 'node:url';
20
20
 
21
21
  import {
@@ -138,9 +138,16 @@ function canonicalizeGlobPath(pattern: string, baseDirectory: string): string {
138
138
  const wildcardIndex = abs.indexOf('*');
139
139
  if (wildcardIndex === -1) return canonicalizePath(abs, baseDirectory);
140
140
 
141
- const prefixEnd = abs.lastIndexOf('/', wildcardIndex);
142
- const prefix = prefixEnd === 0 ? '/' : abs.slice(0, prefixEnd);
143
- return canonicalizePath(prefix, baseDirectory) + abs.slice(prefixEnd);
141
+ const prefixEnd = abs.lastIndexOf(sep, wildcardIndex);
142
+ const root = parse(abs).root;
143
+ const wildcardAtRoot = prefixEnd < root.length;
144
+ const prefix = wildcardAtRoot ? root : abs.slice(0, prefixEnd);
145
+ const suffixStart = wildcardAtRoot ? root.length : prefixEnd;
146
+ return canonicalizePath(prefix, baseDirectory) + abs.slice(suffixStart);
147
+ }
148
+
149
+ function normalizePathForMatch(filePath: string): string {
150
+ return process.platform === 'win32' ? filePath.replaceAll('\\', '/').toLowerCase() : filePath;
144
151
  }
145
152
 
146
153
  const globRegExpCache = new Map<string, RegExp>();
@@ -192,17 +199,17 @@ function pathDepth(absolutePath: string): number {
192
199
  // none match. A glob is anchored to the whole path, so it ranks at the path's
193
200
  // own depth; a literal pattern ranks at the depth of the prefix it covers.
194
201
  function matchDepth(filePath: string, patterns: string[], baseDirectory: string): number {
195
- const abs = canonicalizePath(filePath, baseDirectory);
202
+ const abs = normalizePathForMatch(canonicalizePath(filePath, baseDirectory));
196
203
  let depth = -1;
197
204
 
198
205
  for (const pattern of patterns) {
199
206
  if (pattern.includes('*')) {
200
- const absPattern = canonicalizeGlobPath(pattern, baseDirectory);
207
+ const absPattern = normalizePathForMatch(canonicalizeGlobPath(pattern, baseDirectory));
201
208
  if (globToRegExp(absPattern).test(abs)) depth = Math.max(depth, pathDepth(abs));
202
209
  } else {
203
- const absPattern = canonicalizePath(pattern, baseDirectory);
204
- const sep = absPattern.endsWith('/') ? '' : '/';
205
- if (abs === absPattern || abs.startsWith(absPattern + sep)) {
210
+ const absPattern = normalizePathForMatch(canonicalizePath(pattern, baseDirectory));
211
+ const separator = absPattern.endsWith('/') ? '' : '/';
212
+ if (abs === absPattern || abs.startsWith(absPattern + separator)) {
206
213
  depth = Math.max(depth, pathDepth(absPattern));
207
214
  }
208
215
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-landstrip",
3
- "version": "0.17.33",
3
+ "version": "0.17.35",
4
4
  "description": "Landlock-based sandboxing for opencode with landstrip",
5
5
  "keywords": [
6
6
  "landlock",
@@ -48,7 +48,7 @@
48
48
  "ci:test": "npm test"
49
49
  },
50
50
  "dependencies": {
51
- "@landstrip/landstrip": "^0.17.33",
51
+ "@landstrip/landstrip": "^0.17.35",
52
52
  "@opentui/solid": ">=0.3.4",
53
53
  "solid-js": "1.9.12"
54
54
  },
package/shared.ts CHANGED
@@ -15,7 +15,7 @@ import { createHash } from 'node:crypto';
15
15
  import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
16
16
  import { homedir, tmpdir } from 'node:os';
17
17
  import { fileURLToPath } from 'node:url';
18
- import { dirname, join } from 'node:path';
18
+ import { dirname, isAbsolute, join, relative, sep } from 'node:path';
19
19
 
20
20
  export interface SandboxFilesystemConfig {
21
21
  denyRead: string[];
@@ -61,9 +61,8 @@ const LANDSTRIP_PACKAGE_NAMES = new Set([
61
61
  // is approved for the broadest reasonable ancestor (e.g. `~/.cargo`, not each
62
62
  // subcrate file), so a single approval covers sibling files under the same tree.
63
63
  export function pathUnderDirectory(filePath: string, dir: string): boolean {
64
- if (filePath === dir) return true;
65
- const sep = dir.endsWith('/') ? '' : '/';
66
- return filePath.startsWith(dir + sep);
64
+ const child = relative(dir, filePath);
65
+ return child === '' || (!isAbsolute(child) && child !== '..' && !child.startsWith(`..${sep}`));
67
66
  }
68
67
 
69
68
  export function sessionAllows(prefixes: Set<string>, filePath: string): boolean {
@@ -81,25 +80,32 @@ export function sessionAllows(prefixes: Set<string>, filePath: string): boolean
81
80
  export function sessionScopeFor(filePath: string, baseDirectory: string): string {
82
81
  const dir = dirname(filePath);
83
82
  const home = homedir();
84
- const boundaries = new Set<string>();
85
- if (home) boundaries.add(home);
83
+ const homeBoundaries = new Set<string>([home]);
86
84
  try {
87
- const realHome = realpathSync.native(home);
88
- if (realHome) boundaries.add(realHome);
85
+ homeBoundaries.add(realpathSync.native(home));
89
86
  } catch {
90
87
  // $HOME not resolvable — fall back to the raw value only.
91
88
  }
92
89
 
93
- for (const boundary of boundaries) {
90
+ for (const boundary of homeBoundaries) {
94
91
  if (pathUnderDirectory(dir, boundary)) {
95
- const rest = dir.slice(boundary.length).replace(/^\/+/, '');
96
- const first = rest.split('/')[0];
92
+ const first = relative(boundary, dir).split(sep)[0];
97
93
  if (!first) return filePath;
98
- return boundary.endsWith('/') ? boundary + first : `${boundary}/${first}`;
94
+ return join(boundary, first);
99
95
  }
100
96
  }
101
97
 
102
- if (pathUnderDirectory(dir, baseDirectory)) return baseDirectory;
98
+ const projectBoundaries = new Set<string>([baseDirectory]);
99
+ try {
100
+ projectBoundaries.add(realpathSync.native(baseDirectory));
101
+ } catch {
102
+ // Project directory not resolvable — fall back to the raw value only.
103
+ }
104
+
105
+ for (const boundary of projectBoundaries) {
106
+ if (pathUnderDirectory(dir, boundary)) return boundary;
107
+ }
108
+
103
109
  return dir;
104
110
  }
105
111