pi-landstrip 0.19.18 → 0.19.19
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 +5 -3
- package/dist/index.ts +33 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,9 +32,11 @@ and platform limits, including Windows read grants and hard-denial restrictions.
|
|
|
32
32
|
|
|
33
33
|
Worker boundaries:
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- Workers can read their startup files despite the bundled `denyRead` defaults.
|
|
36
|
+
If your own sandbox settings block a needed path with `denyRead`, Pi asks for
|
|
37
|
+
approval before starting the worker. Declining prevents startup. Existing
|
|
38
|
+
`allowRead` grants and session approvals are reused. `denyReadAlways` blocks
|
|
39
|
+
startup without asking.
|
|
38
40
|
- Hard read denials also apply to shell host reads. Private workers protect
|
|
39
41
|
parent auth-file/lock roots with `denyReadAlways` and `denyWrite`;
|
|
40
42
|
private worker authentication is unsupported on Windows.
|
package/dist/index.ts
CHANGED
|
@@ -5583,7 +5583,8 @@ function loadSandboxConfig(cwd, includeProject) {
|
|
|
5583
5583
|
const globalConfig = deepMerge(JSON.parse(readFileSync7(join8(packageDir2, "sandbox.json"), "utf-8")), globalOverrides);
|
|
5584
5584
|
return {
|
|
5585
5585
|
...deepMerge(globalConfig, projectOverrides),
|
|
5586
|
-
explicitDenyRead: mergeArray(globalOverrides.filesystem?.denyRead ?? [], projectOverrides.filesystem?.denyRead)
|
|
5586
|
+
explicitDenyRead: mergeArray(globalOverrides.filesystem?.denyRead ?? [], projectOverrides.filesystem?.denyRead),
|
|
5587
|
+
explicitAllowRead: mergeArray(globalOverrides.filesystem?.allowRead ?? [], projectOverrides.filesystem?.allowRead)
|
|
5587
5588
|
};
|
|
5588
5589
|
}
|
|
5589
5590
|
function windowsStatus() {
|
|
@@ -6135,9 +6136,13 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6135
6136
|
if (scope !== filePath)
|
|
6136
6137
|
notify(ctx, `${verb} allowed (${choice}): ${scope}`, "info");
|
|
6137
6138
|
}
|
|
6138
|
-
async function applyReadChoice(ctx, choice, filePath, cwd, allowances) {
|
|
6139
|
+
async function applyReadChoice(ctx, choice, filePath, cwd, allowances, scope = sessionScopeFor(filePath, cwd)) {
|
|
6139
6140
|
const { globalPath, projectPath } = getPiConfigPaths(cwd, "sandbox.json");
|
|
6140
|
-
|
|
6141
|
+
if (allowances?.explicitDenyRead && !readAllowed(filePath, [scope], mergeArray(loadConfig(cwd).filesystem.denyRead, [
|
|
6142
|
+
...allowances.explicitDenyRead.patterns,
|
|
6143
|
+
...[...allowances.explicitDenyRead.globRoots.values()].flat()
|
|
6144
|
+
]), cwd))
|
|
6145
|
+
scope = filePath;
|
|
6141
6146
|
const configPath = choice === "project" ? projectPath : choice === "global" ? globalPath : undefined;
|
|
6142
6147
|
if (configPath) {
|
|
6143
6148
|
await addConfigValue(configPath, scope, (config) => config.filesystem?.allowRead ?? [], (config, allowRead) => {
|
|
@@ -6147,6 +6152,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
|
|
|
6147
6152
|
const target = choice === "once" ? allowances?.readPaths : sessionAllowedReadPaths;
|
|
6148
6153
|
if (target && !target.includes(scope))
|
|
6149
6154
|
target.push(scope);
|
|
6155
|
+
allowances?.explicitDenyRead?.approvedPaths.push(scope);
|
|
6150
6156
|
noteScope(ctx, "Read", choice, filePath, scope);
|
|
6151
6157
|
}
|
|
6152
6158
|
async function applyWriteChoice(ctx, choice, filePath, cwd, allowances) {
|
|
@@ -6291,8 +6297,8 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6291
6297
|
});
|
|
6292
6298
|
});
|
|
6293
6299
|
}
|
|
6294
|
-
async function resolveTrapQuery(trap, ctx, cwd, allowances, promptOnBlock, signal) {
|
|
6295
|
-
if (
|
|
6300
|
+
async function resolveTrapQuery(trap, ctx, cwd, allowances, promptOnBlock, signal, readScope) {
|
|
6301
|
+
if (trap.kind !== "filesystem") {
|
|
6296
6302
|
const key = `${trap.operation}\x00${trap.target}`;
|
|
6297
6303
|
const current = () => sessionAllowedTargets.includes(key) || allowances.targets.includes(key) ? { action: "allow" } : undefined;
|
|
6298
6304
|
const existing = current();
|
|
@@ -6320,7 +6326,13 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6320
6326
|
return { action: "deny", reason: "hard-deny" };
|
|
6321
6327
|
}
|
|
6322
6328
|
if (trap.operation === "read" && allowances.explicitDenyRead && matchesPattern(path, mergeArray(allowances.explicitDenyRead.patterns, config.explicitDenyRead), cwd, allowances.explicitDenyRead.globRoots)) {
|
|
6323
|
-
return
|
|
6329
|
+
return isPathReadAllowed(path, mergeArray(config.explicitAllowRead, [
|
|
6330
|
+
...sessionAllowedReadPaths,
|
|
6331
|
+
...allowances.explicitDenyRead.approvedPaths
|
|
6332
|
+
]), mergeArray(config.explicitDenyRead, [
|
|
6333
|
+
...allowances.explicitDenyRead.patterns,
|
|
6334
|
+
...[...allowances.explicitDenyRead.globRoots.values()].flat()
|
|
6335
|
+
]), cwd, { requireAllowMatch: true }) ? { action: "allow" } : undefined;
|
|
6324
6336
|
}
|
|
6325
6337
|
if (trap.operation === "write" && (matchesPattern(path, config.filesystem.denyWriteAlways, cwd) || matchesPattern(path, config.filesystem.denyWrite, cwd))) {
|
|
6326
6338
|
return { action: "deny", reason: "hard-deny" };
|
|
@@ -6339,7 +6351,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6339
6351
|
if (choice === "abort" || promptSignal.aborted)
|
|
6340
6352
|
return { action: "deny", reason: "rejected" };
|
|
6341
6353
|
if (trap.operation === "read") {
|
|
6342
|
-
await applyReadChoice(ctx, choice, path, cwd, allowances);
|
|
6354
|
+
await applyReadChoice(ctx, choice, path, cwd, allowances, readScope);
|
|
6343
6355
|
} else {
|
|
6344
6356
|
await applyWriteChoice(ctx, choice, path, cwd, allowances);
|
|
6345
6357
|
}
|
|
@@ -6461,15 +6473,24 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
|
|
|
6461
6473
|
const explicitDenyGlobRoots = new Map;
|
|
6462
6474
|
processAllowances.explicitDenyRead = {
|
|
6463
6475
|
patterns: config.explicitDenyRead,
|
|
6464
|
-
globRoots: explicitDenyGlobRoots
|
|
6476
|
+
globRoots: explicitDenyGlobRoots,
|
|
6477
|
+
approvedPaths: []
|
|
6465
6478
|
};
|
|
6466
|
-
|
|
6479
|
+
const startupPaths = [
|
|
6480
|
+
...new Set([options.cwd, options.command, ...processAllowances.readPaths])
|
|
6481
|
+
];
|
|
6482
|
+
for (const path of startupPaths) {
|
|
6467
6483
|
if (/[*?[\]]/.test(path)) {
|
|
6468
6484
|
throw new Error(`Worker startup read path "${path}" must be a concrete path, not a glob`);
|
|
6469
6485
|
}
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6486
|
+
if (matchesPattern(path, mergeArray(config.filesystem.denyReadAlways, processAllowances.protectedPaths), options.cwd)) {
|
|
6487
|
+
throw new Error(`Worker startup read path "${path}" is denied by sandbox policy`);
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
for (const path of startupPaths) {
|
|
6491
|
+
const result = await resolveTrapQuery({ kind: "filesystem", operation: "read", path }, options.ctx, options.cwd, processAllowances, true, options.signal, normalizeBlockedPath(path, options.cwd));
|
|
6492
|
+
if (result.action !== "allow") {
|
|
6493
|
+
throw new Error(`Worker startup read access denied: ${path} (${result.reason})`);
|
|
6473
6494
|
}
|
|
6474
6495
|
}
|
|
6475
6496
|
const { token: proxyToken, authorization: proxyAuthorization } = createProxyCredentials();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.19",
|
|
4
4
|
"description": "Sandboxed Bash and process-backed agents for Pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ci:test": "npm run test"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@landstrip/landstrip-api": "^0.19.
|
|
42
|
+
"@landstrip/landstrip-api": "^0.19.19",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"undici": "^8.9.0",
|