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 CHANGED
@@ -1,29 +1,44 @@
1
1
  # pi-landstrip
2
2
 
3
- ![pi-landstrip screenshot](screenshot.png)
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
- Landlock-based sandboxing for [pi](https://pi.dev/) using
6
- [`landstrip`](https://github.com/landstrip/landstrip).
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
- ## Install
10
+ ## Installing the extension
9
11
 
10
- ```bash
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
- On unsupported platforms the extension loads but leaves sandboxing disabled.
21
+ ### Manual install
18
22
 
19
- ## Configure
23
+ Add the extension to `~/.pi/agent/settings.json` (global) or
24
+ `.pi/settings.json` (project):
20
25
 
21
- Create `.pi/sandbox.json` in a project or `~/.pi/agent/sandbox.json` globally.
22
- Project config takes precedence.
26
+ ```json
27
+ {
28
+ "packages": ["npm:pi-landstrip"]
29
+ }
30
+ ```
23
31
 
24
- See [`sandbox.json`](./sandbox.json) for a starter config.
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
- Use sandbox config to toggle sandboxing:
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
- The `/sandbox` UI updates the project config when present, otherwise the global config.
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
- ## Usage
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 Pi to show the active config and toggle sandboxing.
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 under
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 DEFAULT_CONFIG: SandboxConfig = {
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
- let globalConfig: SandboxConfigFile = {};
164
- let projectConfig: SandboxConfigFile = {};
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
- if (existsSync(globalConfigPath)) {
167
- try {
168
- globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf-8'));
169
- } catch (error) {
170
- console.error(`Warning: Could not parse ${globalConfigPath}: ${error}`);
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 deepMerge(deepMerge(DEFAULT_CONFIG, globalConfig), projectConfig);
172
+ return globalConfig;
183
173
  }
184
174
 
185
175
  function mergeArray(base: string[], override?: string[]): string[] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.16.8",
3
+ "version": "0.16.9",
4
4
  "description": "Landlock-based sandboxing for pi with interactive permission prompts",
5
5
  "keywords": [
6
6
  "landstrip",
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": ["**/.env", "**/.env.*", "**/*.pem", "**/*.key"]
15
+ "denyWrite": [
16
+ "**/.env",
17
+ "**/.env.*",
18
+ "**/*.pem",
19
+ "**/*.key",
20
+ ".pi/sandbox.json",
21
+ "~/.pi/agent/sandbox.json"
22
+ ]
16
23
  }
17
24
  }