auto-permission-system 3.9.0 → 3.12.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.
- package/CHANGELOG.md +0 -9
- package/README.md +19 -20
- package/extensions/guard.ts +23 -8
- package/extensions/permission-mode.ts +4 -4
- package/extensions/sandbox.ts +1 -1
- package/package.json +8 -10
- package/{sandbox.cloud.json → sandbox.claude.json} +1 -0
- package/sandbox.default.json +12 -2
- package/skills/auto-permission-system/SKILL.md +10 -9
- package/src/core/policy/patterns.ts +57 -0
- package/src/core/trust.ts +1 -1
- package/src/detect/sensitive-file-scanner/patterns.ts +27 -0
- package/src/l1-sandbox/bash-ops.ts +82 -11
- package/src/l1-sandbox/config.ts +18 -1
- package/src/l1-sandbox/host-bridge.ts +99 -0
- package/src/l1-sandbox/manager.ts +14 -2
- package/src/l1-sandbox/network-ask.ts +6 -6
- package/src/l2-guard/exposure.ts +5 -15
- package/src/l2-guard/program-grants.ts +5 -5
- package/src/l2-guard/program-id.ts +3 -3
- package/src/l2-guard/prompts.ts +4 -4
- package/src/ui/ask-flow.ts +21 -9
- package/src/ui/ask-selector.ts +84 -11
- package/src/ui/ask.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,15 +3,6 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Entries below [1.0.0] are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from [Conventional Commits](https://www.conventionalcommits.org/) — do not edit by hand.
|
|
5
5
|
|
|
6
|
-
# [3.9.0](https://github.com/Navin009/auto-permission-system/compare/v3.8.0...v3.9.0) (2026-10-13)
|
|
7
|
-
|
|
8
|
-
### Features
|
|
9
|
-
|
|
10
|
-
* **demo:** crisp widescreen HD terminal recording (`demo.mp4`, `demo.gif`) demonstrating Cloud Auto Mode and Advanced Secure Mode
|
|
11
|
-
* **branding:** official security shield logo and banner in documentation and package
|
|
12
|
-
* **docs:** comprehensive FAQ, Cloud Auto Mode architecture, interactive prompt guides, and zero-trust documentation
|
|
13
|
-
* **metadata:** package keywords, clean npm manifest, and remove legacy PyPI references
|
|
14
|
-
|
|
15
6
|
# [2.0.0](https://github.com/Navin009/auto-permission-system/compare/v1.2.0...v2.0.0) (2026-10-02)
|
|
16
7
|
|
|
17
8
|
|
package/README.md
CHANGED
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
[](./LICENSE)
|
|
12
12
|
[](https://pi.dev/packages)
|
|
13
13
|
[](https://github.com/Navin009/auto-permission-system/actions/workflows/ci.yml)
|
|
14
|
-
[](#secure-auto-mode--claude-auto-mode)
|
|
15
15
|
[](#security-for-pi-how-it-works)
|
|
16
16
|
[](#permission-system--modes)
|
|
17
17
|
|
|
18
|
-
**Auto Permission System** is a zero-trust permission architecture and **Secure Auto Mode /
|
|
18
|
+
**Auto Permission System** is a zero-trust permission architecture and **Secure Auto Mode / Claude Auto Mode for the [Pi coding agent](https://pi.dev)**. It brings the confidence of **Claude's auto mode** to Pi—enabling autonomous agent workflows while actively scanning files, shell commands, and tool content to **prevent data breaches and credential exfiltration**.
|
|
19
19
|
|
|
20
20
|
Whether running interactively with keyboard-driven ask prompts or running headlessly in CI/CD pipelines, this package enforces multi-layer boundaries across filesystem access, network egress, shell commands, and MCP tools.
|
|
21
21
|
|
|
@@ -25,14 +25,12 @@ Whether running interactively with keyboard-driven ask prompts or running headle
|
|
|
25
25
|
|
|
26
26
|

|
|
27
27
|
|
|
28
|
-
> **High-Definition Video:** Watch or download the full-resolution recording at [assets/demo.mp4](https://raw.githubusercontent.com/Navin009/auto-permission-system/main/assets/demo.mp4) (H.264, 60fps). Run `./demo/run-demo.sh` locally to reproduce this live simulation.
|
|
29
|
-
|
|
30
28
|
### Demonstrated Moments in the Recording:
|
|
31
29
|
1. **Interactive `.env` Read Guard:** Intercepts `read(".env")` before the AI can see secrets. Displays a 10s countdown prompt defaulting to `No` (Block).
|
|
32
30
|
2. **Kernel OS Sandbox Confinement:** Prevents bash commands from writing to system paths outside allowed roots (`EROFS: Read-only file system`).
|
|
33
31
|
3. **Governed Network Egress:** Catches unknown outbound connections (`curl`) with an interactive domain ask prompt and a 15s countdown timer.
|
|
34
32
|
4. **Real-Time API Key & Secret Redaction:** Automatically detects exposed API tokens (`sk-proj-...`, `ghp_...`) in command output and replaces them with `***REDACTED***` before reaching the LLM context.
|
|
35
|
-
5. **Autonomous
|
|
33
|
+
5. **Autonomous Claude Auto Mode:** Demonstrates headless, fail-closed CI execution (`pi -p`) with zero deadlocks and zero hangs.
|
|
36
34
|
|
|
37
35
|
---
|
|
38
36
|
|
|
@@ -43,7 +41,7 @@ Whether running interactively with keyboard-driven ask prompts or running headle
|
|
|
43
41
|
- [Key Features at a Glance](#key-features-at-a-glance)
|
|
44
42
|
- [Security for Pi: How It Works](#security-for-pi-how-it-works)
|
|
45
43
|
- [Interactive Ask Prompts](#interactive-ask-prompts)
|
|
46
|
-
- [Secure Auto Mode &
|
|
44
|
+
- [Secure Auto Mode & Claude Auto Mode](#secure-auto-mode--claude-auto-mode)
|
|
47
45
|
- [Real-Time Secret Detection & Content Scanning](#real-time-secret-detection--content-scanning)
|
|
48
46
|
- [Permission System & Modes](#permission-system--modes)
|
|
49
47
|
- [Configuration Reference](#configuration-reference)
|
|
@@ -100,7 +98,7 @@ Once installed, the extension automatically loads whenever you launch `pi`. Chec
|
|
|
100
98
|
| Feature | Description | Protection Level |
|
|
101
99
|
|---|---|---|
|
|
102
100
|
| 🛡️ **3-Layer Defense** | OS Kernel sandbox (`bubblewrap`/`sandbox-exec`) + in-process tool guard + real-time content redactor. | Zero-Trust |
|
|
103
|
-
| ⚡ **Secure Auto Mode** | Autonomous execution inspired by Claude's auto mode; scans files and output to prevent data breaches. | Autonomous /
|
|
101
|
+
| ⚡ **Secure Auto Mode** | Autonomous execution inspired by Claude's auto mode; scans files and output to prevent data breaches. | Autonomous / Claude |
|
|
104
102
|
| 🔑 **Interactive Ask Prompts** | Real-time terminal prompts with 10s countdowns (`Once`, `Session`, `Project`, `Global`). | Interactive Human-in-the-Loop |
|
|
105
103
|
| 🔍 **AST & Regex Secret Scanner** | Scans tool stdout/stderr for API keys, private keys, and tokens, redacting them before model context. | Data Breach Prevention |
|
|
106
104
|
| 🌐 **Network Egress Gate** | Intercepts unapproved network connections and prompts for confirmation with wildcards. | Data Leak Prevention |
|
|
@@ -162,7 +160,7 @@ When an unlisted domain, `.env` file, or directory write is requested during an
|
|
|
162
160
|
|
|
163
161
|
> No
|
|
164
162
|
Yes, just this once
|
|
165
|
-
Yes, for this
|
|
163
|
+
Yes, for this conversation
|
|
166
164
|
|
|
167
165
|
Default: No (10s)
|
|
168
166
|
```
|
|
@@ -179,7 +177,7 @@ When an unlisted domain, `.env` file, or directory write is requested during an
|
|
|
179
177
|
|
|
180
178
|
> No
|
|
181
179
|
Yes, just this once
|
|
182
|
-
Yes, all in group for this
|
|
180
|
+
Yes, all in group for this conversation
|
|
183
181
|
Yes, always…
|
|
184
182
|
|
|
185
183
|
Default: Yes, just this once (15s)
|
|
@@ -191,33 +189,34 @@ When an unlisted domain, `.env` file, or directory write is requested during an
|
|
|
191
189
|
⚠ Sensitive information detected: API Secret Key
|
|
192
190
|
|
|
193
191
|
Tool bash (node verify_service.js)
|
|
194
|
-
Finding sk-(?!ant-)[A-Za-z0-9_-]
|
|
192
|
+
Finding 23: sk-(?!ant-)[A-Za-z0-9_-]
|
|
195
193
|
Question Should the AI be allowed to see it?
|
|
196
194
|
|
|
197
195
|
> No, keep private (recommended)
|
|
198
196
|
Yes, allow
|
|
199
197
|
```
|
|
198
|
+
- `Finding` prints the **complete** detected line and its line number — one `Finding` row per match when several secrets appear, with no truncation.
|
|
200
199
|
- Choosing **No, keep private** replaces the token with `***REDACTED***` in the model's context window, preventing credentials from leaking to LLM providers.
|
|
201
200
|
|
|
202
201
|
---
|
|
203
202
|
|
|
204
|
-
## Secure Auto Mode &
|
|
203
|
+
## Secure Auto Mode & Claude Auto Mode
|
|
205
204
|
|
|
206
205
|
In modern AI coding tools like Claude Code, developers love **auto mode** because the agent can execute tasks autonomously without constantly prompting for confirmation on trivial, harmless steps.
|
|
207
206
|
|
|
208
207
|
However, unconstrained auto mode is a significant data breach risk. If an autonomous agent reads credentials, runs unvetted scripts, or sends code to third-party endpoints, the entire developer environment can be compromised.
|
|
209
208
|
|
|
210
|
-
**
|
|
209
|
+
**Claude Auto Mode for Pi** delivers **Claude-style auto mode with zero-trust security**:
|
|
211
210
|
- **Autonomous Execution with Real-Time Guardrails:** The agent operates autonomously within approved boundaries, allowing rapid development while guarding sensitive resources.
|
|
212
211
|
- **Advanced Secure Scanning:** Uses `advanced-secure` mode to scan files, commands, and content to stop data breaches before they can occur.
|
|
213
212
|
- **Fail-Closed in Headless CI/CD:** When running unattended (`pi -p`, GitHub Actions, Docker, remote devboxes), unapproved operations fail closed immediately with a structured reason instead of hanging or deadlocking the pipeline.
|
|
214
213
|
|
|
215
|
-
### Activating
|
|
214
|
+
### Activating Claude Auto Mode in CI/CD:
|
|
216
215
|
|
|
217
216
|
```bash
|
|
218
|
-
# 1. Copy the shipped
|
|
217
|
+
# 1. Copy the shipped Claude Auto Mode template to your repository:
|
|
219
218
|
mkdir -p .pi
|
|
220
|
-
cp node_modules/auto-permission-system/sandbox.
|
|
219
|
+
cp node_modules/auto-permission-system/sandbox.claude.json .pi/sandbox.json
|
|
221
220
|
|
|
222
221
|
# 2. Run Pi headlessly with complete automated protection:
|
|
223
222
|
pi -p "Review pull request changes, verify dependencies, and execute test suite"
|
|
@@ -240,10 +239,10 @@ jobs:
|
|
|
240
239
|
run: |
|
|
241
240
|
npm install -g @earendil-works/pi-coding-agent
|
|
242
241
|
pi install npm:auto-permission-system
|
|
243
|
-
- name: Activate
|
|
242
|
+
- name: Activate Claude Auto Mode Preset
|
|
244
243
|
run: |
|
|
245
244
|
mkdir -p .pi
|
|
246
|
-
cp node_modules/auto-permission-system/sandbox.
|
|
245
|
+
cp node_modules/auto-permission-system/sandbox.claude.json .pi/sandbox.json
|
|
247
246
|
- name: Run Autonomous Review in Auto Mode
|
|
248
247
|
run: |
|
|
249
248
|
pi -p "Verify changes, run npm test, and flag security issues"
|
|
@@ -394,11 +393,11 @@ grep '"finding"' ~/.pi/agent/audit.log | tail -n 10
|
|
|
394
393
|
|
|
395
394
|
A selection of common questions is below. For the complete, detailed guide, read **[docs/FAQ.md](docs/FAQ.md)**.
|
|
396
395
|
|
|
397
|
-
### How does
|
|
396
|
+
### How does Claude Auto Mode secure unattended runs?
|
|
398
397
|
In headless mode (`pi -p` or subagent executions), interactive ask prompts cannot be presented to a human. Auto Permission System operates in a fail-closed posture: any tool call or bash command targeting an unlisted domain, credential file, or outside-project path is automatically blocked, and the refusal reason is provided to the agent and recorded in the audit log.
|
|
399
398
|
|
|
400
|
-
### What is the relationship between
|
|
401
|
-
Claude Code popularized autonomous "auto mode", allowing developers to let AI code without stopping for confirmation on harmless steps.
|
|
399
|
+
### What is the relationship between Claude Auto Mode and Claude's auto mode?
|
|
400
|
+
Claude Code popularized autonomous "auto mode", allowing developers to let AI code without stopping for confirmation on harmless steps. Claude Auto Mode brings this exact convenience to the Pi coding agent, but adds essential zero-trust boundaries: using Advanced Secure Mode to scan files, shell commands, and content to guarantee that autonomous execution never leads to data breaches or leaked API tokens.
|
|
402
401
|
|
|
403
402
|
### How do I allow the agent to reach a new domain?
|
|
404
403
|
Add it to `network.allowedDomains` in `<cwd>/.pi/sandbox.json` or `~/.pi/agent/extensions/sandbox.json`, or run a curl command once and select **Yes, always… → All in group in this project**.
|
package/extensions/guard.ts
CHANGED
|
@@ -65,7 +65,7 @@ async function askDetection(
|
|
|
65
65
|
if (choice === "session") {
|
|
66
66
|
detectionGrants.add(key);
|
|
67
67
|
audit({ layer: 2, tool: kind, subject, reason, decision: "session", note: "advanced-secure", cwd: ctx.cwd });
|
|
68
|
-
ctx.ui.notify(`security-guard: allowed for this
|
|
68
|
+
ctx.ui.notify(`security-guard: allowed for this conversation (not saved) → ${subject}`, "info");
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
audit({ layer: 2, tool: kind, subject, reason, decision: "no", note: "advanced-secure", cwd: ctx.cwd });
|
|
@@ -91,7 +91,22 @@ async function detectMcpAsk(
|
|
|
91
91
|
policy: mcpPolicy,
|
|
92
92
|
});
|
|
93
93
|
if (result.decision !== "ask") return null;
|
|
94
|
-
|
|
94
|
+
|
|
95
|
+
// Include detailed call parameters or script preview if present
|
|
96
|
+
let detailedSummary = result.summary;
|
|
97
|
+
if (input && Object.keys(input).length > 0) {
|
|
98
|
+
const rawScript = input.code ?? input.script ?? input.command ?? input.query;
|
|
99
|
+
if (typeof rawScript === "string" && rawScript.includes("\n")) {
|
|
100
|
+
detailedSummary += `\n\n${rawScript}`;
|
|
101
|
+
} else {
|
|
102
|
+
const formattedArgs = JSON.stringify(input, null, 2);
|
|
103
|
+
if (formattedArgs.includes("\n")) {
|
|
104
|
+
detailedSummary += `\n\nArguments:\n${formattedArgs}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return askDetection(ctx, "tool", toolName, detailedSummary);
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
export default function (pi: ExtensionAPI) {
|
|
@@ -288,7 +303,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
288
303
|
if (typeof rawPath === "string" && rawPath) return rawPath;
|
|
289
304
|
if (event.toolName === "bash") {
|
|
290
305
|
const cmd = (event.input as { command?: string } | undefined)?.command;
|
|
291
|
-
return cmd ? `bash
|
|
306
|
+
return cmd ? `bash (${cmd})` : "bash";
|
|
292
307
|
}
|
|
293
308
|
return event.toolName;
|
|
294
309
|
})();
|
|
@@ -301,15 +316,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
301
316
|
if (choice === "program-session" && programId) {
|
|
302
317
|
grantProgramOutputSession(programId);
|
|
303
318
|
audit({ layer: 2, tool: event.toolName, decision: "session", grant: programId, note: "advanced-secure-output", findings, cwd: ctx.cwd });
|
|
304
|
-
ctx.ui.notify(
|
|
319
|
+
ctx.ui.notify(`🧠 Advanced Secure: you allowed all ${programId} output for the rest of this conversation.`, "info");
|
|
305
320
|
} else {
|
|
306
321
|
audit({ layer: 2, tool: event.toolName, decision: "yes", note: "advanced-secure-output", findings, cwd: ctx.cwd });
|
|
307
|
-
ctx.ui.notify(
|
|
322
|
+
ctx.ui.notify(`🧠 Advanced Secure: you allowed ${event.toolName} output to be sent.`, "warning");
|
|
308
323
|
}
|
|
309
324
|
return;
|
|
310
325
|
}
|
|
311
326
|
audit({ layer: 2, tool: event.toolName, decision: "no", note: "advanced-secure-output", findings, cwd: ctx.cwd });
|
|
312
|
-
ctx.ui.notify(
|
|
327
|
+
ctx.ui.notify(`🧠 Advanced Secure: blocked ${event.toolName} output — it may contain sensitive information. Nothing was sent to the model.`, "warning");
|
|
313
328
|
return { content: [{ type: "text", text: withheldNotice(subject) }] };
|
|
314
329
|
});
|
|
315
330
|
|
|
@@ -349,7 +364,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
349
364
|
}
|
|
350
365
|
recordProjectTrust(file, TRUST_STORE);
|
|
351
366
|
audit({ layer: 2, event: "project-trusted", file, applied: changes, cwd: ctx.cwd });
|
|
352
|
-
ctx.ui.notify("Done. The file applies now. Start a new
|
|
367
|
+
ctx.ui.notify("Done. The file applies now. Start a new conversation so the bash sandbox uses it too.", "info");
|
|
353
368
|
return;
|
|
354
369
|
}
|
|
355
370
|
if (!active) {
|
|
@@ -367,7 +382,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
367
382
|
` cwd: ${ctx.cwd}`,
|
|
368
383
|
` hasUI: ${(ctx as { hasUI?: boolean }).hasUI !== false}`,
|
|
369
384
|
` subagent.network: ${policy.subagent?.network ?? "allow"}`,
|
|
370
|
-
`
|
|
385
|
+
` conversation grants: ${sessionGrantSummary() || "(none)"}`,
|
|
371
386
|
` project file: ${!existsSync(projectPolicyPath(ctx.cwd)) ? "none" : projectTrusted(ctx.cwd) ? "trusted" : untrustedProjectChanges(ctx.cwd).length ? `not trusted, its weaker settings are ignored (/security trust)` : "only block rules, they apply"}`,
|
|
372
387
|
` outside project: read ${policy.filesystem.outsideProject?.read ?? "allow"}${(policy.filesystem.outsideProject?.allowRead ?? []).length ? `, allowRead ${policy.filesystem.outsideProject?.allowRead?.join(", ")}` : ""}`,
|
|
373
388
|
"",
|
|
@@ -46,7 +46,7 @@ const EXPLANATION = [
|
|
|
46
46
|
" No file contents are inspected.",
|
|
47
47
|
"",
|
|
48
48
|
"YOLO",
|
|
49
|
-
" Turns every layer off. Saved like the other modes, so new
|
|
49
|
+
" Turns every layer off. Saved like the other modes, so new conversations",
|
|
50
50
|
" start with it too until you pick Default or Advanced Secure again.",
|
|
51
51
|
].join("\n");
|
|
52
52
|
|
|
@@ -72,8 +72,8 @@ function renderSandboxChip(ctx: ExtensionContext): void {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function modeNotice(mode: PermissionMode): { text: string; level: "info" | "warning" } {
|
|
75
|
-
if (mode === "yolo") return { text: "⚠️ YOLO — all security layers disabled. Saved; new
|
|
76
|
-
if (mode === "advanced-secure") return { text: "
|
|
75
|
+
if (mode === "yolo") return { text: "⚠️ YOLO — all security layers disabled. Saved; new conversations start this way too.", level: "warning" };
|
|
76
|
+
if (mode === "advanced-secure") return { text: "🧠 Advanced Secure on. File reads, MCP calls, and tool output are checked for secrets.", level: "info" };
|
|
77
77
|
return { text: "🛡 Default mode. Rule-based policy only.", level: "info" };
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -92,7 +92,7 @@ async function pickMode(pi: ExtensionAPI, ctx: ExtensionContext, yolo: { active:
|
|
|
92
92
|
emitYolo(pi.events, true);
|
|
93
93
|
audit({ layer: 0, event: "permission-mode", mode: "yolo", cwd: ctx.cwd, note: "runtime-sync" });
|
|
94
94
|
renderSandboxChip(ctx);
|
|
95
|
-
ctx.ui.notify("⚠️ YOLO — all security layers disabled for this
|
|
95
|
+
ctx.ui.notify("⚠️ YOLO — all security layers disabled for this conversation.", "warning");
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
ctx.ui.notify(`Permission mode is already ${modeLabel(next)}.`, "info");
|
package/extensions/sandbox.ts
CHANGED
|
@@ -218,7 +218,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
218
|
` Allow Write: ${config.filesystem?.allowWrite?.join(", ") || "(none)"}`,
|
|
219
219
|
` Deny Write: ${config.filesystem?.denyWrite?.join(", ") || "(none)"}`,
|
|
220
220
|
` Outside project reads: ${config.filesystem?.outsideProject?.read ?? "allow"}`,
|
|
221
|
-
`
|
|
221
|
+
` Conversation grants: ${filesystemSessionGrantSummary() || "(none)"}`,
|
|
222
222
|
];
|
|
223
223
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
224
224
|
},
|
package/package.json
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-permission-system",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.12.0",
|
|
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",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"pi-package",
|
|
9
9
|
"pi",
|
|
10
10
|
"pi-coding-agent",
|
|
11
|
+
"claude-auto-mode-for-pi",
|
|
11
12
|
"pi-extension",
|
|
12
13
|
"pi-permission",
|
|
13
14
|
"pi-permissions",
|
|
14
15
|
"pi-permission-system",
|
|
15
16
|
"pi-security",
|
|
16
|
-
"cloud-auto-mode-for-pi",
|
|
17
17
|
"security-for-pi",
|
|
18
18
|
"permission-system-for-pi",
|
|
19
19
|
"secure-pi",
|
|
20
|
-
"
|
|
21
|
-
"pi-
|
|
20
|
+
"claude-auto-mode",
|
|
21
|
+
"pi-claude-auto-mode",
|
|
22
22
|
"auto-permission-system",
|
|
23
23
|
"auto-secure",
|
|
24
24
|
"secure",
|
|
25
25
|
"secure-it",
|
|
26
26
|
"auto-mode",
|
|
27
27
|
"safe-auto-mode",
|
|
28
|
-
"claude-auto-mode",
|
|
29
28
|
"data-breach-prevention",
|
|
30
29
|
"content-scan",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"auto-mode",
|
|
30
|
+
"claude",
|
|
31
|
+
"claude-security",
|
|
34
32
|
"permission",
|
|
35
33
|
"permissions",
|
|
36
34
|
"permission-system",
|
|
@@ -91,7 +89,7 @@
|
|
|
91
89
|
"assets/",
|
|
92
90
|
"sandbox.example.json",
|
|
93
91
|
"sandbox.default.json",
|
|
94
|
-
"sandbox.
|
|
92
|
+
"sandbox.claude.json",
|
|
95
93
|
"banner.png",
|
|
96
94
|
"README.md",
|
|
97
95
|
"CHANGELOG.md",
|
package/sandbox.default.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"enabled": true,
|
|
3
3
|
"mode": "advanced-secure",
|
|
4
4
|
"network": {
|
|
5
|
+
"allowLocalhost": true,
|
|
5
6
|
"allowedDomains": [
|
|
6
7
|
"registry.npmjs.org",
|
|
7
8
|
"registry.yarnpkg.com",
|
|
@@ -24,13 +25,22 @@
|
|
|
24
25
|
"rubygems.org",
|
|
25
26
|
"api.nuget.org",
|
|
26
27
|
"deno.land",
|
|
27
|
-
"jsr.io"
|
|
28
|
+
"jsr.io",
|
|
29
|
+
"api.openai.com",
|
|
30
|
+
"huggingface.co",
|
|
31
|
+
"*.huggingface.co",
|
|
32
|
+
"cdn-lfs.huggingface.co",
|
|
33
|
+
"*.diffusion.studio",
|
|
34
|
+
"diffusion.studio"
|
|
28
35
|
]
|
|
29
36
|
},
|
|
30
37
|
"filesystem": {
|
|
31
38
|
"outsideProject": {
|
|
32
39
|
"read": "allow",
|
|
33
|
-
"allowRead": [
|
|
40
|
+
"allowRead": [
|
|
41
|
+
"~/Downloads",
|
|
42
|
+
"~/.cache"
|
|
43
|
+
]
|
|
34
44
|
},
|
|
35
45
|
"denyRead": [],
|
|
36
46
|
"askRead": [
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: auto-permission-system
|
|
3
|
-
description: Use this skill when the user asks about auto-permission-system, security for Pi, permission system for Pi, how to secure Pi,
|
|
3
|
+
description: Use this skill when the user asks about auto-permission-system, security for Pi, permission system for Pi, how to secure Pi, Claude auto mode for Pi, security policy configuration, why a tool call was blocked, how to add an allow-list entry, or how to configure sandbox behavior. Covers the two-layer sandbox (OS-level bash sandbox and in-process tool guard), policy file locations and fields, Claude auto mode preset (sandbox.claude.json), ask-tier prompt options, absolute-deny paths, escape hatches (--yolo, --no-sandbox, /permission-mode → YOLO), the audit log, and common tasks like allowing a new domain or write path.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Security for Pi (Pi Permission System) — Skill
|
|
7
7
|
|
|
8
|
-
Use this skill when the user asks about the auto-permission-system extension, security for Pi, permission system for Pi, how to secure Pi, or configuring
|
|
8
|
+
Use this skill when the user asks about the auto-permission-system extension, security for Pi, permission system for Pi, how to secure Pi, or configuring Claude auto mode for Pi.
|
|
9
9
|
|
|
10
10
|
## What this extension does
|
|
11
11
|
|
|
@@ -14,19 +14,19 @@ Wraps the `bash` tool with `sandbox-exec` (macOS) or `bubblewrap` (Linux). Block
|
|
|
14
14
|
- Filesystem writes outside `allowWrite`
|
|
15
15
|
- Filesystem reads of `denyRead` paths (defaults include `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.pi/agent`)
|
|
16
16
|
- Filesystem reads outside the project when `outsideProject.read` is `"ask"` or `"deny"` — when set, the home dir (and the project's parent) is fenced with `denyRead` and the project plus `allowWrite`/`outsideProject.allowRead` are re-exposed (ADR-014). With `"ask"`, a bash command that *plainly* names an outside path is confirmed with you **before it runs** (ADR-015): allow (`yes — this once`, or a persistent `always` grant) → the real, complete output; deny → the command is not run at all (no silently trimmed output). `install`/`npm` under `~/.pi/agent` are pre-allowed via `outsideProject.allowRead`, so they never prompt. Obfuscated reads still hit the fence and stay masked.
|
|
17
|
-
- Network egress to domains not in `allowedDomains`: a host in neither list asks before the proxy decides (ADR-023) — `yes — this once` covers that bash command's connections to the host, `yes — for this
|
|
17
|
+
- Network egress to domains not in `allowedDomains`: a host in neither list asks before the proxy decides (ADR-023) — `yes — this once` covers that bash command's connections to the host, `yes — for this conversation` lasts until the conversation ends, and `always` writes `overrides.allowDomains` and applies live. A host in `deniedDomains`, or `network.strictAllowlist: true`, is a hard deny that is never ask-able; headless blocks.
|
|
18
18
|
|
|
19
19
|
**Layer 2 — In-process tool guard**
|
|
20
20
|
Hooks `tool_call` for `read`, `grep`, `find`, `ls`, `write`, `edit`, `fetch_content`, `web_search`, `get_search_content`. Applies the same policy file as Layer 1. For `grep`, `find` and `ls` the search root is checked like a `read` path. A `tool_result` hook also removes `grep` output lines from denied files beneath an allowed root (for example a `.env` deep in the tree); approving the root does not approve those files. When a call is blocked, shows an interactive prompt with persistence options.
|
|
21
21
|
|
|
22
|
-
**Layer 3 — Subagent posture &
|
|
23
|
-
When `ctx.hasUI === false` (subagents, `-p`, JSON mode, CI/CD runs), applies the `subagent.network` policy: `allow` (default) | `deny` | `research-only`. In
|
|
22
|
+
**Layer 3 — Subagent posture & Claude Auto Mode**
|
|
23
|
+
When `ctx.hasUI === false` (subagents, `-p`, JSON mode, CI/CD runs), applies the `subagent.network` policy: `allow` (default) | `deny` | `research-only`. In Claude Auto Mode, unapproved prompts fail closed.
|
|
24
24
|
|
|
25
25
|
## Policy files
|
|
26
26
|
|
|
27
27
|
Merged in order (later wins):
|
|
28
28
|
1. `sandbox.default.json` — shipped baseline (`outsideProject.read: "ask"`, full `denyRead`/`denyWrite`, `modelDenyRead`)
|
|
29
|
-
2. `sandbox.
|
|
29
|
+
2. `sandbox.claude.json` — shipped Claude Auto Mode preset for headless, Docker, and CI/CD runs (`strictAllowlist: true`, `outsideProject.read: "deny"`)
|
|
30
30
|
3. `~/.pi/agent/extensions/sandbox.json` — global user policy
|
|
31
31
|
4. `<cwd>/.pi/sandbox.json` — project-local overrides (also written by ask-tier prompts)
|
|
32
32
|
|
|
@@ -39,6 +39,7 @@ A higher layer only states what it changes; absent keys keep the layer below. A
|
|
|
39
39
|
"enabled": true,
|
|
40
40
|
"mode": "default", // "default" (rules) | "advanced-secure" (secret detection, ADR-018)
|
|
41
41
|
"network": {
|
|
42
|
+
"allowLocalhost": true, // true (default): localhost on all ports allowed; false: strict network isolation
|
|
42
43
|
"allowedDomains": ["github.com", "*.github.com"], // empty = allow all
|
|
43
44
|
"deniedDomains": [], // hard deny, never ask-able
|
|
44
45
|
"strictAllowlist": false // true = deny unknown hosts instead of asking
|
|
@@ -87,7 +88,7 @@ A project `.pi/sandbox.json` applies in full only after the user trusted its exa
|
|
|
87
88
|
When a normal (non-absolute-deny) call is blocked:
|
|
88
89
|
- **no — block** — hard deny (default, pre-selected)
|
|
89
90
|
- **yes — this once** — allow just this call
|
|
90
|
-
- **yes — for this
|
|
91
|
+
- **yes — for this conversation** — this file, its parent folder, or this domain until the conversation ends; not saved
|
|
91
92
|
- **always for CURRENT project** — whitelist this file in `<cwd>/.pi/sandbox.json`
|
|
92
93
|
- **always for CURRENT project (folder)** — whitelist the parent directory
|
|
93
94
|
- **always for ALL projects** — whitelist in `~/.pi/agent/extensions/sandbox.json`
|
|
@@ -99,7 +100,7 @@ For a Layer 1 bash **write** blocked after the command already ran, `yes — thi
|
|
|
99
100
|
|
|
100
101
|
Every prompt waits **15 seconds** by default and shows it live as `Default: … (Ns)` under the body. For file reads/writes no answer means the safe default: **block / deny**, and Esc blocks. For a network ask, an unanswered countdown means **allow once** (Esc still denies) — ADR-024. In the TUI the `Default:` line disappears as soon as you press ↑/↓ and the timer restarts, so the hint never competes with an active choice (ADR-031). The prompt is keyboard-only (ADR-036): ↑/↓ move the highlight, Enter selects, Esc cancels — a mouse click or wheel over the prompt does nothing.
|
|
101
102
|
|
|
102
|
-
The same options apply to a bash command whose network request reaches a host outside `allowedDomains` (ADR-023), with the countdown default and live footer from ADR-024/ADR-031: `No` / `Yes, just this once` / `Yes, all in group for this
|
|
103
|
+
The same options apply to a bash command whose network request reaches a host outside `allowedDomains` (ADR-023), with the countdown default and live footer from ADR-024/ADR-031: `No` / `Yes, just this once` / `Yes, all in group for this conversation` / `Yes, always…`, and an untouched countdown counts as allow-once. Because the sandbox proxy asks per connection, `Allow (default)` covers that bash command rather than one connection — redirects and parallel connections to the same host do not re-prompt. A host in `deniedDomains`, or `network.strictAllowlist: true`, is blocked outright: the prompt never appears. A remembered host is written to `overrides.allowDomains` and takes effect immediately, so the waiting request completes without a retry. Prompts from both layers are serialized through one queue (`src/ui/ask-flow.ts`), so two unknown hosts cannot replace each other's selector and hang the waiting connection.
|
|
103
104
|
|
|
104
105
|
## Sensitive reads ask
|
|
105
106
|
|
|
@@ -113,7 +114,7 @@ The same options apply to a bash command whose network request reaches a host ou
|
|
|
113
114
|
- **`advanced-secure`** (recommended) — adds secret/credential detection, and **only adds asks or blocks** (never loosens):
|
|
114
115
|
- file reads whose name looks like a credential store ask before the read;
|
|
115
116
|
- 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).
|
|
116
|
-
- before any tool/command/file output reaches the model, a secret-like hit shows the
|
|
117
|
+
- 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).
|
|
117
118
|
|
|
118
119
|
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).
|
|
119
120
|
|
|
@@ -31,6 +31,63 @@ export function isWriteBlockError(output: string): boolean {
|
|
|
31
31
|
return WRITE_BLOCK_RE.test(output);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Loopback / localhost connection refusal evidence in a sandboxed command's output.
|
|
36
|
+
* On Linux, bubblewrap runs with --unshare-net which isolates network namespaces.
|
|
37
|
+
* Host services listening on 127.0.0.1/localhost (like Diffusion Studio on port 3274,
|
|
38
|
+
* or local dev servers) are not reachable from inside the container namespace.
|
|
39
|
+
*/
|
|
40
|
+
const LOOPBACK_REFUSAL_RE =
|
|
41
|
+
/(?:Failed to connect to (?:127\.0\.0\.1|localhost|::1)|(?:connect|connection refused).*?(?:127\.0\.0\.1|localhost|::1)|(?:127\.0\.0\.1|localhost|::1).*?(?:connection refused|ECONNREFUSED)|ECONNREFUSED.*?(?:127\.0\.0\.1|localhost|::1)|Diffusion Studio is not running)/i;
|
|
42
|
+
|
|
43
|
+
/** True when the command failed due to connecting to a loopback/localhost address in the sandbox. */
|
|
44
|
+
export function isLoopbackConnectionRefused(output: string): boolean {
|
|
45
|
+
return LOOPBACK_REFUSAL_RE.test(output);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface LoopbackEndpointInfo {
|
|
49
|
+
host: string;
|
|
50
|
+
port?: string;
|
|
51
|
+
service?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract endpoint metadata (host, port, service name) from a loopback connection failure.
|
|
56
|
+
*/
|
|
57
|
+
export function extractLoopbackEndpoint(output: string, command?: string): LoopbackEndpointInfo | undefined {
|
|
58
|
+
if (!isLoopbackConnectionRefused(output)) return undefined;
|
|
59
|
+
|
|
60
|
+
// Check for Diffusion Studio specific error or command
|
|
61
|
+
const isDiffusion = /diffusion studio is not running/i.test(output) || (command && /\b(?:dapi|diffusion)\b/i.test(command));
|
|
62
|
+
if (isDiffusion) {
|
|
63
|
+
return { host: "127.0.0.1", port: "3274", service: "Diffusion Studio" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Match "Failed to connect to 127.0.0.1 port 3274" or "localhost port 8080"
|
|
67
|
+
const curlMatch = output.match(/Failed to connect to (127\.0\.0\.1|localhost|::1) port (\d+)/i);
|
|
68
|
+
if (curlMatch) {
|
|
69
|
+
return {
|
|
70
|
+
host: curlMatch[1],
|
|
71
|
+
port: curlMatch[2],
|
|
72
|
+
service: curlMatch[2] === "3274" ? "Diffusion Studio" : undefined,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Match "connect ECONNREFUSED 127.0.0.1:3274"
|
|
77
|
+
const nodeMatch = output.match(/(?:ECONNREFUSED|connect to)\s+(127\.0\.0\.1|localhost|::1)(?::| port )(\d+)/i);
|
|
78
|
+
if (nodeMatch) {
|
|
79
|
+
return {
|
|
80
|
+
host: nodeMatch[1],
|
|
81
|
+
port: nodeMatch[2],
|
|
82
|
+
service: nodeMatch[2] === "3274" ? "Diffusion Studio" : undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Generic host match
|
|
87
|
+
const hostMatch = output.match(/(127\.0\.0\.1|localhost|::1)/i);
|
|
88
|
+
return { host: hostMatch ? hostMatch[1] : "127.0.0.1" };
|
|
89
|
+
}
|
|
90
|
+
|
|
34
91
|
/** A shell-name/empty token is not a path; everything else resolves against cwd unless absolute. */
|
|
35
92
|
function toAbsolutePath(rawToken: string, cwd: string, home: string): string | undefined {
|
|
36
93
|
let token = rawToken.trim().replace(/[.,;]+$/, "");
|
package/src/core/trust.ts
CHANGED
|
@@ -67,7 +67,7 @@ export function isProjectFileDeclined(projectPath: string, storePath: string): b
|
|
|
67
67
|
* and share only the store file (ADR-018/ADR-020).
|
|
68
68
|
*/
|
|
69
69
|
export interface ProjectTrust {
|
|
70
|
-
/** Record pi's own project-trust answer for this
|
|
70
|
+
/** Record pi's own project-trust answer for this conversation. Declined is never trusted. */
|
|
71
71
|
setDeclined(value: boolean): void;
|
|
72
72
|
/** The project file applies in full only when its current hash is recorded and pi did not decline. */
|
|
73
73
|
isTrusted(cwd: string): boolean;
|
|
@@ -181,6 +181,33 @@ export const COMMON_NON_SECRET_VALUES = new Set([
|
|
|
181
181
|
"secretvalue",
|
|
182
182
|
"somevalue",
|
|
183
183
|
"myvalue",
|
|
184
|
+
// Common keyboard keys / UI events (e.g. `Successfully pressed key: Backspace`)
|
|
185
|
+
"backspace",
|
|
186
|
+
"tab",
|
|
187
|
+
"enter",
|
|
188
|
+
"return",
|
|
189
|
+
"escape",
|
|
190
|
+
"esc",
|
|
191
|
+
"space",
|
|
192
|
+
"pageup",
|
|
193
|
+
"pagedown",
|
|
194
|
+
"end",
|
|
195
|
+
"home",
|
|
196
|
+
"arrowleft",
|
|
197
|
+
"arrowup",
|
|
198
|
+
"arrowright",
|
|
199
|
+
"arrowdown",
|
|
200
|
+
"left",
|
|
201
|
+
"up",
|
|
202
|
+
"right",
|
|
203
|
+
"down",
|
|
204
|
+
"delete",
|
|
205
|
+
"insert",
|
|
206
|
+
"capslock",
|
|
207
|
+
"numlock",
|
|
208
|
+
"scrolllock",
|
|
209
|
+
"printscreen",
|
|
210
|
+
"pause",
|
|
184
211
|
]);
|
|
185
212
|
|
|
186
213
|
/**
|