opencode-landstrip 0.19.7 → 0.19.8
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/README.md +6 -12
- package/package.json +2 -2
- package/shared.ts +24 -7
package/README.md
CHANGED
|
@@ -19,23 +19,17 @@ Approval never bypasses hard denials. Interactive requests without a live
|
|
|
19
19
|
presenter remain denied. Only AI `bash` receives OS isolation: OpenCode's plugin
|
|
20
20
|
API cannot replace direct user shell commands.
|
|
21
21
|
|
|
22
|
-
Policy precedence (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
3. `.opencode/sandbox.json`.
|
|
27
|
-
4. Plugin options.
|
|
22
|
+
Policy precedence: bundled [`sandbox.json`](sandbox.json) → global →
|
|
23
|
+
`.opencode/sandbox.json` → plugin options. The global file is
|
|
24
|
+
`$XDG_CONFIG_HOME/opencode/sandbox.json` (`~/.config` when unset, empty, or relative).
|
|
25
|
+
Persistent approvals update the chosen project/global file.
|
|
28
26
|
|
|
29
27
|
Arrays combine; later scalar values win. Defaults allow project writes, deny
|
|
30
28
|
sensitive-file writes, and block network access. Enabled sandboxing fails closed
|
|
31
29
|
on unusable binaries/platforms; `enabled: false` explicitly permits unsandboxed Bash.
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
unsupported on Windows.
|
|
36
|
-
- `filesystem.denyWriteAlways` cannot be overridden by equal/nested write grants
|
|
37
|
-
and blocks promptable writes on those paths. Non-empty `denyWriteAlways` is
|
|
38
|
-
unsupported on Windows.
|
|
31
|
+
`filesystem.denyReadAlways` and `filesystem.denyWriteAlways` block approvals and
|
|
32
|
+
equal/nested grants. Non-empty hard-denial lists are unsupported on Windows.
|
|
39
33
|
|
|
40
34
|
See [landstrip(1)](../landstrip/man/man1/landstrip.1) for native CLI/policy semantics.
|
|
41
35
|
Runtime seccomp traps/query approval are Linux-only. Native macOS `--trap` reports
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-landstrip",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.8",
|
|
4
4
|
"description": "OS-level AI command 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-api": "^0.19.
|
|
51
|
+
"@landstrip/landstrip-api": "^0.19.8",
|
|
52
52
|
"@opentui/core": ">=0.5.12",
|
|
53
53
|
"@opentui/solid": ">=0.5.12",
|
|
54
54
|
"solid-js": "1.9.12"
|
package/shared.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
deepMergeSandboxConfig,
|
|
7
7
|
extractDomainsFromCommand,
|
|
8
8
|
isRecord,
|
|
9
|
+
mergeArray,
|
|
9
10
|
parseSandboxConfig,
|
|
10
11
|
} from '@landstrip/landstrip-api/shared';
|
|
11
12
|
import type { SandboxConfig, SandboxConfigOverrides } from '@landstrip/landstrip-api/shared';
|
|
@@ -74,7 +75,7 @@ import { createHash } from 'node:crypto';
|
|
|
74
75
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
75
76
|
import { homedir, tmpdir } from 'node:os';
|
|
76
77
|
import { fileURLToPath } from 'node:url';
|
|
77
|
-
import { dirname, join } from 'node:path';
|
|
78
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
78
79
|
|
|
79
80
|
const packageDir = dirname(fileURLToPath(import.meta.url));
|
|
80
81
|
|
|
@@ -104,8 +105,11 @@ export function deepMerge(base: SandboxConfig, overrides: SandboxConfigOverrides
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
export function getConfigPaths(baseDirectory: string): { globalPath: string; projectPath: string } {
|
|
108
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
109
|
+
const configDirectory =
|
|
110
|
+
configHome && isAbsolute(configHome) ? configHome : join(homedir(), '.config');
|
|
107
111
|
return {
|
|
108
|
-
globalPath: join(
|
|
112
|
+
globalPath: join(configDirectory, 'opencode', 'sandbox.json'),
|
|
109
113
|
projectPath: join(baseDirectory, '.opencode', 'sandbox.json'),
|
|
110
114
|
};
|
|
111
115
|
}
|
|
@@ -136,6 +140,7 @@ export function loadConfig(
|
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
const templateConfig: SandboxConfig = JSON.parse(readFileSync(templatePath, 'utf-8'));
|
|
143
|
+
templateConfig.filesystem.denyWrite.push(globalPath);
|
|
139
144
|
const globalOverrides = readConfigFile(globalPath);
|
|
140
145
|
const baseConfig = deepMerge(templateConfig, globalOverrides);
|
|
141
146
|
|
|
@@ -143,12 +148,24 @@ export function loadConfig(
|
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
export function writeConfigFile(configPath: string, update: SandboxConfigOverrides): void {
|
|
146
|
-
const
|
|
151
|
+
const next: Record<string, unknown> = { ...readConfigFile(configPath) };
|
|
152
|
+
for (const [key, value] of Object.entries(normalizeConfig(update))) {
|
|
153
|
+
if (!isRecord(value)) {
|
|
154
|
+
next[key] = value;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
158
|
+
const previous = next[key];
|
|
159
|
+
const section: Record<string, unknown> = isRecord(previous) ? { ...previous } : {};
|
|
160
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
161
|
+
if (fieldValue === undefined) continue;
|
|
162
|
+
const existing = section[field];
|
|
163
|
+
section[field] = Array.isArray(fieldValue)
|
|
164
|
+
? mergeArray(Array.isArray(existing) ? existing : [], fieldValue)
|
|
165
|
+
: fieldValue;
|
|
166
|
+
}
|
|
167
|
+
next[key] = section;
|
|
168
|
+
}
|
|
152
169
|
|
|
153
170
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
154
171
|
writeFileSync(configPath, JSON.stringify(next, null, 2) + '\n');
|