pi-landstrip 0.3.2 → 0.3.3
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/index.ts +51 -32
- package/package.json +2 -2
- package/sandbox.json +1 -0
package/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ interface SandboxFilesystemConfig {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
interface SandboxNetworkConfig {
|
|
50
|
+
allowNetwork: boolean;
|
|
50
51
|
allowLocalBinding: boolean;
|
|
51
52
|
allowAllUnixSockets: boolean;
|
|
52
53
|
allowUnixSockets: string[];
|
|
@@ -62,10 +63,11 @@ interface SandboxConfig {
|
|
|
62
63
|
|
|
63
64
|
interface LandstripPolicy {
|
|
64
65
|
network: {
|
|
66
|
+
allowNetwork: boolean;
|
|
65
67
|
allowLocalBinding: boolean;
|
|
66
68
|
allowAllUnixSockets: boolean;
|
|
67
69
|
allowUnixSockets: string[];
|
|
68
|
-
httpProxyPort
|
|
70
|
+
httpProxyPort?: number;
|
|
69
71
|
};
|
|
70
72
|
filesystem: SandboxFilesystemConfig;
|
|
71
73
|
}
|
|
@@ -78,12 +80,13 @@ interface LandstripErrorResponse {
|
|
|
78
80
|
message: string;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
const LANDSTRIP_VERSION = [0, 9,
|
|
83
|
+
const LANDSTRIP_VERSION = [0, 9, 7] as const;
|
|
82
84
|
const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
|
|
83
85
|
|
|
84
86
|
const DEFAULT_CONFIG: SandboxConfig = {
|
|
85
87
|
enabled: true,
|
|
86
88
|
network: {
|
|
89
|
+
allowNetwork: false,
|
|
87
90
|
allowLocalBinding: false,
|
|
88
91
|
allowAllUnixSockets: false,
|
|
89
92
|
allowUnixSockets: [],
|
|
@@ -694,15 +697,16 @@ export function createLandstripIntegration(
|
|
|
694
697
|
return true;
|
|
695
698
|
}
|
|
696
699
|
|
|
697
|
-
function buildLandstripPolicy(cwd: string, proxyPort: number): LandstripPolicy {
|
|
700
|
+
function buildLandstripPolicy(cwd: string, proxyPort: number | null): LandstripPolicy {
|
|
698
701
|
const config = loadConfig(cwd);
|
|
699
702
|
|
|
700
703
|
return {
|
|
701
704
|
network: {
|
|
705
|
+
allowNetwork: config.network.allowNetwork,
|
|
702
706
|
allowLocalBinding: config.network.allowLocalBinding,
|
|
703
707
|
allowAllUnixSockets: config.network.allowAllUnixSockets,
|
|
704
708
|
allowUnixSockets: config.network.allowUnixSockets,
|
|
705
|
-
httpProxyPort: proxyPort,
|
|
709
|
+
...(proxyPort !== null ? { httpProxyPort: proxyPort } : {}),
|
|
706
710
|
},
|
|
707
711
|
filesystem: {
|
|
708
712
|
denyRead: config.filesystem.denyRead,
|
|
@@ -713,7 +717,7 @@ export function createLandstripIntegration(
|
|
|
713
717
|
};
|
|
714
718
|
}
|
|
715
719
|
|
|
716
|
-
function writePolicyFile(cwd: string, proxyPort: number): { dir: string; path: string } {
|
|
720
|
+
function writePolicyFile(cwd: string, proxyPort: number | null): { dir: string; path: string } {
|
|
717
721
|
const dir = mkdtempSync(join(tmpdir(), 'pi-landstrip-'));
|
|
718
722
|
const path = join(dir, 'policy.json');
|
|
719
723
|
writeFileSync(
|
|
@@ -858,8 +862,11 @@ export function createLandstripIntegration(
|
|
|
858
862
|
if (!existsSync(cwd)) throw new Error(`Working directory does not exist: ${cwd}`);
|
|
859
863
|
|
|
860
864
|
const { shell, args } = getShellConfig(SettingsManager.create(cwd).getShellPath());
|
|
861
|
-
const
|
|
862
|
-
const
|
|
865
|
+
const config = loadConfig(cwd);
|
|
866
|
+
const allowNetwork = config.network.allowNetwork;
|
|
867
|
+
const proxy = allowNetwork ? null : await startProxy(ctx, cwd);
|
|
868
|
+
const proxyPort = proxy ? proxy.port : null;
|
|
869
|
+
const policy = writePolicyFile(cwd, proxyPort);
|
|
863
870
|
const landstripArgs = ['-p', policy.path, shell, ...args, command];
|
|
864
871
|
|
|
865
872
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -872,13 +879,13 @@ export function createLandstripIntegration(
|
|
|
872
879
|
cleaned = true;
|
|
873
880
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
874
881
|
signal?.removeEventListener('abort', onAbort);
|
|
875
|
-
void proxy
|
|
882
|
+
void proxy?.stop();
|
|
876
883
|
rmSync(policy.dir, { recursive: true, force: true });
|
|
877
884
|
};
|
|
878
885
|
|
|
879
886
|
const child = spawn(binaryPath(), landstripArgs, {
|
|
880
887
|
cwd,
|
|
881
|
-
env: proxyEnv(env, proxy
|
|
888
|
+
env: allowNetwork ? { ...process.env, ...env } : proxyEnv(env, proxy!.port),
|
|
882
889
|
detached: true,
|
|
883
890
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
884
891
|
});
|
|
@@ -1005,6 +1012,10 @@ export function createLandstripIntegration(
|
|
|
1005
1012
|
}
|
|
1006
1013
|
|
|
1007
1014
|
function warnIfAllDomainsAllowed(ctx: ExtensionContext, config: SandboxConfig): void {
|
|
1015
|
+
if (config.network.allowNetwork) {
|
|
1016
|
+
ctx.ui.notify('Network sandbox is disabled because network.allowNetwork is true.', 'warning');
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1008
1019
|
if (!allowsAllDomains(config.network.allowedDomains)) return;
|
|
1009
1020
|
ctx.ui.notify(
|
|
1010
1021
|
'Network sandbox allows all domains because network.allowedDomains contains "*".',
|
|
@@ -1013,9 +1024,11 @@ export function createLandstripIntegration(
|
|
|
1013
1024
|
}
|
|
1014
1025
|
|
|
1015
1026
|
function enableStatus(ctx: ExtensionContext, config: SandboxConfig): void {
|
|
1016
|
-
const networkLabel =
|
|
1017
|
-
? '
|
|
1018
|
-
:
|
|
1027
|
+
const networkLabel = config.network.allowNetwork
|
|
1028
|
+
? 'unrestricted'
|
|
1029
|
+
: allowsAllDomains(config.network.allowedDomains)
|
|
1030
|
+
? 'all domains'
|
|
1031
|
+
: `${config.network.allowedDomains.length} domains`;
|
|
1019
1032
|
ctx.ui.setStatus(
|
|
1020
1033
|
'sandbox',
|
|
1021
1034
|
ctx.ui.theme.fg(
|
|
@@ -1049,7 +1062,7 @@ export function createLandstripIntegration(
|
|
|
1049
1062
|
if (!hasMinimumVersion(version, LANDSTRIP_VERSION)) {
|
|
1050
1063
|
sandboxEnabled = false;
|
|
1051
1064
|
sandboxReady = false;
|
|
1052
|
-
ctx.ui.notify(`landstrip 0.9.
|
|
1065
|
+
ctx.ui.notify(`landstrip 0.9.7 or newer is required; found: ${version}`, 'error');
|
|
1053
1066
|
return false;
|
|
1054
1067
|
}
|
|
1055
1068
|
|
|
@@ -1093,18 +1106,21 @@ export function createLandstripIntegration(
|
|
|
1093
1106
|
|
|
1094
1107
|
pi.on('user_bash', async (event, ctx) => {
|
|
1095
1108
|
if (!sandboxEnabled || !sandboxReady) return;
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1109
|
+
const config = loadConfig(ctx.cwd);
|
|
1110
|
+
if (!config.enabled) return;
|
|
1111
|
+
|
|
1112
|
+
if (!config.network.allowNetwork) {
|
|
1113
|
+
const blockedDomain = await preflightCommandDomains(event.command, ctx);
|
|
1114
|
+
if (blockedDomain) {
|
|
1115
|
+
return {
|
|
1116
|
+
result: {
|
|
1117
|
+
output: `Blocked: "${blockedDomain}" is not allowed by the sandbox. Use /sandbox to review your config.`,
|
|
1118
|
+
exitCode: 1,
|
|
1119
|
+
cancelled: false,
|
|
1120
|
+
truncated: false,
|
|
1121
|
+
},
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1108
1124
|
}
|
|
1109
1125
|
|
|
1110
1126
|
return { operations: createLandstripBashOps(ctx) };
|
|
@@ -1119,12 +1135,14 @@ export function createLandstripIntegration(
|
|
|
1119
1135
|
const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
|
|
1120
1136
|
|
|
1121
1137
|
if (sandboxReady && isToolCallEventType('bash', event)) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1138
|
+
if (!config.network.allowNetwork) {
|
|
1139
|
+
const blockedDomain = await preflightCommandDomains(event.input.command, ctx);
|
|
1140
|
+
if (blockedDomain) {
|
|
1141
|
+
return {
|
|
1142
|
+
block: true,
|
|
1143
|
+
reason: `Network access to "${blockedDomain}" is blocked by the sandbox.`,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1128
1146
|
}
|
|
1129
1147
|
}
|
|
1130
1148
|
|
|
@@ -1231,7 +1249,8 @@ export function createLandstripIntegration(
|
|
|
1231
1249
|
` Global config: ${globalPath}`,
|
|
1232
1250
|
` landstrip: ${binaryPath()}`,
|
|
1233
1251
|
'',
|
|
1234
|
-
|
|
1252
|
+
`Network (bash ${config.network.allowNetwork ? 'unrestricted' : 'through HTTP proxy'}):`,
|
|
1253
|
+
` Allow network: ${config.network.allowNetwork}`,
|
|
1235
1254
|
` Allowed domains: ${config.network.allowedDomains.join(', ') || '(none)'}`,
|
|
1236
1255
|
` Denied domains: ${config.network.deniedDomains.join(', ') || '(none)'}`,
|
|
1237
1256
|
...(sessionAllowedDomains.length > 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Landlock-based sandboxing for pi with interactive permission prompts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@earendil-works/pi-tui": "^0.78.0",
|
|
34
|
-
"@jarkkojs/landstrip": "^0.9.
|
|
34
|
+
"@jarkkojs/landstrip": "^0.9.7"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@earendil-works/pi-coding-agent": "^0.78.0",
|