pi-better-sandbox 0.5.1 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-better-sandbox",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Pi extension with independent Main and Subagents permission profiles, file guards, and OS sandbox enforcement.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -525,9 +525,8 @@ export function executableFromPath(name: string): string | undefined {
525
525
  * afterwards because a resumed task re-runs the launch vector it captured and
526
526
  * its `--ro-bind` sources have to still be there.
527
527
  *
528
- * Returns false when the placeholder could not be created. That is not a hole:
529
- * the confined process runs as this same user, so a path this process cannot
530
- * create is a path that process cannot create either.
528
+ * Returns false when the placeholder could not be created. Callers must fail
529
+ * closed for writable regions: a task may replace or chmod the blocking parent.
531
530
  */
532
531
  function materializeDenyPath(path: string): boolean {
533
532
  if (existsSync(path)) return true;
@@ -570,8 +569,9 @@ function buildLinuxSandboxCommand(
570
569
  if (policy.permissions) return buildLinuxPermissionCommand(bwrap, args, policy, seams);
571
570
  const materialize = seams.materializeDenyPath ?? materializeDenyPath;
572
571
  const denyBinds = policy.denyWrite.flatMap((path) => {
573
- const mountable = writableInsideLinuxSandbox(path, policy.writableRoot) && materialize(path);
574
- return [mountable ? "--ro-bind" : "--ro-bind-try", path, path];
572
+ const writable = writableInsideLinuxSandbox(path, policy.writableRoot);
573
+ if (writable && !materialize(path)) throw new Error(`Cannot protect denied path: ${path}`);
574
+ return [writable ? "--ro-bind" : "--ro-bind-try", path, path];
575
575
  });
576
576
  return {
577
577
  file: bwrap,
@@ -583,8 +583,8 @@ function buildLinuxSandboxCommand(
583
583
  // Layered last so a denied path wins over every writable bind above.
584
584
  // A denied path need not exist yet, so one inside a writable region
585
585
  // is materialized first; `-try` remains for the paths that are
586
- // read-only regardless and for the ones that could not be created,
587
- // which are paths the confined process cannot create either.
586
+ // read-only regardless. A writable region whose guard cannot be
587
+ // materialized must fail before launching the task.
588
588
  ...denyBinds,
589
589
  "--",
590
590
  args.execPath, ...args.execArgs,
@@ -738,7 +738,10 @@ function buildLinuxPermissionCommand(
738
738
  for (const writableRoot of [...(writableProject ? [project] : []),
739
739
  ...(policy.runtimeWrite ?? []).filter((path) => !compatibility.includes(path))]) {
740
740
  const materialize = seams.materializeDenyPath ?? materializeDenyPath;
741
- const leaves = protectedPaths.filter((path) => contains(writableRoot, path) && materialize(path));
741
+ const leaves = protectedPaths.filter((path) => contains(writableRoot, path));
742
+ for (const path of leaves) {
743
+ if (!materialize(path)) throw new Error(`Cannot protect denied path: ${path}`);
744
+ }
742
745
  for (const parent of protectedAncestors(leaves).filter((path) => contains(writableRoot, path) && path !== writableRoot)) {
743
746
  writableAncestors.add(parent);
744
747
  }
@@ -160,6 +160,8 @@ export function installTaskTools(pi: ExtensionAPI, options: {
160
160
  shellPath?: () => string | undefined;
161
161
  trustedSources: readonly string[];
162
162
  admitExtensionTool?: (name: string, input: unknown, sourcePath: string | undefined) => boolean;
163
+ /** Build the admitted bash definition from the confined operations (default: the SDK bash tool). */
164
+ bashDefinition?: (cwd: string, operations: BashOperations) => ReturnType<typeof createBashToolDefinition>;
163
165
  }) {
164
166
  const { controller } = options;
165
167
  const sourceKey = (path: string) => path.startsWith("<") ? path : canonicalizePath(path);
@@ -180,7 +182,7 @@ export function installTaskTools(pi: ExtensionAPI, options: {
180
182
  pi.registerTool(own(createReadToolDefinition(cwd, { operations: files.read })));
181
183
  pi.registerTool(own(createWriteToolDefinition(cwd, { operations: files.write })));
182
184
  pi.registerTool(own(createEditToolDefinition(cwd, { operations: files.edit })));
183
- pi.registerTool(own(createBashToolDefinition(cwd, { operations: bash })));
185
+ pi.registerTool(own(options.bashDefinition ? options.bashDefinition(cwd, bash) : createBashToolDefinition(cwd, { operations: bash })));
184
186
  };
185
187
  register(options.cwd);
186
188
  pi.on("user_bash", () => ({ operations: bash }));