pi-landstrip 0.3.0 → 0.3.2
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 +226 -164
- package/landstrip.d.ts +3 -0
- package/package.json +3 -2
package/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Copyright (C) Jarkko Sakkinen 2026
|
|
3
3
|
|
|
4
|
+
/// <reference path="./landstrip.d.ts" />
|
|
5
|
+
|
|
4
6
|
import type {
|
|
5
7
|
AgentToolResult,
|
|
6
8
|
AgentToolUpdateCallback,
|
|
@@ -72,12 +74,11 @@ interface LandstripErrorResponse {
|
|
|
72
74
|
category: 'policy' | 'tool' | 'platform' | 'system';
|
|
73
75
|
file?: string;
|
|
74
76
|
program?: string;
|
|
75
|
-
|
|
76
|
-
kind?: 'launch' | 'encoding';
|
|
77
|
+
type?: 'filesystem' | 'network' | 'platform' | 'launch' | 'encoding';
|
|
77
78
|
message: string;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
const LANDSTRIP_VERSION = [0, 9,
|
|
81
|
+
const LANDSTRIP_VERSION = [0, 9, 5] as const;
|
|
81
82
|
const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
|
|
82
83
|
|
|
83
84
|
const DEFAULT_CONFIG: SandboxConfig = {
|
|
@@ -353,6 +354,9 @@ function parseLandstripErrors(output: string): LandstripErrorResponse[] {
|
|
|
353
354
|
parsed !== null &&
|
|
354
355
|
typeof parsed.category === 'string' &&
|
|
355
356
|
['policy', 'tool', 'platform', 'system'].includes(parsed.category) &&
|
|
357
|
+
(parsed.type === undefined ||
|
|
358
|
+
(typeof parsed.type === 'string' &&
|
|
359
|
+
['filesystem', 'network', 'platform', 'launch', 'encoding'].includes(parsed.type))) &&
|
|
356
360
|
typeof parsed.message === 'string' &&
|
|
357
361
|
parsed.message.length > 0
|
|
358
362
|
) {
|
|
@@ -371,14 +375,14 @@ function formatLandstripErrors(errors: LandstripErrorResponse[]): string {
|
|
|
371
375
|
.map((err) => {
|
|
372
376
|
const parts: string[] = [`landstrip: ${err.category}`];
|
|
373
377
|
|
|
374
|
-
if (err.
|
|
375
|
-
parts.push(`(${err.
|
|
378
|
+
if (err.file) {
|
|
379
|
+
parts.push(` (${err.file})`);
|
|
376
380
|
}
|
|
377
381
|
if (err.program) {
|
|
378
382
|
parts.push(` ${err.program}`);
|
|
379
383
|
}
|
|
380
|
-
if (err.
|
|
381
|
-
parts.push(`:${err.
|
|
384
|
+
if (err.type) {
|
|
385
|
+
parts.push(`:${err.type}`);
|
|
382
386
|
}
|
|
383
387
|
parts.push(`: ${err.message}`);
|
|
384
388
|
|
|
@@ -591,16 +595,33 @@ function pipeSockets(client: Socket, upstream: Socket, initialData?: Buffer): vo
|
|
|
591
595
|
upstream.pipe(client);
|
|
592
596
|
}
|
|
593
597
|
|
|
598
|
+
type LandstripBashTool = ReturnType<typeof createBashToolDefinition>;
|
|
599
|
+
|
|
600
|
+
export interface LandstripIntegrationOptions {
|
|
601
|
+
registerBashTool?: boolean;
|
|
602
|
+
cwd?: string;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export interface LandstripIntegration {
|
|
606
|
+
createBashTool(cwd: string, ctx?: ExtensionContext): LandstripBashTool;
|
|
607
|
+
register(pi: ExtensionAPI): void;
|
|
608
|
+
}
|
|
609
|
+
|
|
594
610
|
export default function (pi: ExtensionAPI) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
611
|
+
createLandstripIntegration().register(pi);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function createLandstripIntegration(
|
|
615
|
+
options: LandstripIntegrationOptions = {},
|
|
616
|
+
): LandstripIntegration {
|
|
617
|
+
const shouldRegisterBashTool = options.registerBashTool ?? true;
|
|
618
|
+
const localCwd = options.cwd ?? process.cwd();
|
|
600
619
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
620
|
+
function createPlainBashTool(cwd: string): LandstripBashTool {
|
|
621
|
+
return createBashToolDefinition(cwd, {
|
|
622
|
+
shellPath: SettingsManager.create(cwd).getShellPath(),
|
|
623
|
+
});
|
|
624
|
+
}
|
|
604
625
|
|
|
605
626
|
let sandboxEnabled = false;
|
|
606
627
|
let sandboxReady = false;
|
|
@@ -828,12 +849,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
828
849
|
});
|
|
829
850
|
}
|
|
830
851
|
|
|
831
|
-
function createLandstripBashOps(
|
|
852
|
+
function createLandstripBashOps(
|
|
853
|
+
ctx: ExtensionContext,
|
|
854
|
+
onStderr: (data: Buffer) => void = () => {},
|
|
855
|
+
): BashOperations {
|
|
832
856
|
return {
|
|
833
857
|
async exec(command, cwd, { onData, signal, timeout, env }) {
|
|
834
858
|
if (!existsSync(cwd)) throw new Error(`Working directory does not exist: ${cwd}`);
|
|
835
859
|
|
|
836
|
-
const { shell, args } = getShellConfig(
|
|
860
|
+
const { shell, args } = getShellConfig(SettingsManager.create(cwd).getShellPath());
|
|
837
861
|
const proxy = await startProxy(ctx, cwd);
|
|
838
862
|
const policy = writePolicyFile(cwd, proxy.port);
|
|
839
863
|
const landstripArgs = ['-p', policy.path, shell, ...args, command];
|
|
@@ -881,7 +905,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
881
905
|
|
|
882
906
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
883
907
|
child.stdout?.on('data', onData);
|
|
884
|
-
child.stderr?.on('data',
|
|
908
|
+
child.stderr?.on('data', (data: Buffer) => {
|
|
909
|
+
onStderr(data);
|
|
910
|
+
onData(data);
|
|
911
|
+
});
|
|
885
912
|
|
|
886
913
|
child.on('error', (error) => {
|
|
887
914
|
cleanup();
|
|
@@ -910,18 +937,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
910
937
|
onUpdate: AgentToolUpdateCallback<BashToolDetails | undefined> | undefined,
|
|
911
938
|
ctx: ExtensionContext,
|
|
912
939
|
): Promise<AgentToolResult<BashToolDetails | undefined>> {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
940
|
+
let landstripStderr = '';
|
|
941
|
+
const sandboxedBash = createBashToolDefinition(ctx.cwd, {
|
|
942
|
+
operations: createLandstripBashOps(ctx, (data) => {
|
|
943
|
+
landstripStderr += data.toString('utf8');
|
|
944
|
+
}),
|
|
945
|
+
shellPath: SettingsManager.create(ctx.cwd).getShellPath(),
|
|
916
946
|
});
|
|
917
947
|
|
|
918
948
|
const run = () => sandboxedBash.execute(id, params, signal, onUpdate, ctx);
|
|
919
|
-
|
|
949
|
+
let result: AgentToolResult<BashToolDetails | undefined>;
|
|
950
|
+
try {
|
|
951
|
+
result = await run();
|
|
952
|
+
} catch (error) {
|
|
953
|
+
const landstripErrors = parseLandstripErrors(landstripStderr);
|
|
954
|
+
if (landstripErrors.length > 0) {
|
|
955
|
+
throw new Error(formatLandstripErrors(landstripErrors));
|
|
956
|
+
}
|
|
957
|
+
throw error;
|
|
958
|
+
}
|
|
920
959
|
const outputText = result.content
|
|
921
960
|
.filter((content) => content.type === 'text')
|
|
922
961
|
.map((content) => content.text)
|
|
923
962
|
.join('\n');
|
|
924
|
-
const landstripErrors = parseLandstripErrors(
|
|
963
|
+
const landstripErrors = parseLandstripErrors(landstripStderr);
|
|
925
964
|
if (landstripErrors.length > 0) {
|
|
926
965
|
const message = formatLandstripErrors(landstripErrors);
|
|
927
966
|
result.content.unshift({ type: 'text', text: `\n${message}\n` });
|
|
@@ -1010,7 +1049,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1010
1049
|
if (!hasMinimumVersion(version, LANDSTRIP_VERSION)) {
|
|
1011
1050
|
sandboxEnabled = false;
|
|
1012
1051
|
sandboxReady = false;
|
|
1013
|
-
ctx.ui.notify(`landstrip 0.9.
|
|
1052
|
+
ctx.ui.notify(`landstrip 0.9.5 or newer is required; found: ${version}`, 'error');
|
|
1014
1053
|
return false;
|
|
1015
1054
|
}
|
|
1016
1055
|
|
|
@@ -1021,178 +1060,201 @@ export default function (pi: ExtensionAPI) {
|
|
|
1021
1060
|
return true;
|
|
1022
1061
|
}
|
|
1023
1062
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
label: 'bash (landstrip)',
|
|
1027
|
-
async execute(id, params, signal, onUpdate, ctx) {
|
|
1028
|
-
if (!sandboxEnabled || !sandboxReady)
|
|
1029
|
-
return localBash.execute(id, params, signal, onUpdate, ctx);
|
|
1030
|
-
|
|
1031
|
-
return runBashWithOptionalRetry(id, params, signal, onUpdate, ctx);
|
|
1032
|
-
},
|
|
1033
|
-
});
|
|
1063
|
+
function createBashTool(cwd: string, ctx?: ExtensionContext): LandstripBashTool {
|
|
1064
|
+
const localBash = createPlainBashTool(cwd);
|
|
1034
1065
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
},
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1066
|
+
return {
|
|
1067
|
+
...localBash,
|
|
1068
|
+
label: 'bash (landstrip)',
|
|
1069
|
+
async execute(id, params, signal, onUpdate, callCtx) {
|
|
1070
|
+
const effectiveCtx = callCtx ?? ctx;
|
|
1071
|
+
if (!sandboxEnabled || !sandboxReady || !effectiveCtx)
|
|
1072
|
+
return localBash.execute(id, params, signal, onUpdate, effectiveCtx);
|
|
1073
|
+
|
|
1074
|
+
return runBashWithOptionalRetry(id, params, signal, onUpdate, effectiveCtx);
|
|
1075
|
+
},
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1050
1078
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1079
|
+
function register(pi: ExtensionAPI): void {
|
|
1080
|
+
const maybePi = pi as ExtensionAPI & {
|
|
1081
|
+
getFlag?: (name: string) => unknown;
|
|
1082
|
+
registerCommand?: ExtensionAPI['registerCommand'];
|
|
1083
|
+
registerFlag?: ExtensionAPI['registerFlag'];
|
|
1084
|
+
};
|
|
1053
1085
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1086
|
+
maybePi.registerFlag?.('no-sandbox', {
|
|
1087
|
+
description: 'Disable landstrip sandboxing for bash commands',
|
|
1088
|
+
type: 'boolean',
|
|
1089
|
+
default: false,
|
|
1090
|
+
});
|
|
1056
1091
|
|
|
1057
|
-
|
|
1058
|
-
if (!config.enabled) return;
|
|
1092
|
+
if (shouldRegisterBashTool) pi.registerTool(createBashTool(localCwd));
|
|
1059
1093
|
|
|
1060
|
-
|
|
1094
|
+
pi.on('user_bash', async (event, ctx) => {
|
|
1095
|
+
if (!sandboxEnabled || !sandboxReady) return;
|
|
1096
|
+
if (!loadConfig(ctx.cwd).enabled) return;
|
|
1061
1097
|
|
|
1062
|
-
|
|
1063
|
-
const blockedDomain = await preflightCommandDomains(event.input.command, ctx);
|
|
1098
|
+
const blockedDomain = await preflightCommandDomains(event.command, ctx);
|
|
1064
1099
|
if (blockedDomain) {
|
|
1065
1100
|
return {
|
|
1066
|
-
|
|
1067
|
-
|
|
1101
|
+
result: {
|
|
1102
|
+
output: `Blocked: "${blockedDomain}" is not allowed by the sandbox. Use /sandbox to review your config.`,
|
|
1103
|
+
exitCode: 1,
|
|
1104
|
+
cancelled: false,
|
|
1105
|
+
truncated: false,
|
|
1106
|
+
},
|
|
1068
1107
|
};
|
|
1069
1108
|
}
|
|
1070
|
-
}
|
|
1071
1109
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1110
|
+
return { operations: createLandstripBashOps(ctx) };
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
1114
|
+
if (!sandboxEnabled) return;
|
|
1115
|
+
|
|
1116
|
+
const config = loadConfig(ctx.cwd);
|
|
1117
|
+
if (!config.enabled) return;
|
|
1118
|
+
|
|
1119
|
+
const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
|
|
1120
|
+
|
|
1121
|
+
if (sandboxReady && isToolCallEventType('bash', event)) {
|
|
1122
|
+
const blockedDomain = await preflightCommandDomains(event.input.command, ctx);
|
|
1123
|
+
if (blockedDomain) {
|
|
1077
1124
|
return {
|
|
1078
1125
|
block: true,
|
|
1079
|
-
reason: `
|
|
1126
|
+
reason: `Network access to "${blockedDomain}" is blocked by the sandbox.`,
|
|
1080
1127
|
};
|
|
1081
1128
|
}
|
|
1082
|
-
await applyReadChoice(choice, filePath, ctx.cwd);
|
|
1083
1129
|
}
|
|
1084
|
-
}
|
|
1085
1130
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1131
|
+
if (isToolCallEventType('read', event)) {
|
|
1132
|
+
const filePath = canonicalizePath(event.input.path);
|
|
1133
|
+
if (!matchesPattern(filePath, getEffectiveAllowRead(ctx.cwd))) {
|
|
1134
|
+
const choice = await promptReadBlock(ctx, filePath);
|
|
1135
|
+
if (choice === 'abort') {
|
|
1136
|
+
return {
|
|
1137
|
+
block: true,
|
|
1138
|
+
reason: `Sandbox: read access denied for "${filePath}"`,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
await applyReadChoice(choice, filePath, ctx.cwd);
|
|
1142
|
+
}
|
|
1096
1143
|
}
|
|
1097
1144
|
|
|
1098
|
-
if (
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1145
|
+
if (isToolCallEventType('write', event) || isToolCallEventType('edit', event)) {
|
|
1146
|
+
const filePath = canonicalizePath((event.input as { path: string }).path);
|
|
1147
|
+
|
|
1148
|
+
if (matchesPattern(filePath, config.filesystem.denyWrite)) {
|
|
1101
1149
|
return {
|
|
1102
1150
|
block: true,
|
|
1103
|
-
reason:
|
|
1151
|
+
reason:
|
|
1152
|
+
`Sandbox: write access denied for "${filePath}" (in denyWrite). ` +
|
|
1153
|
+
`To change this, edit denyWrite in:\n ${projectPath}\n ${globalPath}`,
|
|
1104
1154
|
};
|
|
1105
1155
|
}
|
|
1106
|
-
await applyWriteChoice(choice, filePath, ctx.cwd);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
});
|
|
1110
1156
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
sandboxEnabled = false;
|
|
1124
|
-
sandboxReady = false;
|
|
1125
|
-
ctx.ui.notify('Sandbox disabled via config', 'info');
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1157
|
+
if (shouldPromptForWrite(filePath, getEffectiveAllowWrite(ctx.cwd), matchesPattern)) {
|
|
1158
|
+
const choice = await promptWriteBlock(ctx, filePath);
|
|
1159
|
+
if (choice === 'abort') {
|
|
1160
|
+
return {
|
|
1161
|
+
block: true,
|
|
1162
|
+
reason: `Sandbox: write access denied for "${filePath}" (not in allowWrite)`,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
await applyWriteChoice(choice, filePath, ctx.cwd);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1128
1169
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1170
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
1171
|
+
const noSandbox = maybePi.getFlag?.('no-sandbox') as boolean;
|
|
1131
1172
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
ctx.ui.notify('Sandbox is already enabled', 'info');
|
|
1173
|
+
if (noSandbox) {
|
|
1174
|
+
sandboxEnabled = false;
|
|
1175
|
+
sandboxReady = false;
|
|
1176
|
+
ctx.ui.notify('Sandbox disabled via --no-sandbox', 'warning');
|
|
1137
1177
|
return;
|
|
1138
1178
|
}
|
|
1139
1179
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
description: 'Disable the landstrip sandbox for this session',
|
|
1146
|
-
handler: async (_args, ctx) => {
|
|
1147
|
-
if (!sandboxEnabled) {
|
|
1148
|
-
ctx.ui.notify('Sandbox is already disabled', 'info');
|
|
1180
|
+
const config = loadConfig(ctx.cwd);
|
|
1181
|
+
if (!config.enabled) {
|
|
1182
|
+
sandboxEnabled = false;
|
|
1183
|
+
sandboxReady = false;
|
|
1184
|
+
ctx.ui.notify('Sandbox disabled via config', 'info');
|
|
1149
1185
|
return;
|
|
1150
1186
|
}
|
|
1151
1187
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
ctx.ui.setStatus('sandbox', '');
|
|
1155
|
-
ctx.ui.notify('Sandbox disabled', 'info');
|
|
1156
|
-
},
|
|
1157
|
-
});
|
|
1188
|
+
enableSandbox(ctx);
|
|
1189
|
+
});
|
|
1158
1190
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1191
|
+
maybePi.registerCommand?.('sandbox-enable', {
|
|
1192
|
+
description: 'Enable the landstrip sandbox for this session',
|
|
1193
|
+
handler: async (_args, ctx) => {
|
|
1194
|
+
if (sandboxEnabled) {
|
|
1195
|
+
ctx.ui.notify('Sandbox is already enabled', 'info');
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1166
1198
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
'',
|
|
1182
|
-
'
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1199
|
+
if (enableSandbox(ctx)) ctx.ui.notify('Sandbox enabled', 'info');
|
|
1200
|
+
},
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
maybePi.registerCommand?.('sandbox-disable', {
|
|
1204
|
+
description: 'Disable the landstrip sandbox for this session',
|
|
1205
|
+
handler: async (_args, ctx) => {
|
|
1206
|
+
if (!sandboxEnabled) {
|
|
1207
|
+
ctx.ui.notify('Sandbox is already disabled', 'info');
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
sandboxEnabled = false;
|
|
1212
|
+
sandboxReady = false;
|
|
1213
|
+
ctx.ui.setStatus('sandbox', '');
|
|
1214
|
+
ctx.ui.notify('Sandbox disabled', 'info');
|
|
1215
|
+
},
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
maybePi.registerCommand?.('sandbox', {
|
|
1219
|
+
description: 'Show sandbox configuration',
|
|
1220
|
+
handler: async (_args, ctx) => {
|
|
1221
|
+
if (!sandboxEnabled) {
|
|
1222
|
+
ctx.ui.notify('Sandbox is disabled', 'info');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const config = loadConfig(ctx.cwd);
|
|
1227
|
+
const { globalPath, projectPath } = getConfigPaths(ctx.cwd);
|
|
1228
|
+
const lines = [
|
|
1229
|
+
'Sandbox Configuration',
|
|
1230
|
+
` Project config: ${projectPath}`,
|
|
1231
|
+
` Global config: ${globalPath}`,
|
|
1232
|
+
` landstrip: ${binaryPath()}`,
|
|
1233
|
+
'',
|
|
1234
|
+
'Network (bash through HTTP proxy):',
|
|
1235
|
+
` Allowed domains: ${config.network.allowedDomains.join(', ') || '(none)'}`,
|
|
1236
|
+
` Denied domains: ${config.network.deniedDomains.join(', ') || '(none)'}`,
|
|
1237
|
+
...(sessionAllowedDomains.length > 0
|
|
1238
|
+
? [` Session allowed: ${sessionAllowedDomains.join(', ')}`]
|
|
1239
|
+
: []),
|
|
1240
|
+
'',
|
|
1241
|
+
'Filesystem (bash + read/write/edit tools):',
|
|
1242
|
+
` Deny Read: ${config.filesystem.denyRead.join(', ') || '(none)'}`,
|
|
1243
|
+
` Allow Read: ${config.filesystem.allowRead.join(', ') || '(none)'}`,
|
|
1244
|
+
` Allow Write: ${config.filesystem.allowWrite.join(', ') || '(none)'}`,
|
|
1245
|
+
` Deny Write: ${config.filesystem.denyWrite.join(', ') || '(none)'}`,
|
|
1246
|
+
...(sessionAllowedReadPaths.length > 0
|
|
1247
|
+
? [` Session read: ${sessionAllowedReadPaths.join(', ')}`]
|
|
1248
|
+
: []),
|
|
1249
|
+
...(sessionAllowedWritePaths.length > 0
|
|
1250
|
+
? [` Session write: ${sessionAllowedWritePaths.join(', ')}`]
|
|
1251
|
+
: []),
|
|
1252
|
+
];
|
|
1253
|
+
|
|
1254
|
+
ctx.ui.notify(lines.join('\n'), 'info');
|
|
1255
|
+
},
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
return { createBashTool, register };
|
|
1198
1260
|
}
|
package/landstrip.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Landlock-based sandboxing for pi with interactive permission prompts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"index.ts",
|
|
18
|
+
"landstrip.d.ts",
|
|
18
19
|
"README.md",
|
|
19
20
|
"sandbox.json"
|
|
20
21
|
],
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@earendil-works/pi-tui": "^0.78.0",
|
|
33
|
-
"@jarkkojs/landstrip": "^0.9.
|
|
34
|
+
"@jarkkojs/landstrip": "^0.9.5"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@earendil-works/pi-coding-agent": "^0.78.0",
|