auto-permission-system 3.18.0 → 3.19.0

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.
@@ -323,7 +323,7 @@ export default function (pi: ExtensionAPI) {
323
323
  return;
324
324
  }
325
325
  audit({ layer: 2, tool: event.toolName, decision: "no", note: "advanced-secure-output", findings, cwd: ctx.cwd });
326
- ctx.ui.notify(`Sensitive data protected: withheld ${event.toolName} output from the AI (potential secrets detected).`, "warning");
326
+ ctx.ui.notify(`Sensitive data protected: ${event.toolName} output was hidden from the AI (potential secrets detected).`, "warning");
327
327
  return { content: [{ type: "text", text: withheldNotice(subject) }] };
328
328
  });
329
329
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-permission-system",
3
- "version": "3.18.0",
3
+ "version": "3.19.0",
4
4
  "description": "Claude auto mode for Pi & security for Pi. A zero-trust permission system for Pi coding agent powered by Advanced Secure mode to scan files, content, and tools to prevent data breaches.",
5
5
  "type": "module",
6
6
  "author": "Navin Kumar",
@@ -123,7 +123,7 @@ The same options apply to a bash command whose network request reaches a host ou
123
123
  - **`advanced-secure`** (recommended) — adds secret/credential detection, and **only adds asks or blocks** (never loosens):
124
124
  - file reads whose name looks like a credential store ask before the read;
125
125
  - risky `mcp__<server>__<tool>` calls ask before they run. The gate ignores the `mcp__<server>__` namespace, takes the operation from the head verb of the name (or the first verb of the description), and asks for destructive names, mutating names (`update_*`, `create_*`, `run_*`, `evaluate_*`), and destructive arguments (SQL `DELETE`/`UPDATE` without `WHERE`, `rm -rf`, `*` targets, HTTP `DELETE`, production targets). A documented tool with an unclassified name (`context`, `capture`, `logs`) does not ask; a tool with **no description** counts as opaque and does. Tune it under `mcp` in `sandbox.json` (ADR-032): `allowTools` / `allowPrefixes` skip asks, `askTools` forces them, `trustAnnotations` lets a server's `readOnlyHint` allow the call, `askThreshold` moves the score line (default 30). An untrusted project file may only tighten it (add `askTools`, lower `askThreshold`, turn `allowSimpleUpdates`/`trustAnnotations` off).
126
- - before any tool/command/file output reaches the model, a secret-like hit shows the tool and each detected line as `Finding <lineNo>: <complete line>` — one `Finding` row per match, no truncation — plus `Question Should the AI be allowed to see it?`, with two choices — **No, keep private** or **Yes, allow**. No, keep private withholds the output and tells the model it was withheld because it may contain sensitive information (not a failure, not an empty result).
126
+ - before any tool/command/file output reaches the model, a secret-like hit shows the tool and each detected line as `Finding <lineNo>: <complete line>` — one `Finding` row per match, no truncation — plus `Question Should the AI be allowed to see it?`, with two choices — **No, keep private** or **Yes, allow**. No, keep private hides the output and tells the model it was hidden because it may contain sensitive credentials (not a failure, not an empty result).
127
127
 
128
128
  Switch with `/permission-mode`; all three modes are saved to the global `sandbox.json` and reflected in the footer's sandbox chip (`Sandbox: 🧠 N domains, M paths` for advanced-secure, `Sandbox: 🔒 …` for default). An untrusted project may move the mode **up** (toward stricter), never down: it cannot turn on YOLO or turn off Advanced Secure. The third choice, **YOLO**, disables every layer; the chip reads `⚠️ YOLO — all security layers disabled` until you pick another mode (ADR-020).
129
129
 
@@ -166,7 +166,7 @@ async function preflightOutsideReads(
166
166
  if (choice === MainChoice.Session) {
167
167
  sessionReadGrants.push(absPath);
168
168
  auditL1({ subject: absPath, decision: "session", scope: "session", reason: "outside-project-read", note: "preflight", cwd });
169
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed read of ${absPath} for this conversation (not saved)`, "info");
169
+ opts.ctx.ui?.notify?.(`sandbox: allowed read of ${absPath} for this conversation (not saved)`, "info");
170
170
  continue;
171
171
  }
172
172
  if (choice !== MainChoice.Remember || !grant) {
@@ -179,10 +179,10 @@ async function preflightOutsideReads(
179
179
  try {
180
180
  const persistedTo = await opts.onAlwaysRead(subject, scope);
181
181
  auditL1({ subject, decision: scope === "cwd" ? "always-cwd" : "always-global", scope, cwd, persisted_to: persistedTo, note: "preflight" });
182
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed read of ${subject} (${scope})`, "warning");
182
+ opts.ctx.ui?.notify?.(`sandbox: allowed read of ${subject} (${scope})`, "warning");
183
183
  } catch (e) {
184
184
  auditL1({ subject, scope, cwd, error: String(e), note: "preflight" });
185
- onData(Buffer.from(`\n❌ pi-sandbox: could not save the permission (${e}). ${subject} remains blocked.\n`));
185
+ onData(Buffer.from(`\n❌ sandbox: could not save the permission (${e}). ${subject} remains blocked.\n`));
186
186
  return { blocked: { exitCode: 1 } };
187
187
  }
