opencode-landstrip 0.16.8 → 0.16.10

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 +25 -18
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -164,10 +164,6 @@ function matchDepth(filePath: string, patterns: string[], baseDirectory: string)
164
164
  return depth;
165
165
  }
166
166
 
167
- function matchesPattern(filePath: string, patterns: string[], baseDirectory: string): boolean {
168
- return matchDepth(filePath, patterns, baseDirectory) >= 0;
169
- }
170
-
171
167
  function resolveFilesystemPatterns(patterns: string[], baseDirectory: string): string[] {
172
168
  return patterns.map((pattern) =>
173
169
  pattern.includes('*')
@@ -188,10 +184,6 @@ function resolveFilesystemConfig(
188
184
  };
189
185
  }
190
186
 
191
- function shouldPromptForWrite(path: string, allowWrite: string[], baseDirectory: string): boolean {
192
- return allowWrite.length === 0 || !matchesPattern(path, allowWrite, baseDirectory);
193
- }
194
-
195
187
  function domainMatchesPattern(domain: string, pattern: string): boolean {
196
188
  const normalizedDomain = domain.toLowerCase();
197
189
  const normalizedPattern = pattern.toLowerCase();
@@ -321,17 +313,19 @@ function evaluateWritePermission(
321
313
  effectiveAllowWrite: string[],
322
314
  ): SandboxPermissionDecision {
323
315
  const filePath = canonicalizePath(path, baseDirectory);
316
+ const allowDepth = matchDepth(filePath, effectiveAllowWrite, baseDirectory);
317
+ const denyDepth = matchDepth(filePath, config.filesystem.denyWrite, baseDirectory);
324
318
 
325
- if (matchesPattern(filePath, config.filesystem.denyWrite, baseDirectory)) {
319
+ if (denyDepth > allowDepth) {
326
320
  return {
327
321
  status: 'deny',
328
322
  kind: 'write',
329
323
  resource: filePath,
330
- message: `Sandbox: write access denied for "${filePath}" (in filesystem.denyWrite).`,
324
+ message: `Sandbox: write access denied for "${filePath}" (denyWrite overrides allowWrite).`,
331
325
  };
332
326
  }
333
327
 
334
- if (!shouldPromptForWrite(filePath, effectiveAllowWrite, baseDirectory)) {
328
+ if (allowDepth >= 0) {
335
329
  return { status: 'allow', kind: 'write', resource: filePath, message: '' };
336
330
  }
337
331
 
@@ -665,12 +659,17 @@ function buildWrappedCommand(
665
659
  if (trapPort === null) return plain;
666
660
 
667
661
  // Connect fd 3 to the TUI's query-response socket BEFORE landstrip applies the
668
- // sandbox, so a denied write can be approved live instead of forcing a re-run.
669
- // The `&&`/`||` guard falls back to the plain (no --trap-fd) invocation when the
670
- // redirect fails — a dead port, a non-bash outer shell without /dev/tcp, or an
671
- // outer `set -e`so a failed connect never hands the broker a dead fd 3.
662
+ // sandbox, so a denied filesystem access can be approved live instead of
663
+ // forcing a re-run.
664
+ //
665
+ // /dev/tcp is a bash/ksh built-init does not work in zsh, dash, or fish.
666
+ // We try the native redirect first (fast path when the host shell supports it)
667
+ // and fall back to an explicit bash invocation that always speaks /dev/tcp.
668
+ // If both fail (no bash, dead port, set -e in the outer shell) landstrip runs
669
+ // without --trap-fd so the toast-notify path still works.
672
670
  const trapped = [landstripBinaryPath(), '--trap-fd', '3', ...baseArgs].map(shellQuote).join(' ');
673
- return `{ exec 3<>/dev/tcp/127.0.0.1/${trapPort} ; } 2>/dev/null && ${trapped} || ${plain}`;
671
+ const bashTrap = `bash -c ${shellQuote(`exec 3<>/dev/tcp/127.0.0.1/${trapPort} && exec "$@"`)} bash ${trapped}`;
672
+ return `{ exec 3<>/dev/tcp/127.0.0.1/${trapPort} ; } 2>/dev/null && ${trapped} || ${bashTrap} 2>/dev/null || ${plain}`;
674
673
  }
675
674
 
676
675
  function isGeneratedWrappedCommand(command: string): boolean {
@@ -696,11 +695,19 @@ function splitShellQuotedArgs(command: string): string[] {
696
695
  if (command[i] === "'") {
697
696
  i++;
698
697
  let arg = '';
699
- while (i < command.length && command[i] !== "'") {
698
+ while (i < command.length) {
699
+ if (command[i] === "'") {
700
+ if (command[i + 1] === '\\' && command[i + 2] === "'" && command[i + 3] === "'") {
701
+ arg += "'";
702
+ i += 4;
703
+ continue;
704
+ }
705
+ i++;
706
+ break;
707
+ }
700
708
  arg += command[i];
701
709
  i++;
702
710
  }
703
- if (i < command.length) i++;
704
711
  args.push(arg);
705
712
  } else {
706
713
  let arg = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-landstrip",
3
- "version": "0.16.8",
3
+ "version": "0.16.10",
4
4
  "description": "Landlock-based sandboxing for opencode with landstrip",
5
5
  "keywords": [
6
6
  "landlock",