pi-landstrip 0.15.7 → 0.15.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.
Files changed (2) hide show
  1. package/index.ts +51 -21
  2. package/package.json +4 -4
package/index.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (C) Jarkko Sakkinen 2026
3
3
 
4
- /// <reference path="./landstrip.d.ts" />
5
-
6
4
  import type {
7
5
  AgentToolResult,
8
6
  AgentToolUpdateCallback,
@@ -70,6 +68,15 @@ interface SandboxConfig {
70
68
  filesystem: SandboxFilesystemConfig;
71
69
  }
72
70
 
71
+ type SandboxFilesystemConfigFile = Partial<SandboxFilesystemConfig>;
72
+ type SandboxNetworkConfigFile = Partial<SandboxNetworkConfig>;
73
+
74
+ interface SandboxConfigFile {
75
+ enabled?: boolean;
76
+ network?: SandboxNetworkConfigFile;
77
+ filesystem?: SandboxFilesystemConfigFile;
78
+ }
79
+
73
80
  type SandboxConfigScope = 'global' | 'project';
74
81
 
75
82
  interface LandstripPolicy {
@@ -155,8 +162,8 @@ function loadConfig(cwd: string): SandboxConfig {
155
162
  const projectConfigPath = join(cwd, '.pi', 'sandbox.json');
156
163
  const globalConfigPath = join(getAgentDir(), 'sandbox.json');
157
164
 
158
- let globalConfig: Partial<SandboxConfig> = {};
159
- let projectConfig: Partial<SandboxConfig> = {};
165
+ let globalConfig: SandboxConfigFile = {};
166
+ let projectConfig: SandboxConfigFile = {};
160
167
 
161
168
  if (existsSync(globalConfigPath)) {
162
169
  try {
@@ -182,7 +189,7 @@ function mergeArray(base: string[], override?: string[]): string[] {
182
189
  return [...new Set([...base, ...override])];
183
190
  }
184
191
 
185
- function deepMerge(base: SandboxConfig, overrides: Partial<SandboxConfig>): SandboxConfig {
192
+ function deepMerge(base: SandboxConfig, overrides: SandboxConfigFile): SandboxConfig {
186
193
  const network = overrides.network;
187
194
  const filesystem = overrides.filesystem;
188
195
 
@@ -212,7 +219,7 @@ function getConfigPaths(cwd: string): { globalPath: string; projectPath: string
212
219
  };
213
220
  }
214
221
 
215
- function readOrEmptyConfig(configPath: string): Partial<SandboxConfig> {
222
+ function readOrEmptyConfig(configPath: string): SandboxConfigFile {
216
223
  if (!existsSync(configPath)) return {};
217
224
  try {
218
225
  return JSON.parse(readFileSync(configPath, 'utf-8'));
@@ -221,7 +228,7 @@ function readOrEmptyConfig(configPath: string): Partial<SandboxConfig> {
221
228
  }
222
229
  }
223
230
 
224
- function writeConfigFile(configPath: string, config: Partial<SandboxConfig>): void {
231
+ function writeConfigFile(configPath: string, config: SandboxConfigFile): void {
225
232
  mkdirSync(dirname(configPath), { recursive: true });
226
233
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
227
234
  }
@@ -255,7 +262,7 @@ async function addDomainToConfig(configPath: string, domain: string): Promise<vo
255
262
  ...config.network,
256
263
  allowedDomains: [...existing, domain],
257
264
  deniedDomains: config.network?.deniedDomains ?? [],
258
- } as SandboxNetworkConfig;
265
+ };
259
266
  writeConfigFile(configPath, config);
260
267
  });
261
268
  }
@@ -269,7 +276,7 @@ async function addReadPathToConfig(configPath: string, pathToAdd: string): Promi
269
276
  config.filesystem = {
270
277
  ...config.filesystem,
271
278
  allowRead: [...existing, pathToAdd],
272
- } as SandboxFilesystemConfig;
279
+ };
273
280
  writeConfigFile(configPath, config);
274
281
  });
275
282
  }
@@ -283,7 +290,7 @@ async function addWritePathToConfig(configPath: string, pathToAdd: string): Prom
283
290
  config.filesystem = {
284
291
  ...config.filesystem,
285
292
  allowWrite: [...existing, pathToAdd],
286
- } as SandboxFilesystemConfig;
293
+ };
287
294
  writeConfigFile(configPath, config);
288
295
  });
289
296
  }
@@ -816,21 +823,25 @@ function proxyEnv(env: NodeJS.ProcessEnv | undefined, port: number): NodeJS.Proc
816
823
  };
817
824
  }
818
825
 
