pi-landstrip 0.16.8 → 0.16.9
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 +48 -16
- package/index.ts +18 -28
- package/package.json +1 -1
- package/sandbox.json +8 -1
package/README.md
CHANGED
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
# pi-landstrip
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`pi-landstrip` is an extension for [pi](https://pi.dev/) providing a sandbox
|
|
4
|
+
defined with a policy compatible with Anthropic's JSON format. It uses
|
|
5
|
+
[`landstrip`](https://github.com/landstrip/landstrip) to implement the sandbox.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
`pi-landstrip` has a default policy [sandbox.json](./sandbox.json), and allows
|
|
8
|
+
the define either or both global or project specific policies.
|
|
7
9
|
|
|
8
|
-
##
|
|
10
|
+
## Installing the extension
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
### Automatic install
|
|
13
|
+
|
|
14
|
+
```
|
|
11
15
|
pi install npm:pi-landstrip
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
This installs `pi-landstrip` and its `@landstrip/landstrip` dependency, which
|
|
15
19
|
includes platform-specific native binaries for Linux, macOS, and Windows.
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
### Manual install
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
Add the extension to `~/.pi/agent/settings.json` (global) or
|
|
24
|
+
`.pi/settings.json` (project):
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"packages": ["npm:pi-landstrip"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
Alternatively, drop the extension under `~/.pi/agent/extensions/` (global) or
|
|
33
|
+
`.pi/extensions/` (project). See the pi
|
|
34
|
+
[extensions](https://pi.dev/docs/latest/extensions) documentation for details.
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
On unsupported platforms the extension loads but leaves sandboxing disabled.
|
|
37
|
+
|
|
38
|
+
## Disable
|
|
39
|
+
|
|
40
|
+
Use the `--no-sandbox` flag, or set `enabled` to `false` in the sandbox
|
|
41
|
+
config:
|
|
27
42
|
|
|
28
43
|
```json
|
|
29
44
|
{
|
|
@@ -31,17 +46,34 @@ Use sandbox config to toggle sandboxing:
|
|
|
31
46
|
}
|
|
32
47
|
```
|
|
33
48
|
|
|
34
|
-
Project config overrides global config.
|
|
35
|
-
|
|
49
|
+
Project config overrides global config. The `/sandbox` UI updates the project
|
|
50
|
+
config when present, otherwise the global config.
|
|
51
|
+
|
|
52
|
+
## Behavior
|
|
53
|
+
|
|
54
|
+
When pi asks for a sandboxed permission, the extension emits a host
|
|
55
|
+
notification. After that the extension opens a dialog with the choices to allow
|
|
56
|
+
once, allow for the session, persist for the project, persist globally, or
|
|
57
|
+
reject. The dialog shows the exact path or domain being approved.
|
|
58
|
+
|
|
59
|
+
Project approvals are written to `.pi/sandbox.json`; global approvals are
|
|
60
|
+
written to `~/.pi/agent/sandbox.json`.
|
|
36
61
|
|
|
37
|
-
|
|
62
|
+
When pi runs a bash command, the extension wraps it in `landstrip`. This
|
|
63
|
+
applies to both the AI `bash` tool calls and manually typed shell-mode commands
|
|
64
|
+
(`!` and `!!`). Network traffic is routed through an allowlist proxy when
|
|
65
|
+
network access is off, and read/write tool access is blocked outside the
|
|
66
|
+
configured filesystem allowlists. The default policy is strict: network access
|
|
67
|
+
is off unless domains are allowed, reads are limited to the project,
|
|
68
|
+
`~/.gitconfig`, and `/dev/null`, and writes are limited to the project and
|
|
69
|
+
`/dev/null`.
|
|
38
70
|
|
|
39
|
-
Use `/sandbox` inside
|
|
71
|
+
Use `/sandbox` inside pi to show the active config and toggle sandboxing.
|
|
40
72
|
|
|
41
73
|
## License
|
|
42
74
|
|
|
43
75
|
`pi-landstrip` is licensed under `MIT`. See [LICENSE](LICENSE) for more
|
|
44
76
|
information.
|
|
45
77
|
|
|
46
|
-
The bundled `@landstrip/landstrip` package is licensed
|
|
78
|
+
The bundled `@landstrip/landstrip` package is licensed separately as
|
|
47
79
|
`Apache-2.0 AND LGPL-2.1-or-later`.
|
package/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
Socket as NetSocket,
|
|
20
20
|
} from 'node:net';
|
|
21
21
|
import { homedir, tmpdir } from 'node:os';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
22
23
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
23
24
|
import { URL } from 'node:url';
|
|
24
25
|
|
|
@@ -108,24 +109,7 @@ const LANDSTRIP_VERSION = [0, 16, 4] as const;
|
|
|
108
109
|
const REQUIRED_LANDSTRIP_VERSION = LANDSTRIP_VERSION.join('.');
|
|
109
110
|
const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
|
|
110
111
|
|
|
111
|
-
const
|
|
112
|
-
enabled: true,
|
|
113
|
-
network: {
|
|
114
|
-
allowNetwork: false,
|
|
115
|
-
allowLocalBinding: false,
|
|
116
|
-
allowAllUnixSockets: false,
|
|
117
|
-
allowUnixSockets: [],
|
|
118
|
-
allowedDomains: [],
|
|
119
|
-
deniedDomains: [],
|
|
120
|
-
},
|
|
121
|
-
filesystem: {
|
|
122
|
-
denyRead: ['/Users', '/home'],
|
|
123
|
-
allowRead: ['.', '~/.gitconfig', '~/.config/git/config', '/dev/null'],
|
|
124
|
-
allowWrite: ['.', '/dev/null'],
|
|
125
|
-
denyWrite: ['**/.env', '**/.env.*', '**/*.pem', '**/*.key'],
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
|
|
112
|
+
const packageDir = dirname(fileURLToPath(import.meta.url));
|
|
129
113
|
type PermissionChoice = 'abort' | 'session' | 'project' | 'global';
|
|
130
114
|
type NotificationLevel = Parameters<ExtensionContext['ui']['notify']>[1];
|
|
131
115
|
|
|
@@ -160,26 +144,32 @@ function loadConfig(cwd: string): SandboxConfig {
|
|
|
160
144
|
const projectConfigPath = join(cwd, '.pi', 'sandbox.json');
|
|
161
145
|
const globalConfigPath = join(getAgentDir(), 'sandbox.json');
|
|
162
146
|
|
|
163
|
-
|
|
164
|
-
|
|
147
|
+
if (!existsSync(globalConfigPath)) {
|
|
148
|
+
const templatePath = join(packageDir, 'sandbox.json');
|
|
149
|
+
mkdirSync(dirname(globalConfigPath), { recursive: true });
|
|
150
|
+
writeFileSync(globalConfigPath, readFileSync(templatePath, 'utf-8'), 'utf-8');
|
|
151
|
+
}
|
|
165
152
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
153
|
+
let globalConfig: SandboxConfig = JSON.parse(
|
|
154
|
+
readFileSync(join(packageDir, 'sandbox.json'), 'utf-8'),
|
|
155
|
+
);
|
|
156
|
+
try {
|
|
157
|
+
const override = JSON.parse(readFileSync(globalConfigPath, 'utf-8'));
|
|
158
|
+
globalConfig = deepMerge(globalConfig, override);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`Warning: Could not parse ${globalConfigPath}: ${error}`);
|
|
172
161
|
}
|
|
173
162
|
|
|
174
163
|
if (existsSync(projectConfigPath)) {
|
|
175
164
|
try {
|
|
176
|
-
projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf-8'));
|
|
165
|
+
const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf-8'));
|
|
166
|
+
return deepMerge(globalConfig, projectConfig);
|
|
177
167
|
} catch (error) {
|
|
178
168
|
console.error(`Warning: Could not parse ${projectConfigPath}: ${error}`);
|
|
179
169
|
}
|
|
180
170
|
}
|
|
181
171
|
|
|
182
|
-
return
|
|
172
|
+
return globalConfig;
|
|
183
173
|
}
|
|
184
174
|
|
|
185
175
|
function mergeArray(base: string[], override?: string[]): string[] {
|
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
|
+
".pi/sandbox.json",
|
|
21
|
+
"~/.pi/agent/sandbox.json"
|
|
22
|
+
]
|
|
16
23
|
}
|
|
17
24
|
}
|