opencode-landstrip 0.17.19 → 0.17.20
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 +132 -129
- package/package.json +4 -4
package/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type SandboxConfig,
|
|
16
16
|
type SandboxFilesystemConfig,
|
|
17
17
|
controlResponseLine,
|
|
18
|
+
decodeLandstripTrap,
|
|
18
19
|
extractDomainsFromCommand,
|
|
19
20
|
formatLandstripTraps,
|
|
20
21
|
getConfigPaths,
|
|
@@ -29,16 +30,12 @@ import {
|
|
|
29
30
|
sessionScopeFor,
|
|
30
31
|
} from './shared.js';
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
network: {
|
|
34
|
-
allowNetwork: boolean;
|
|
35
|
-
allowLocalBinding: boolean;
|
|
36
|
-
allowAllUnixSockets: boolean;
|
|
37
|
-
allowUnixSockets: string[];
|
|
33
|
+
type LandstripPolicy = {
|
|
34
|
+
network: Omit<SandboxConfig['network'], 'allowedDomains' | 'deniedDomains'> & {
|
|
38
35
|
httpProxyPort?: number;
|
|
39
36
|
};
|
|
40
37
|
filesystem: SandboxFilesystemConfig;
|
|
41
|
-
}
|
|
38
|
+
};
|
|
42
39
|
|
|
43
40
|
interface BashSandboxState {
|
|
44
41
|
originalCommand: string;
|
|
@@ -219,8 +216,23 @@ function allowsAllDomains(allowedDomains: string[]): boolean {
|
|
|
219
216
|
return allowedDomains.includes('*');
|
|
220
217
|
}
|
|
221
218
|
|
|
222
|
-
function
|
|
223
|
-
return
|
|
219
|
+
function isDomainAllowed(domain: string, config: SandboxConfig): boolean {
|
|
220
|
+
return (
|
|
221
|
+
config.network.allowNetwork ||
|
|
222
|
+
(!domainMatchesAny(domain, config.network.deniedDomains) &&
|
|
223
|
+
domainMatchesAny(domain, config.network.allowedDomains))
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isFilesystemAllowed(
|
|
228
|
+
path: string,
|
|
229
|
+
allowPatterns: string[],
|
|
230
|
+
denyPatterns: string[],
|
|
231
|
+
baseDirectory: string,
|
|
232
|
+
): boolean {
|
|
233
|
+
const allowDepth = matchDepth(path, allowPatterns, baseDirectory);
|
|
234
|
+
const denyDepth = matchDepth(path, denyPatterns, baseDirectory);
|
|
235
|
+
return allowDepth >= 0 && allowDepth >= denyDepth;
|
|
224
236
|
}
|
|
225
237
|
|
|
226
238
|
function extractCandidatePaths(command: string): string[] {
|
|
@@ -250,24 +262,24 @@ function extractBlockedPath(
|
|
|
250
262
|
let match = output.match(
|
|
251
263
|
/(?:\/bin\/bash|bash|sh): (?:line \d+: )?([^:\n]+): (?:Operation not permitted|Permission denied)/,
|
|
252
264
|
);
|
|
253
|
-
if (match?.[1]) return
|
|
265
|
+
if (match?.[1]) return canonicalizePath(match[1], baseDirectory);
|
|
254
266
|
|
|
255
267
|
// ls/cat/cp: cannot open/access/stat '/path': Permission denied
|
|
256
268
|
match = output.match(
|
|
257
269
|
/^[a-zA-Z0-9_-]+: cannot (?:open|access|stat|create)(?: directory)? '?([^'\n]+?)'?(?: for (?:reading|writing))?: Permission denied$/m,
|
|
258
270
|
);
|
|
259
|
-
if (match?.[1]) return
|
|
271
|
+
if (match?.[1]) return canonicalizePath(match[1], baseDirectory);
|
|
260
272
|
|
|
261
273
|
// Generic: cmd: /absolute/path: Permission denied or Operation not permitted
|
|
262
274
|
match = output.match(
|
|
263
275
|
/^[a-zA-Z0-9_-]+: (\/[^\n:]+): (?:Operation not permitted|Permission denied)$/m,
|
|
264
276
|
);
|
|
265
|
-
if (match?.[1]) return
|
|
277
|
+
if (match?.[1]) return canonicalizePath(match[1], baseDirectory);
|
|
266
278
|
|
|
267
279
|
// Landstrip structured trap format carrying a denied path
|
|
268
280
|
const landstripTraps = parseLandstripTraps(output);
|
|
269
281
|
for (const trap of landstripTraps) {
|
|
270
|
-
if (trap.kind === 'filesystem') return
|
|
282
|
+
if (trap.kind === 'filesystem') return canonicalizePath(trap.path, baseDirectory);
|
|
271
283
|
}
|
|
272
284
|
|
|
273
285
|
if (
|
|
@@ -290,15 +302,15 @@ function evaluateReadPermission(
|
|
|
290
302
|
effectiveAllowRead: string[],
|
|
291
303
|
): SandboxPermissionDecision {
|
|
292
304
|
const filePath = canonicalizePath(path, baseDirectory);
|
|
293
|
-
const allowDepth = matchDepth(filePath, effectiveAllowRead, baseDirectory);
|
|
294
|
-
const denyDepth = matchDepth(filePath, config.filesystem.denyRead, baseDirectory);
|
|
295
305
|
|
|
296
306
|
// Reads are interactive, so the read tool never hard-denies: a path covered by
|
|
297
307
|
// allowRead at least as specifically as any denyRead is allowed silently;
|
|
298
308
|
// everything else asks for approval (allow once/session/persist or reject)
|
|
299
309
|
// rather than being blocked outright. denyRead still hard-applies to bash
|
|
300
310
|
// through the landstrip binary policy, which has no way to prompt.
|
|
301
|
-
if (
|
|
311
|
+
if (
|
|
312
|
+
isFilesystemAllowed(filePath, effectiveAllowRead, config.filesystem.denyRead, baseDirectory)
|
|
313
|
+
) {
|
|
302
314
|
return { status: 'allow', kind: 'read', resource: filePath, message: '' };
|
|
303
315
|
}
|
|
304
316
|
|
|
@@ -329,7 +341,9 @@ function evaluateWritePermission(
|
|
|
329
341
|
};
|
|
330
342
|
}
|
|
331
343
|
|
|
332
|
-
if (
|
|
344
|
+
if (
|
|
345
|
+
isFilesystemAllowed(filePath, effectiveAllowWrite, config.filesystem.denyWrite, baseDirectory)
|
|
346
|
+
) {
|
|
333
347
|
return { status: 'allow', kind: 'write', resource: filePath, message: '' };
|
|
334
348
|
}
|
|
335
349
|
|
|
@@ -358,7 +372,7 @@ function evaluateDomainPermission(
|
|
|
358
372
|
};
|
|
359
373
|
}
|
|
360
374
|
|
|
361
|
-
if (
|
|
375
|
+
if (isDomainAllowed(domain, config)) {
|
|
362
376
|
return { status: 'allow', kind: 'domain', resource: domain, message: '' };
|
|
363
377
|
}
|
|
364
378
|
|
|
@@ -370,6 +384,16 @@ function evaluateDomainPermission(
|
|
|
370
384
|
};
|
|
371
385
|
}
|
|
372
386
|
|
|
387
|
+
function evaluateCommandDomains(
|
|
388
|
+
command: string,
|
|
389
|
+
config: SandboxConfig,
|
|
390
|
+
): SandboxPermissionDecision[] {
|
|
391
|
+
if (config.network.allowNetwork) return [];
|
|
392
|
+
return extractDomainsFromCommand(command).map((domain) =>
|
|
393
|
+
evaluateDomainPermission(domain, config),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
373
397
|
function landstripVersion(): string | null {
|
|
374
398
|
const result = spawnSync(landstripBinaryPath(), ['--version'], { encoding: 'utf-8' });
|
|
375
399
|
if (result.status !== 0) return null;
|
|
@@ -467,11 +491,6 @@ function writePolicyFile(
|
|
|
467
491
|
function startProxy(config: SandboxConfig): Promise<{ port: number; stop: () => Promise<void> }> {
|
|
468
492
|
const sockets = new Set<Socket>();
|
|
469
493
|
|
|
470
|
-
function domainAllowed(domain: string): boolean {
|
|
471
|
-
if (domainMatchesAny(domain, config.network.deniedDomains)) return false;
|
|
472
|
-
return domainMatchesAny(domain, config.network.allowedDomains);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
494
|
async function handleConnect(client: Socket, target: string, rest: Buffer): Promise<void> {
|
|
476
495
|
const endpoint = splitHostPort(target, 443);
|
|
477
496
|
if (!endpoint || !Number.isFinite(endpoint.port)) {
|
|
@@ -479,7 +498,7 @@ function startProxy(config: SandboxConfig): Promise<{ port: number; stop: () =>
|
|
|
479
498
|
return;
|
|
480
499
|
}
|
|
481
500
|
|
|
482
|
-
if (!
|
|
501
|
+
if (!isDomainAllowed(endpoint.host, config)) {
|
|
483
502
|
denyProxyRequest(client);
|
|
484
503
|
return;
|
|
485
504
|
}
|
|
@@ -525,7 +544,7 @@ function startProxy(config: SandboxConfig): Promise<{ port: number; stop: () =>
|
|
|
525
544
|
url = new URL(`http://${host}${rawTarget}`);
|
|
526
545
|
}
|
|
527
546
|
|
|
528
|
-
if (!
|
|
547
|
+
if (!isDomainAllowed(url.hostname, config)) {
|
|
529
548
|
denyProxyRequest(client);
|
|
530
549
|
return;
|
|
531
550
|
}
|
|
@@ -660,33 +679,25 @@ function startTrapServer(
|
|
|
660
679
|
buffer = buffer.slice(nl + 1);
|
|
661
680
|
nl = buffer.indexOf('\n');
|
|
662
681
|
if (line.length === 0) continue;
|
|
663
|
-
let
|
|
682
|
+
let trap: LandstripTrap | null = null;
|
|
664
683
|
try {
|
|
665
|
-
|
|
666
|
-
if (typeof parsed === 'object' && parsed !== null) {
|
|
667
|
-
obj = parsed as Record<string, unknown>;
|
|
668
|
-
}
|
|
684
|
+
trap = decodeLandstripTrap(JSON.parse(line));
|
|
669
685
|
} catch {
|
|
670
|
-
|
|
686
|
+
trap = null;
|
|
671
687
|
}
|
|
672
|
-
if (
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
// specific; a tie or more-specific allow carves the path back in.
|
|
682
|
-
const denyReadDepth = matchDepth(path, denyRead, baseDirectory);
|
|
683
|
-
const allowReadDepth = matchDepth(path, effectiveAllowRead, baseDirectory);
|
|
684
|
-
const denyWriteDepth = matchDepth(path, denyWrite, baseDirectory);
|
|
685
|
-
const allowWriteDepth = matchDepth(path, effectiveAllowWrite, baseDirectory);
|
|
688
|
+
if (
|
|
689
|
+
(trap?.kind === 'filesystem' || trap?.kind === 'network') &&
|
|
690
|
+
trap.state === 'query' &&
|
|
691
|
+
trap.query_id
|
|
692
|
+
) {
|
|
693
|
+
const queryId = trap.query_id;
|
|
694
|
+
if (trap.kind === 'filesystem') {
|
|
695
|
+
const path = canonicalizePath(trap.path, baseDirectory);
|
|
696
|
+
const operation = trap.operation;
|
|
686
697
|
const allowed =
|
|
687
698
|
operation === 'read'
|
|
688
|
-
?
|
|
689
|
-
:
|
|
699
|
+
? isFilesystemAllowed(path, effectiveAllowRead, denyRead, baseDirectory)
|
|
700
|
+
: isFilesystemAllowed(path, effectiveAllowWrite, denyWrite, baseDirectory);
|
|
690
701
|
if (allowed) {
|
|
691
702
|
trapSocket.write(controlResponseLine(queryId, 'allow'));
|
|
692
703
|
} else {
|
|
@@ -698,15 +709,15 @@ function startTrapServer(
|
|
|
698
709
|
trapLines.push(line);
|
|
699
710
|
}
|
|
700
711
|
} else if (
|
|
701
|
-
|
|
702
|
-
|
|
712
|
+
trap.kind === 'network' &&
|
|
713
|
+
(trap.operation === 'connect' || trap.operation === 'bind')
|
|
703
714
|
) {
|
|
704
715
|
// No policy field expresses "allow this address:port" (allowedDomains
|
|
705
716
|
// is hostname-based and enforced by the HTTP(S) proxy, not the broker),
|
|
706
717
|
// so — matching the filesystem branch above — auto-grant, remember it
|
|
707
718
|
// for the rest of the session, and surface it afterward rather than
|
|
708
719
|
// hanging or hard-denying an already-approved command.
|
|
709
|
-
const target =
|
|
720
|
+
const target = trap.target;
|
|
710
721
|
if (!sessionAllowedTargets.has(target)) {
|
|
711
722
|
sessionAllowedTargets.add(target);
|
|
712
723
|
trapLines.push(line);
|
|
@@ -843,6 +854,45 @@ function extractPatchPaths(patchText: string): string[] {
|
|
|
843
854
|
return paths;
|
|
844
855
|
}
|
|
845
856
|
|
|
857
|
+
function evaluateToolPermissions(
|
|
858
|
+
tool: string,
|
|
859
|
+
args: Record<string, unknown>,
|
|
860
|
+
config: SandboxConfig,
|
|
861
|
+
baseDirectory: string,
|
|
862
|
+
effectiveAllowRead: string[],
|
|
863
|
+
effectiveAllowWrite: string[],
|
|
864
|
+
): SandboxPermissionDecision[] {
|
|
865
|
+
if (tool === 'read') {
|
|
866
|
+
const paths = Array.isArray(args.paths)
|
|
867
|
+
? args.paths.filter((path): path is string => typeof path === 'string')
|
|
868
|
+
: [getToolPath(args)].filter((path): path is string => path !== undefined);
|
|
869
|
+
return paths.map((path) =>
|
|
870
|
+
evaluateReadPermission(path, config, baseDirectory, effectiveAllowRead),
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (tool === 'glob' || tool === 'grep' || tool === 'list') {
|
|
875
|
+
return [evaluateReadPermission(getSearchPath(args), config, baseDirectory, effectiveAllowRead)];
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (tool === 'write' || tool === 'edit') {
|
|
879
|
+
const path = getToolPath(args);
|
|
880
|
+
return path ? [evaluateWritePermission(path, config, baseDirectory, effectiveAllowWrite)] : [];
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (tool === 'apply_patch' && typeof args.patchText === 'string') {
|
|
884
|
+
return extractPatchPaths(args.patchText).map((path) =>
|
|
885
|
+
evaluateWritePermission(path, config, baseDirectory, effectiveAllowWrite),
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (tool === 'bash' && typeof args.command === 'string') {
|
|
890
|
+
return evaluateCommandDomains(args.command, config);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
|
|
846
896
|
function errorWithConfigPaths(baseDirectory: string, message: string): Error {
|
|
847
897
|
const { globalPath, projectPath } = getConfigPaths(baseDirectory);
|
|
848
898
|
return new Error(`${message}\n\nUpdate sandbox config in:\n ${projectPath}\n ${globalPath}`);
|
|
@@ -857,12 +907,16 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
|
|
|
857
907
|
const sessionAllowedWritePaths = new Set<string>();
|
|
858
908
|
const sessionAllowedTargets = new Set<string>();
|
|
859
909
|
|
|
910
|
+
function mergeAllowances(configured: string[], session: Set<string>): string[] {
|
|
911
|
+
return [...configured, ...session];
|
|
912
|
+
}
|
|
913
|
+
|
|
860
914
|
function getEffectiveAllowRead(config: SandboxConfig): string[] {
|
|
861
|
-
return
|
|
915
|
+
return mergeAllowances(config.filesystem.allowRead, sessionAllowedReadPaths);
|
|
862
916
|
}
|
|
863
917
|
|
|
864
918
|
function getEffectiveAllowWrite(config: SandboxConfig): string[] {
|
|
865
|
-
return
|
|
919
|
+
return mergeAllowances(config.filesystem.allowWrite, sessionAllowedWritePaths);
|
|
866
920
|
}
|
|
867
921
|
let enabledNotified = false;
|
|
868
922
|
let configuredShell: string | undefined;
|
|
@@ -1139,11 +1193,10 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
|
|
|
1139
1193
|
};
|
|
1140
1194
|
|
|
1141
1195
|
if (!allowNetwork) {
|
|
1142
|
-
for (const
|
|
1143
|
-
const decision = evaluateDomainPermission(domain, effectiveConfig);
|
|
1196
|
+
for (const decision of evaluateCommandDomains(args.command as string, effectiveConfig)) {
|
|
1144
1197
|
if (decision.status === 'allow') continue;
|
|
1145
1198
|
if (decision.status === 'ask' && hasCallAllowance(callID, decision)) {
|
|
1146
|
-
callAllowedDomains.push(
|
|
1199
|
+
callAllowedDomains.push(decision.resource);
|
|
1147
1200
|
continue;
|
|
1148
1201
|
}
|
|
1149
1202
|
throw errorWithConfigPaths(directory, decision.message);
|
|
@@ -1226,41 +1279,22 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
|
|
|
1226
1279
|
: undefined;
|
|
1227
1280
|
const patterns = permissionPatterns(request);
|
|
1228
1281
|
|
|
1229
|
-
const decisions: SandboxPermissionDecision[] = [];
|
|
1230
1282
|
const effectiveAllowRead = getEffectiveAllowRead(config);
|
|
1231
1283
|
const effectiveAllowWrite = getEffectiveAllowWrite(config);
|
|
1232
|
-
|
|
1233
|
-
if (permission === 'read')
|
|
1234
|
-
for (const pattern of patterns) {
|
|
1235
|
-
decisions.push(evaluateReadPermission(pattern, config, directory, effectiveAllowRead));
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
if (permission === 'glob' || permission === 'grep' || permission === 'list') {
|
|
1240
|
-
const searchPath = typeof metadata.path === 'string' ? metadata.path : '.';
|
|
1241
|
-
decisions.push(evaluateReadPermission(searchPath, config, directory, effectiveAllowRead));
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1284
|
+
const args: Record<string, unknown> = { ...metadata };
|
|
1285
|
+
if (permission === 'read') args.paths = patterns;
|
|
1244
1286
|
if (permission === 'edit') {
|
|
1245
|
-
|
|
1246
|
-
typeof metadata.filepath === 'string'
|
|
1247
|
-
? metadata.filepath
|
|
1248
|
-
: patterns.length === 1
|
|
1249
|
-
? patterns[0]
|
|
1250
|
-
: undefined;
|
|
1251
|
-
if (filepath) {
|
|
1252
|
-
decisions.push(evaluateWritePermission(filepath, config, directory, effectiveAllowWrite));
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
if (permission === 'bash') {
|
|
1257
|
-
const command = typeof metadata.command === 'string' ? metadata.command : patterns[0];
|
|
1258
|
-
if (typeof command === 'string' && !config.network.allowNetwork) {
|
|
1259
|
-
for (const domain of extractDomainsFromCommand(command)) {
|
|
1260
|
-
decisions.push(evaluateDomainPermission(domain, config));
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1287
|
+
args.path = typeof metadata.filepath === 'string' ? metadata.filepath : patterns[0];
|
|
1263
1288
|
}
|
|
1289
|
+
if (permission === 'bash' && typeof args.command !== 'string') args.command = patterns[0];
|
|
1290
|
+
const decisions = evaluateToolPermissions(
|
|
1291
|
+
permission,
|
|
1292
|
+
args,
|
|
1293
|
+
config,
|
|
1294
|
+
directory,
|
|
1295
|
+
effectiveAllowRead,
|
|
1296
|
+
effectiveAllowWrite,
|
|
1297
|
+
);
|
|
1264
1298
|
|
|
1265
1299
|
const decision =
|
|
1266
1300
|
decisions.find((item) => item.status === 'deny') ??
|
|
@@ -1277,49 +1311,21 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
|
|
|
1277
1311
|
const config = await activeConfig();
|
|
1278
1312
|
if (!config) return;
|
|
1279
1313
|
|
|
1280
|
-
const effectiveAllowRead = getEffectiveAllowRead(config);
|
|
1281
|
-
const effectiveAllowWrite = getEffectiveAllowWrite(config);
|
|
1282
|
-
|
|
1283
1314
|
if (input.tool === 'bash') {
|
|
1284
1315
|
await prepareBash(input.callID, output.args, config);
|
|
1285
1316
|
return;
|
|
1286
1317
|
}
|
|
1287
1318
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
if (input.tool === 'glob' || input.tool === 'grep' || input.tool === 'list') {
|
|
1299
|
-
enforcePermission(
|
|
1300
|
-
input.callID,
|
|
1301
|
-
evaluateReadPermission(getSearchPath(output.args), config, directory, effectiveAllowRead),
|
|
1302
|
-
);
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
if (input.tool === 'write' || input.tool === 'edit') {
|
|
1307
|
-
const path = getToolPath(output.args);
|
|
1308
|
-
if (path)
|
|
1309
|
-
enforcePermission(
|
|
1310
|
-
input.callID,
|
|
1311
|
-
evaluateWritePermission(path, config, directory, effectiveAllowWrite),
|
|
1312
|
-
);
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
if (input.tool === 'apply_patch' && typeof output.args.patchText === 'string') {
|
|
1317
|
-
for (const path of extractPatchPaths(output.args.patchText)) {
|
|
1318
|
-
enforcePermission(
|
|
1319
|
-
input.callID,
|
|
1320
|
-
evaluateWritePermission(path, config, directory, effectiveAllowWrite),
|
|
1321
|
-
);
|
|
1322
|
-
}
|
|
1319
|
+
const decisions = evaluateToolPermissions(
|
|
1320
|
+
input.tool,
|
|
1321
|
+
output.args,
|
|
1322
|
+
config,
|
|
1323
|
+
directory,
|
|
1324
|
+
getEffectiveAllowRead(config),
|
|
1325
|
+
getEffectiveAllowWrite(config),
|
|
1326
|
+
);
|
|
1327
|
+
for (const decision of decisions) {
|
|
1328
|
+
enforcePermission(input.callID, decision);
|
|
1323
1329
|
}
|
|
1324
1330
|
},
|
|
1325
1331
|
|
|
@@ -1430,11 +1436,8 @@ const plugin: Plugin = async ({ client, directory }: PluginInput, options?: Plug
|
|
|
1430
1436
|
if (writeDecision.status === 'deny') reportBlocked(writeDecision);
|
|
1431
1437
|
}
|
|
1432
1438
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
const decision = evaluateDomainPermission(domain, config);
|
|
1436
|
-
if (decision.status !== 'allow') reportBlocked(decision);
|
|
1437
|
-
}
|
|
1439
|
+
for (const decision of evaluateCommandDomains(shellCommand, config)) {
|
|
1440
|
+
if (decision.status !== 'allow') reportBlocked(decision);
|
|
1438
1441
|
}
|
|
1439
1442
|
}
|
|
1440
1443
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-landstrip",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.20",
|
|
4
4
|
"description": "Landlock-based sandboxing for opencode with landstrip",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landlock",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"fmt": "oxfmt index.ts tui.ts shared.ts package.json tsconfig.json sandbox.json .oxfmtrc.json README.md test/*.
|
|
40
|
+
"fmt": "oxfmt index.ts tui.ts shared.ts package.json tsconfig.json sandbox.json .oxfmtrc.json README.md test/*.mjs",
|
|
41
41
|
"lint": "oxlint index.ts tui.ts shared.ts",
|
|
42
42
|
"check": "tsc --noEmit",
|
|
43
43
|
"test": "node --test test/*.test.mjs",
|
|
44
44
|
"all": "npm run fmt && npm run lint && npm run check && npm test",
|
|
45
|
-
"ci:fmt": "oxfmt --check index.ts tui.ts shared.ts package.json tsconfig.json sandbox.json .oxfmtrc.json README.md test/*.
|
|
45
|
+
"ci:fmt": "oxfmt --check index.ts tui.ts shared.ts package.json tsconfig.json sandbox.json .oxfmtrc.json README.md test/*.mjs",
|
|
46
46
|
"ci:lint": "npm run lint",
|
|
47
47
|
"ci:check": "npm run check",
|
|
48
48
|
"ci:test": "npm test"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@landstrip/landstrip": "^0.17.
|
|
51
|
+
"@landstrip/landstrip": "^0.17.20",
|
|
52
52
|
"@opentui/solid": ">=0.3.4",
|
|
53
53
|
"solid-js": "1.9.12"
|
|
54
54
|
},
|