pi-landstrip 0.14.0 → 0.14.1

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 +21 -35
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -90,7 +90,7 @@ interface LandstripBashCallbacks {
90
90
  onErrorFd?: (data: Buffer) => void;
91
91
  }
92
92
 
93
- const LANDSTRIP_VERSION = [0, 14, 2] as const;
93
+ const LANDSTRIP_VERSION = [0, 14, 5] as const;
94
94
  const REQUIRED_LANDSTRIP_VERSION = LANDSTRIP_VERSION.join('.');
95
95
  const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
96
96
 
@@ -198,7 +198,7 @@ function deepMerge(base: SandboxConfig, overrides: Partial<SandboxConfig>): Sand
198
198
 
199
199
  function getConfigPaths(cwd: string): { globalPath: string; projectPath: string } {
200
200
  return {
201
- globalPath: join(homedir(), '.pi', 'agent', 'sandbox.json'),
201
+ globalPath: join(getAgentDir(), 'sandbox.json'),
202
202
  projectPath: join(cwd, '.pi', 'sandbox.json'),
203
203
  };
204
204
  }
@@ -391,40 +391,12 @@ function isFilesystemTrap(trap: LandstripTrap): trap is LandstripFilesystemTrap
391
391
  return trap.kind === 'filesystem';
392
392
  }
393
393
 
394
- function extractCandidatePaths(command: string): string[] {
395
- const paths: string[] = [];
396
- // Split on whitespace, preserving quoted strings minimally
397
- const tokens = command.match(/[^\s"']+|"[^"]*"|'[^']*'/g) ?? [];
398
- for (const token of tokens) {
399
- const clean = token.replace(/^["']|["']$/g, '').replace(/[,;]$/, '');
400
- if (isPathLike(clean)) {
401
- paths.push(clean);
402
- }
403
- }
404
- return paths;
405
- }
406
-
407
- function extractBlockedPath(output: string, cwd: string, command?: string): string | null {
394
+ function extractBlockedPath(output: string, cwd: string): string | null {
408
395
  const landstripErrors = parseLandstripTraps(output).filter(isFilesystemTrap);
409
396
  if (landstripErrors.length > 0) {
410
397
  return normalizeBlockedPath(landstripErrors[0].file, cwd);
411
398
  }
412
399
 
413
- // If landstrip reported an error but without a file field, try to
414
- // extract the blocked path from the command itself.
415
- if (landstripErrors.length > 0 && command) {
416
- const config = loadConfig(cwd);
417
- for (const candidate of extractCandidatePaths(command)) {
418
- const resolved = normalizeBlockedPath(candidate, cwd);
419
- if (
420
- matchesPattern(resolved, config.filesystem.denyRead) ||
421
- !matchesPattern(resolved, config.filesystem.allowRead)
422
- ) {
423
- return resolved;
424
- }
425
- }
426
- }
427
-
428
400
  return extractNativeDeniedPath(output, cwd);
429
401
  }
430
402
 
@@ -982,8 +954,8 @@ export function createLandstripIntegration(
982
954
  },
983
955
  filesystem: {
984
956
  denyRead: config.filesystem.denyRead,
985
- allowRead: [...config.filesystem.allowRead, ...sessionAllowedReadPaths],
986
- allowWrite: [...config.filesystem.allowWrite, ...sessionAllowedWritePaths],
957
+ allowRead: getEffectiveAllowRead(cwd),
958
+ allowWrite: getEffectiveAllowWrite(cwd),
987
959
  denyWrite: config.filesystem.denyWrite,
988
960
  },
989
961
  };
@@ -1019,10 +991,17 @@ export function createLandstripIntegration(
1019
991
  return;
1020
992
  }
1021
993
 
994
+ let settled = false;
1022
995
  const upstream = connectNet(endpoint.port, endpoint.host, () => {
996
+ settled = true;
1023
997
  client.write('HTTP/1.1 200 Connection Established\r\n\r\n');
1024
998
  pipeSockets(client, upstream, rest);
1025
999
  });
1000
+ upstream.once('error', () => {
1001
+ if (settled) return;
1002
+ settled = true;
1003
+ denyProxyRequest(client, '502 Bad Gateway');
1004
+ });
1026
1005
  }
1027
1006
 
1028
1007
  async function handleHttp(client: Socket, headerText: string, rest: Buffer): Promise<void> {
@@ -1061,10 +1040,17 @@ export function createLandstripIntegration(
1061
1040
  const rewrittenHeader = lines
1062
1041
  .filter((line) => !line.toLowerCase().startsWith('proxy-connection:'))
1063
1042
  .join('\r\n');
1043
+ let settled = false;
1064
1044
  const upstream = connectNet(port, url.hostname, () => {
1045
+ settled = true;
1065
1046
  upstream.write(`${rewrittenHeader}\r\n\r\n`);
1066
1047
  pipeSockets(client, upstream, rest);
1067
1048
  });
1049
+ upstream.once('error', () => {
1050
+ if (settled) return;
1051
+ settled = true;
1052
+ denyProxyRequest(client, '502 Bad Gateway');
1053
+ });
1068
1054
  }
1069
1055
 
1070
1056
  function handleClient(client: Socket): void {
@@ -1215,8 +1201,8 @@ export function createLandstripIntegration(
1215
1201
  const errorOutput = errorFdAcc || stderrAcc;
1216
1202
 
1217
1203
  const blockedPath =
1218
- extractBlockedPath(errorOutput, cwd, command) ??
1219
- (errorFdAcc ? extractBlockedPath(stderrAcc, cwd, command) : null);
1204
+ extractBlockedPath(errorOutput, cwd) ??
1205
+ (errorFdAcc ? extractBlockedPath(stderrAcc, cwd) : null);
1220
1206
  const blockedWritePath =
1221
1207
  extractBlockedWritePath(errorOutput, cwd) ??
1222
1208
  (errorFdAcc ? extractBlockedWritePath(stderrAcc, cwd) : null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Landlock-based sandboxing for pi with interactive permission prompts",
5
5
  "keywords": [
6
6
  "landstrip",