826
+ function parseProxyPort(value: string | undefined, defaultPort: number): number | null {
827
+ const rawPort = value ?? String(defaultPort);
828
+ if (!/^\d+$/.test(rawPort)) return null;
829
+
830
+ const port = Number(rawPort);
831
+ return port >= 1 && port <= 65535 ? port : null;
832
+ }
833
+
819
834
  function splitHostPort(target: string, defaultPort: number): { host: string; port: number } | null {
820
- const bracketMatch = target.match(/^\[([^\]]+)\](?::(\d+))?$/);
835
+ const bracketMatch = target.match(/^\[([^\]]+)\](?::(.*))?$/);
821
836
  if (bracketMatch) {
822
- return {
823
- host: bracketMatch[1],
824
- port: bracketMatch[2] ? Number(bracketMatch[2]) : defaultPort,
825
- };
837
+ const port = parseProxyPort(bracketMatch[2], defaultPort);
838
+ return port === null ? null : { host: bracketMatch[1], port };
826
839
  }
827
840
 
828
841
  const lastColon = target.lastIndexOf(':');
829
842
  if (lastColon > -1 && target.indexOf(':') === lastColon) {
830
- return {
831
- host: target.slice(0, lastColon),
832
- port: Number(target.slice(lastColon + 1)),
833
- };
843
+ const port = parseProxyPort(target.slice(lastColon + 1), defaultPort);
844
+ return port === null ? null : { host: target.slice(0, lastColon), port };
834
845
  }
835
846
 
836
847
  return { host: target, port: defaultPort };
@@ -853,20 +864,28 @@ function pipeSockets(client: Socket, upstream: Socket, initialData?: Buffer): vo
853
864
 
854
865
  type LandstripBashTool = ReturnType<typeof createBashToolDefinition>;
855
866
 
867
+ /** Options for creating a landstrip sandbox integration. */
856
868
  export interface LandstripIntegrationOptions {
869
+ /** Register a sandboxed bash tool when the integration is registered. */
857
870
  registerBashTool?: boolean;
871
+ /** Working directory used when registering the default bash tool. */
858
872
  cwd?: string;
859
873
  }
860
874
 
875
+ /** Landstrip sandbox integration hooks for Pi. */
861
876
  export interface LandstripIntegration {
877
+ /** Create a bash tool definition that runs commands through landstrip when enabled. */
862
878
  createBashTool(cwd: string, ctx?: ExtensionContext): LandstripBashTool;
879
+ /** Register the integration's tools, events, flags, and commands with Pi. */
863
880
  register(pi: ExtensionAPI): void;
864
881
  }
865
882
 
883
+ /** Register the landstrip extension with Pi. */
866
884
  export default function (pi: ExtensionAPI) {
867
885
  createLandstripIntegration().register(pi);
868
886
  }
869
887
 
888
+ /** Create a landstrip integration for registration or custom embedding. */
870
889
  export function createLandstripIntegration(
871
890
  options: LandstripIntegrationOptions = {},
872
891
  ): LandstripIntegration {
@@ -1037,7 +1056,13 @@ export function createLandstripIntegration(
1037
1056
  denyProxyRequest(client, '400 Bad Request');
1038
1057
  return;
1039
1058
  }
1040
- url = new URL(`http://${host}${rawTarget}`);
1059
+
1060
+ try {
1061
+ url = new URL(`http://${host}${rawTarget}`);
1062
+ } catch {
1063
+ denyProxyRequest(client, '400 Bad Request');
1064
+ return;
1065
+ }
1041
1066
  }
1042
1067
 
1043
1068
  if (!(await ensureDomainAllowed(ctx, url.hostname, cwd))) {
@@ -1045,7 +1070,12 @@ export function createLandstripIntegration(
1045
1070
  return;
1046
1071
  }
1047
1072
 
1048
- const port = Number(url.port || (url.protocol === 'https:' ? 443 : 80));
1073
+ const defaultPort = url.protocol === 'https:' ? 443 : 80;
1074
+ const port = parseProxyPort(url.port || undefined, defaultPort);
1075
+ if (port === null) {
1076
+ denyProxyRequest(client, '400 Bad Request');
1077
+ return;
1078
+ }
1049
1079
  const path = `${url.pathname}${url.search}` || '/';
1050
1080
  lines[0] = `${method} ${path} ${version}`;
1051
1081
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.15.7",
3
+ "version": "0.15.9",
4
4
  "description": "Landlock-based sandboxing for pi with interactive permission prompts",
5
5
  "keywords": [
6
6
  "landstrip",
@@ -31,17 +31,17 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@earendil-works/pi-tui": "^0.78.0",
34
- "@landstrip/landstrip": "^0.15.9"
34
+ "@landstrip/landstrip": "^0.15.15"
35
35
  },
36
36
  "devDependencies": {
37
- "@earendil-works/pi-coding-agent": "^0.79.6",
37
+ "@earendil-works/pi-coding-agent": "^0.74.2",
38
38
  "@types/node": "^24.0.0",
39
39
  "oxfmt": "^0.53.0",
40
40
  "oxlint": "^1.68.0",
41
41
  "typescript": "^5.0.0"
42
42
  },
43
43
  "peerDependencies": {
44
- "@earendil-works/pi-coding-agent": "^0.79.6"
44
+ "@earendil-works/pi-coding-agent": "^0.74.2"
45
45
  },
46
46
  "peerDependenciesMeta": {
47
47
  "@earendil-works/pi-coding-agent": {