opencode-landstrip 0.17.34 → 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.
- package/index.ts +16 -9
- package/package.json +2 -2
- 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(
|
|
142
|
-
const
|
|
143
|
-
|
|
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
|
|
205
|
-
if (abs === absPattern || abs.startsWith(absPattern +
|
|
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.
|
|
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.
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
85
|
-
if (home) boundaries.add(home);
|
|
83
|
+
const homeBoundaries = new Set<string>([home]);
|
|
86
84
|
try {
|
|
87
|
-
|
|
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
|
|
90
|
+
for (const boundary of homeBoundaries) {
|
|
94
91
|
if (pathUnderDirectory(dir, boundary)) {
|
|
95
|
-
const
|
|
96
|
-
const first = rest.split('/')[0];
|
|
92
|
+
const first = relative(boundary, dir).split(sep)[0];
|
|
97
93
|
if (!first) return filePath;
|
|
98
|
-
return
|
|
94
|
+
return join(boundary, first);
|
|
99
95
|
}
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
|
|
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
|
|