opencode-landstrip 0.16.12 → 0.16.14
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 +40 -47
- package/package.json +1 -1
- package/sandbox.json +8 -1
- package/shared.ts +18 -25
package/README.md
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
# opencode-landstrip
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
`opencode-landstrip` is a plugin for [OpenCode](https://opencode.ai/) providing
|
|
4
|
+
a sandbox defined with a policy compatible with Anthropic's JSON format. It uses
|
|
5
|
+
[`landstrip`](https://github.com/landstrip/landstrip) to implement the sandbox.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
`opencode-landstrip` has a default policy [sandbox.json](./sandbox.json), and
|
|
8
|
+
allows the define either or both global or project specific policies.
|
|
9
|
+
|
|
10
|
+
## Installing the plugin
|
|
11
|
+
|
|
12
|
+
### Automatic install
|
|
13
|
+
|
|
14
|
+
Project specific install:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
opencode plugin install opencode-landstrip
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Global install:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
opencode plugin install opencode-landstrip --global
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Manual install
|
|
27
|
+
|
|
28
|
+
These changes are applied to OpenCode's configuration directories
|
|
7
29
|
|
|
8
30
|
Add the plugin to `opencode.json`:
|
|
9
31
|
|
|
@@ -14,8 +36,7 @@ Add the plugin to `opencode.json`:
|
|
|
14
36
|
}
|
|
15
37
|
```
|
|
16
38
|
|
|
17
|
-
Add
|
|
18
|
-
manually:
|
|
39
|
+
Add TUI entry point to `tui.json`:
|
|
19
40
|
|
|
20
41
|
```json
|
|
21
42
|
{
|
|
@@ -24,59 +45,31 @@ manually:
|
|
|
24
45
|
}
|
|
25
46
|
```
|
|
26
47
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
This installs `opencode-landstrip` and its `@landstrip/landstrip` dependency, which
|
|
30
|
-
includes platform-specific native binaries for Linux, macOS, and Windows.
|
|
31
|
-
|
|
32
|
-
Requires OpenCode `1.17.7` or newer.
|
|
33
|
-
|
|
34
|
-
On unsupported platforms the plugin loads but leaves sandboxing disabled.
|
|
35
|
-
|
|
36
|
-
## Configure
|
|
37
|
-
|
|
38
|
-
Create `.opencode/sandbox.json` in a project or
|
|
39
|
-
`~/.config/opencode/sandbox.json` globally. Project config takes precedence and
|
|
40
|
-
array fields are merged with global/default values.
|
|
48
|
+
The plugin can be later on disabled as follows:
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"$schema": "https://opencode.ai/config.json",
|
|
53
|
+
"plugin": [["opencode-landstrip", { "enabled": false }]]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
43
56
|
|
|
44
57
|
## Behavior
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`/dev/null`.
|
|
52
|
-
|
|
53
|
-
Run `/sandbox` in the TUI to inspect the active sandbox configuration. A compact
|
|
54
|
-
status badge in the prompt area shows whether the sandbox is active and whether
|
|
55
|
-
network is proxied or open.
|
|
56
|
-
|
|
57
|
-
When OpenCode asks for a sandboxed permission, the TUI plugin plays the host's
|
|
58
|
-
permission sound and desktop notification, then opens a single dialog with
|
|
59
|
-
choices to allow once, allow for the session, persist for the project, persist
|
|
60
|
-
globally, or reject. The dialog shows the exact path or domain being approved.
|
|
59
|
+
When OpenCode asks for a sandboxed permission, the plugin emits a host
|
|
60
|
+
notification. After that the plugin opens a dialog with the choices to allow
|
|
61
|
+
once, allow for the session, persist for the project, persist globally, or
|
|
62
|
+
reject. The dialog shows the exact path or domain being approved.
|
|
63
|
+
|
|
61
64
|
Project approvals are written to `.opencode/sandbox.json`; global approvals are
|
|
62
|
-
written to `~/.config/opencode/sandbox.json`.
|
|
65
|
+
written to `~/.config/opencode/sandbox.json`. When the global configuration is
|
|
66
|
+
initially written it acquires the copy of the default sandbox configuration.
|
|
63
67
|
|
|
64
68
|
OpenCode's current plugin API allows wrapping AI `bash` tool calls, but does not
|
|
65
69
|
allow a plugin to replace manually typed shell-mode commands with a landstrip
|
|
66
70
|
wrapper. Those commands can still receive the proxy environment from OpenCode,
|
|
67
71
|
but they are not process-sandboxed by this plugin.
|
|
68
72
|
|
|
69
|
-
## Disable
|
|
70
|
-
|
|
71
|
-
Set `enabled` to `false` in `sandbox.json`, or pass plugin options:
|
|
72
|
-
|
|
73
|
-
```json
|
|
74
|
-
{
|
|
75
|
-
"$schema": "https://opencode.ai/config.json",
|
|
76
|
-
"plugin": [["opencode-landstrip", { "enabled": false }]]
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
73
|
## License
|
|
81
74
|
|
|
82
75
|
`opencode-landstrip` is licensed under `MIT`. See [LICENSE](LICENSE) for more
|
package/package.json
CHANGED
package/sandbox.json
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
"denyRead": ["/Users", "/home"],
|
|
13
13
|
"allowRead": [".", "~/.gitconfig", "~/.config/git/config", "/dev/null"],
|
|
14
14
|
"allowWrite": [".", "/dev/null"],
|
|
15
|
-
"denyWrite": [
|
|
15
|
+
"denyWrite": [
|
|
16
|
+
"**/.env",
|
|
17
|
+
"**/.env.*",
|
|
18
|
+
"**/*.pem",
|
|
19
|
+
"**/*.key",
|
|
20
|
+
".opencode/sandbox.json",
|
|
21
|
+
"~/.config/opencode/sandbox.json"
|
|
22
|
+
]
|
|
16
23
|
}
|
|
17
24
|
}
|
package/shared.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { binaryPath } from '@landstrip/landstrip';
|
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { homedir, tmpdir } from 'node:os';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import { dirname, join } from 'node:path';
|
|
10
11
|
|
|
11
12
|
export interface SandboxFilesystemConfig {
|
|
@@ -36,23 +37,7 @@ export interface SandboxConfigOverrides {
|
|
|
36
37
|
filesystem?: Partial<SandboxFilesystemConfig>;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
enabled: true,
|
|
41
|
-
network: {
|
|
42
|
-
allowNetwork: false,
|
|
43
|
-
allowLocalBinding: false,
|
|
44
|
-
allowAllUnixSockets: false,
|
|
45
|
-
allowUnixSockets: [],
|
|
46
|
-
allowedDomains: [],
|
|
47
|
-
deniedDomains: [],
|
|
48
|
-
},
|
|
49
|
-
filesystem: {
|
|
50
|
-
denyRead: ['/Users', '/home'],
|
|
51
|
-
allowRead: ['.', '~/.gitconfig', '~/.config/git/config', '/dev/null'],
|
|
52
|
-
allowWrite: ['.', '/dev/null'],
|
|
53
|
-
denyWrite: ['**/.env', '**/.env.*', '**/*.pem', '**/*.key'],
|
|
54
|
-
},
|
|
55
|
-
};
|
|
40
|
+
const packageDir = dirname(fileURLToPath(import.meta.url));
|
|
56
41
|
|
|
57
42
|
const LANDSTRIP_PACKAGE_NAMES = new Set([
|
|
58
43
|
'@landstrip/landstrip',
|
|
@@ -188,13 +173,18 @@ export function loadConfig(
|
|
|
188
173
|
optionOverrides: SandboxConfigOverrides,
|
|
189
174
|
): SandboxConfig {
|
|
190
175
|
const { globalPath, projectPath } = getConfigPaths(baseDirectory);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
),
|
|
196
|
-
|
|
197
|
-
|
|
176
|
+
const templatePath = join(packageDir, 'sandbox.json');
|
|
177
|
+
|
|
178
|
+
if (!existsSync(globalPath)) {
|
|
179
|
+
mkdirSync(dirname(globalPath), { recursive: true });
|
|
180
|
+
writeFileSync(globalPath, readFileSync(templatePath, 'utf-8'), 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const templateConfig: SandboxConfig = JSON.parse(readFileSync(templatePath, 'utf-8'));
|
|
184
|
+
const globalOverrides = readConfigFile(globalPath) ?? {};
|
|
185
|
+
const baseConfig = deepMerge(templateConfig, globalOverrides);
|
|
186
|
+
|
|
187
|
+
return deepMerge(deepMerge(baseConfig, readConfigFile(projectPath) ?? {}), optionOverrides);
|
|
198
188
|
}
|
|
199
189
|
|
|
200
190
|
export function writeConfigFile(configPath: string, update: SandboxConfigOverrides): void {
|
|
@@ -203,7 +193,10 @@ export function writeConfigFile(configPath: string, update: SandboxConfigOverrid
|
|
|
203
193
|
throw new Error(`Config file ${configPath} is corrupted; refusing to overwrite`);
|
|
204
194
|
}
|
|
205
195
|
|
|
206
|
-
const
|
|
196
|
+
const templateConfig: SandboxConfig = JSON.parse(
|
|
197
|
+
readFileSync(join(packageDir, 'sandbox.json'), 'utf-8'),
|
|
198
|
+
);
|
|
199
|
+
const next = deepMerge(deepMerge(templateConfig, current), update);
|
|
207
200
|
|
|
208
201
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
209
202
|
writeFileSync(configPath, JSON.stringify(next, null, 2) + '\n');
|