188
188
  }
@@ -373,7 +373,7 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
373
373
  blockedHint = "";
374
374
  auditL1({ subject: offending ?? spelledOffending, decision: "write-denied", reason: "the OS sandbox could not start (bwrap mount setup)", cwd: opts?.ctx?.cwd, note: "sandbox-setup-error" });
375
375
  opts?.ctx?.ui?.notify?.(`Sandbox setup notice: could not prepare folder isolation for ${offending ?? spelledOffending ?? "the sandbox"}. System defaults will be used.`, "warning");
376
- decisionHint = `\n[System Error] pi-sandbox: the OS sandbox could not start (bwrap could not create a protected mount point), so nothing was permission-prompted. This is a sandbox setup problem, not a policy denial.\n`;
376
+ decisionHint = `\n[System Error] sandbox: the OS sandbox could not start (bwrap could not create a protected mount point), so nothing was permission-prompted. This is a sandbox setup problem, not a policy denial.\n`;
377
377
  onData(Buffer.from(decisionHint));
378
378
  } else if (offending && outsideDenied && !hardDenied && outsideMode === "ask" && opts?.ctx?.hasUI && opts.ctx.ui?.select && opts.onAlwaysRead) {
379
379
  const absPath = offending;
@@ -393,8 +393,8 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
393
393
  if (choice === MainChoice.Once) readOnce.push(absPath);
394
394
  else sessionReadGrants.push(absPath);
395
395
  auditL1({ subject: absPath, decision: choice, scope: choice === MainChoice.Once ? "invocation" : "session", cwd: opts.ctx.cwd, note: "outside-project-read" });
396
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed read of ${absPath} ${choice === MainChoice.Once ? "once" : "for this conversation"}`, "info");
397
- onData(Buffer.from(`\n[Permission Granted] pi-sandbox: read of ${absPath} allowed ${choice === MainChoice.Once ? "once" : "for this conversation"}. Command output with the new permission follows.\n`));
396
+ opts.ctx.ui?.notify?.(`sandbox: allowed read of ${absPath} ${choice === MainChoice.Once ? "once" : "for this conversation"}`, "info");
397
+ onData(Buffer.from(`\n[Permission Granted] sandbox: read of ${absPath} allowed ${choice === MainChoice.Once ? "once" : "for this conversation"}. Command output with the new permission follows.\n`));
398
398
  resolve(attempt([]));
399
399
  return;
400
400
  }
@@ -403,13 +403,13 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
403
403
  try {
404
404
  const persistedTo = await opts.onAlwaysRead(subject, grant.scope);
405
405
  auditL1({ subject, granularity: grant.folder ? "folder" : "file", original: absPath, decision: grant.scope === "cwd" ? "always-cwd" : "always-global", scope: grant.scope, cwd: opts.ctx.cwd, persisted_to: persistedTo, note: "outside-project-read" });
406
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed read of ${subject} (${grant.scope}${grant.folder ? ", folder" : ""})`, "info");
407
- onData(Buffer.from(`\n[Permission Granted] pi-sandbox: read of ${subject} allowed (${grant.scope}${grant.folder ? ", folder" : ""}). Command output with the new permission follows.\n`));
406
+ opts.ctx.ui?.notify?.(`sandbox: allowed read of ${subject} (${grant.scope}${grant.folder ? ", folder" : ""})`, "info");
407
+ onData(Buffer.from(`\n[Permission Granted] sandbox: read of ${subject} allowed (${grant.scope}${grant.folder ? ", folder" : ""}). Command output with the new permission follows.\n`));
408
408
  resolve(attempt([]));
409
409
  return;
410
410
  } catch (e) {
411
411
  auditL1({ subject, scope: grant.scope, cwd: opts.ctx.cwd, error: String(e), note: "outside-project-read" });
412
- decisionHint = `\n[Security Notice] pi-sandbox: could not save the permission (${e}). ${subject} remains blocked.\n`;
412
+ decisionHint = `\n[Security Notice] sandbox: could not save the permission (${e}). ${subject} remains blocked.\n`;
413
413
  }
414
414
  }
415
415
  } catch (e) {
@@ -459,8 +459,8 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
459
459
  if (choice === MainChoice.Once) {
460
460
  grantedWriteFolders.add(parentDir);
461
461
  auditL1({ subject: absPath, granularity: "folder", original: absPath, decision: "once", scope: "invocation", cwd: opts.ctx.cwd, note: "write-once" });
462
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed write to ${absPath} once`, "info");
463
- onData(Buffer.from(`\n[Permission Granted] pi-sandbox: write to ${absPath} allowed once. Command output with the new permission follows.\n`));
462
+ opts.ctx.ui?.notify?.(`sandbox: allowed write to ${absPath} once`, "info");
463
+ onData(Buffer.from(`\n[Permission Granted] sandbox: write to ${absPath} allowed once. Command output with the new permission follows.\n`));
464
464
  resolve(attempt([...writeOnce, parentDir]));
465
465
  return;
466
466
  }
@@ -469,8 +469,8 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
469
469
  sessionWriteGrants.push(value);
470
470
  (value === parentDir ? grantedWriteFolders : grantedWriteFiles).add(value);
471
471
  auditL1({ subject: value, granularity: value === parentDir ? "folder" : "file", original: absPath, decision: "session", scope: "session", cwd: opts.ctx.cwd, note: "write-session" });
472
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed write to ${value} for this conversation`, "info");
473
- onData(Buffer.from(`\n[Permission Granted] pi-sandbox: write to ${value} allowed for this conversation. Command output with the new permission follows.\n`));
472
+ opts.ctx.ui?.notify?.(`sandbox: allowed write to ${value} for this conversation`, "info");
473
+ onData(Buffer.from(`\n[Permission Granted] sandbox: write to ${value} allowed for this conversation. Command output with the new permission follows.\n`));
474
474
  resolve(attempt([]));
475
475
  return;
476
476
  }
@@ -480,14 +480,14 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
480
480
  const persistedTo = await opts.onAlways(subject, grant.scope);
481
481
  (grant.folder ? grantedWriteFolders : grantedWriteFiles).add(subject);
482
482
  auditL1({ subject, granularity: grant.folder ? "folder" : "file", original: absPath, decision: grant.scope === "cwd" ? "always-cwd" : "always-global", scope: grant.scope, cwd: opts.ctx.cwd, persisted_to: persistedTo });
483
- opts.ctx.ui?.notify?.(`pi-sandbox: allowed write to ${subject} (${grant.scope}${grant.folder ? ", folder" : ""})`, "info");
484
- onData(Buffer.from(`\n[Permission Granted] pi-sandbox: write to ${subject} allowed (${grant.scope}${grant.folder ? ", folder" : ""}). Command output with the new permission follows.\n`));
483
+ opts.ctx.ui?.notify?.(`sandbox: allowed write to ${subject} (${grant.scope}${grant.folder ? ", folder" : ""})`, "info");
484
+ onData(Buffer.from(`\n[Permission Granted] sandbox: write to ${subject} allowed (${grant.scope}${grant.folder ? ", folder" : ""}). Command output with the new permission follows.\n`));
485
485
  resolve(attempt([]));
486
486
  return;
487
487
  } catch (e) {
488
488
  auditL1({ subject, granularity: grant.folder ? "folder" : "file", original: absPath, scope: grant.scope, cwd: opts.ctx.cwd, error: String(e) });
489
- opts.ctx.ui?.notify?.(`pi-sandbox: could not save the permission (${e})`, "error");
490
- decisionHint = `\n[Security Notice] pi-sandbox: could not save the permission (${e}). ${subject} remains blocked.\n`;
489
+ opts.ctx.ui?.notify?.(`sandbox: could not save the permission (${e})`, "error");
490
+ decisionHint = `\n[Security Notice] sandbox: could not save the permission (${e}). ${subject} remains blocked.\n`;
491
491
  }
492
492
  }
493
493
  } catch (e) {
@@ -482,7 +482,7 @@ export async function askExposure(ctx: AskCtx, hits: string[], programId?: strin
482
482
  */
483
483
  export function withheldNotice(subject: string): string {
484
484
  return [
485
- `[Security Policy Notice] Output from \`${subject}\` was withheld because it contains sensitive credentials.`,
485
+ `[Security Policy Notice] Output from \`${subject}\` was hidden because it contains sensitive credentials.`,
486
486
  "",
487
487
  "The tool completed successfully, but the output is unavailable.",
488
488
  "Continue with the available information, or ask the user to allow access if the output is needed.",