auto-permission-system 3.6.0 → 3.8.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/README.md +91 -5
- package/extensions/guard.ts +17 -35
- package/extensions/permission-mode.ts +9 -5
- package/extensions/sandbox.ts +5 -18
- package/package.json +29 -6
- package/sandbox.cloud.json +62 -0
- package/sandbox.example.json +10 -0
- package/skills/auto-permission-system/SKILL.md +25 -9
- package/src/core/grep-filter.ts +12 -12
- package/src/core/index.ts +4 -1
- package/src/core/policy/classify.ts +22 -10
- package/src/core/policy/commands.ts +17 -15
- package/src/core/policy/default-file.ts +8 -9
- package/src/core/policy/defaults.ts +9 -0
- package/src/core/policy/domains.ts +5 -5
- package/src/core/policy/fence.ts +44 -37
- package/src/core/policy/merge.ts +12 -12
- package/src/core/policy/mode.ts +17 -8
- package/src/core/policy/overrides.ts +56 -0
- package/src/core/policy/paths.ts +16 -0
- package/src/core/policy/patterns.ts +87 -51
- package/src/core/policy/{files.ts → policy-file.ts} +11 -6
- package/src/core/policy/subagent.ts +11 -0
- package/src/core/trust.ts +155 -92
- package/src/core/user-named.ts +36 -32
- package/src/detect/filename-gate/classify.ts +0 -9
- package/src/detect/mcp-gate/constants.ts +2 -0
- package/src/detect/mcp-gate/evaluate.ts +36 -22
- package/src/detect/mcp-gate/index.ts +2 -0
- package/src/detect/mcp-gate/signals.ts +2 -13
- package/src/detect/mcp-gate/verbs.ts +31 -9
- package/src/detect/sensitive-file-scanner/assignment.ts +3 -6
- package/src/detect/sensitive-file-scanner/content-scan.ts +0 -4
- package/src/detect/sensitive-file-scanner/file-scan.ts +0 -10
- package/src/detect/sensitive-file-scanner/jwt.ts +4 -5
- package/src/detect/sensitive-file-scanner/redaction.ts +0 -6
- package/src/detect/sensitive-file-scanner/value-analysis.ts +4 -15
- package/src/detect/tool-output-scanner/evaluate.ts +0 -1
- package/src/detect/tool-output-scanner/scan.ts +0 -2
- package/src/l1-sandbox/bash-ops.ts +192 -103
- package/src/l1-sandbox/config.ts +42 -68
- package/src/l1-sandbox/manager.ts +69 -37
- package/src/l1-sandbox/network-ask.ts +61 -52
- package/src/l2-guard/matching.ts +66 -94
- package/src/l2-guard/policy.ts +36 -40
- package/src/l2-guard/prompts.ts +61 -65
- package/src/l2-guard/subagent.ts +11 -11
- package/src/shared/audit.ts +0 -1
- package/src/shared/canonicalize.ts +29 -0
- package/src/shared/yolo.ts +0 -1
- package/src/ui/ask-flow.ts +160 -82
- package/src/ui/ask-selector.ts +28 -10
- package/src/ui/ask.ts +157 -126
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Auto Permission System: Cloud Auto Mode & Security for Pi Coding Agent
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/auto-permission-system)
|
|
4
4
|
[](https://www.npmjs.com/package/auto-permission-system)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](https://pi.dev/packages)
|
|
7
7
|
[](https://github.com/Navin009/auto-permission-system/actions/workflows/ci.yml)
|
|
8
|
+
[](#cloud-auto-mode-for-pi)
|
|
9
|
+
[](#security-for-pi--how-to-secure-pi)
|
|
10
|
+
[](#permission-system-for-pi-pi-permission-system)
|
|
8
11
|
|
|
9
|
-
**
|
|
10
|
-
It puts allow/deny rules, interactive ask prompts, secret detection and an audit log around every tool the agent can call — file reads and writes, shell commands, searches and network access — so the agent can work in your repository without reaching your credentials or the wider internet by accident.
|
|
12
|
+
**Auto Permission System** is the comprehensive toolkit providing **cloud auto mode for Pi**, **security for Pi**, and a zero-trust **permission system for Pi** ([pi.dev](https://pi.dev)). Whether you need an interactive **Pi permission system** with real-time ask prompts, an automated **PyPI permission system** to guard `.pypirc` and `pip install` commands, or a hardened **cloud auto mode** to **secure Pi** in headless CI/CD environments, this extension enforces multi-layer boundaries across filesystem access, network egress, shell commands, and MCP tools.
|
|
11
13
|
|
|
12
14
|
Enforcement happens in three layers:
|
|
13
15
|
|
|
@@ -22,6 +24,10 @@ When a tool call is blocked you get an interactive prompt — no need to leave p
|
|
|
22
24
|
- [Install](#install)
|
|
23
25
|
- [Requirements](#requirements)
|
|
24
26
|
- [Configuration](#configuration)
|
|
27
|
+
- [Cloud Auto Mode for Pi](#cloud-auto-mode-for-pi)
|
|
28
|
+
- [Security for Pi & How to Secure Pi](#security-for-pi--how-to-secure-pi)
|
|
29
|
+
- [Permission System for Pi (Pi Permission System)](#permission-system-for-pi-pi-permission-system)
|
|
30
|
+
- [PyPI & Python Ecosystem Security](#pypi--python-ecosystem-security)
|
|
25
31
|
- [Commands](#commands)
|
|
26
32
|
- [Escape hatches](#escape-hatches)
|
|
27
33
|
- [FAQ](#faq)
|
|
@@ -89,6 +95,14 @@ A higher layer only states what it changes; absent keys keep the layer below. A
|
|
|
89
95
|
"subagent": {
|
|
90
96
|
"network": "allow" // "allow" | "deny" | "research-only"
|
|
91
97
|
},
|
|
98
|
+
"mcp": { // Advanced Secure MCP gate tuning (ADR-032)
|
|
99
|
+
"allowTools": ["mcp__diffusion_studio__context"], // exact names that never ask
|
|
100
|
+
"allowPrefixes": ["mcp__ticktick__get_"], // name prefixes that never ask
|
|
101
|
+
"askTools": [], // exact names that always ask
|
|
102
|
+
"trustAnnotations": false, // true = a server's readOnlyHint allows without asking
|
|
103
|
+
"allowSimpleUpdates": true, // narrow field updates skip the ask
|
|
104
|
+
"askThreshold": 30 // risk score at which a call asks (lower = stricter)
|
|
105
|
+
},
|
|
92
106
|
"commands": {
|
|
93
107
|
"ask": ["printenv", "env", "/proc/*/environ"] // bash: ask before these (they can print env tokens)
|
|
94
108
|
}
|
|
@@ -124,7 +138,7 @@ Never asked about: the project itself, your `allowWrite` roots, pi's own package
|
|
|
124
138
|
|
|
125
139
|
### Ask-tier prompt
|
|
126
140
|
|
|
127
|
-
`no — block` is pre-selected, so Enter alone blocks. **Network (domain) asks are the exception** (ADR-024): the countdown default is `Allow once` — the footer reads `Default: Yes, just this once (15s)` — so an unanswered countdown counts as allow-once for that bash command, while Enter and Esc still block/deny. Once you press ↑/↓ the `Default:` line disappears and the 15s activity timer restarts (ADR-031). Besides `yes — this once` and the persistent `always` options there is `yes — for this session` (this file, its folder, or this domain), kept in memory until the session ends and never saved (ADR-010). A bash command that reaches a domain outside `allowedDomains` gets the same prompt (ADR-023); because the sandbox proxy asks per connection, `yes — this once` covers that command's connections to the host, and a remembered host is written to `overrides.allowDomains` and applies immediately. Hosts in `deniedDomains`, or with `strictAllowlist: true`, are blocked without a prompt. Prompts are shown one at a time: the extension serializes them, because pi's selector is a singleton and two overlapping prompts can orphan each other and hang the waiting connection. For a bash write that the sandbox refused after the command already ran, `yes — this once` re-runs the command once with that folder allowed for the one invocation only (never saved), and `denyWrite` still wins. The refusal is read from the command's own error text — `EPERM` / `Operation not permitted` on macOS, `EROFS` / `Read-only file system` on Linux (ADR-022) — so a Linux EROFS gets the same prompt instead of a silent read-only failure; a refused write is never mistaken for an outside-project read.
|
|
141
|
+
`no — block` is pre-selected, so Enter alone blocks. **Network (domain) asks are the exception** (ADR-024): the countdown default is `Allow once` — the footer reads `Default: Yes, just this once (15s)` — so an unanswered countdown counts as allow-once for that bash command, while Enter and Esc still block/deny. Once you press ↑/↓ the `Default:` line disappears and the 15s activity timer restarts (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. Besides `yes — this once` and the persistent `always` options there is `yes — for this session` (this file, its folder, or this domain), kept in memory until the session ends and never saved (ADR-010). A bash command that reaches a domain outside `allowedDomains` gets the same prompt (ADR-023); because the sandbox proxy asks per connection, `yes — this once` covers that command's connections to the host, and a remembered host is written to `overrides.allowDomains` and applies immediately. Hosts in `deniedDomains`, or with `strictAllowlist: true`, are blocked without a prompt. Prompts are shown one at a time: the extension serializes them, because pi's selector is a singleton and two overlapping prompts can orphan each other and hang the waiting connection. For a bash write that the sandbox refused after the command already ran, `yes — this once` re-runs the command once with that folder allowed for the one invocation only (never saved), and `denyWrite` still wins. The refusal is read from the command's own error text — `EPERM` / `Operation not permitted` on macOS, `EROFS` / `Read-only file system` on Linux (ADR-022) — so a Linux EROFS gets the same prompt instead of a silent read-only failure; a refused write is never mistaken for an outside-project read.
|
|
128
142
|
|
|
129
143
|
Every prompt waits **10 seconds** by default. For file reads and writes, no answer resolves to the safe default: **block / deny**. For a network ask, no answer resolves to **allow once** (ADR-024); Esc still denies.
|
|
130
144
|
|
|
@@ -139,7 +153,7 @@ An entry without `/` or `~` (`.env`, `*.key`) is a file name. pi's own tools (La
|
|
|
139
153
|
- **`default`** (shipped) — rules only: sandbox.json paths, domains, and commands. Nothing inspects file contents.
|
|
140
154
|
- **`advanced-secure`** (recommended) — adds secret/credential detection:
|
|
141
155
|
- **File reads:** a filename that looks like a credential store asks before the read.
|
|
142
|
-
- **MCP calls:** `mcp__<server>__<tool>` calls are classified; a risky (mutating
|
|
156
|
+
- **MCP calls:** `mcp__<server>__<tool>` calls are classified; a risky call asks before it runs. The gate ignores the server namespace, reads the operation from the head verb of the tool name (or the first verb of the description), and asks for destructive names, mutating names (`update_*`, `create_*`, `run_*`, `evaluate_*`), and destructive arguments (SQL `DELETE`, `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` — `allowTools` / `allowPrefixes` skip asks, `askTools` forces them, `trustAnnotations` trusts a server's `readOnlyHint`, `askThreshold` moves the score line (ADR-032).
|
|
143
157
|
- **Tool/command/file output:** before any output reaches the model, a secret-like hit shows `⚠ Sensitive information detected`, the file, and each detected line as `lineNo: text`, and the question **Should the AI be allowed to see it?** with two choices — **No, keep private** or **Yes, allow**. A bare `key` counts only when the value looks machine-generated: `key=<random>` asks, while `key=value`, `key=subagent.network`, and code such as `const key = x` stay clean. No, keep private withholds the output and tells the model plainly that the output was withheld because it may contain sensitive information, so the model does not mistake it for a failure or an empty result. Esc or timeout keeps it private.
|
|
144
158
|
|
|
145
159
|
Detection can only **add** asks or blocks; it never loosens a rule. The mode is layered like every other key: an untrusted project may opt *in* to `advanced-secure`, but only a trusted project (or the global file) may turn it off.
|
|
@@ -164,6 +178,72 @@ Access to `~/.ssh`, `~/.gnupg`, `~/.aws`, `*.pem`, `*.key` and pi's own `~/.pi/a
|
|
|
164
178
|
|
|
165
179
|
`sandbox.json` is strict JSON: no comments, no trailing commas. If a file does not parse, both layers skip it and use the built-in defaults for it. Layer 2 then shows an error at session start and writes `policy-parse-error` to the audit log. The "always" prompts refuse to write to a file they cannot parse, so a hand-written policy is never replaced.
|
|
166
180
|
|
|
181
|
+
## Cloud Auto Mode for Pi
|
|
182
|
+
|
|
183
|
+
When running the Pi coding agent autonomously in the cloud, CI/CD pipelines, Docker containers, remote devboxes (GitHub Codespaces, Gitpod, Daytona), or headless subagents (`pi -p`, JSON mode), interactive terminal ask prompts cannot be answered by a human.
|
|
184
|
+
|
|
185
|
+
**Cloud Auto Mode for Pi** provides a deterministic, zero-trust security configuration engineered for autonomous execution:
|
|
186
|
+
|
|
187
|
+
- **Fail-Closed by Design:** Any tool call or bash command requesting an unlisted domain, credential path, or unapproved write fails closed immediately with an informative audit reason, preventing unmonitored agent deviations.
|
|
188
|
+
- **Pre-Configured Cloud Preset (`sandbox.cloud.json`):** Shipped directly in this package, `sandbox.cloud.json` sets `strictAllowlist: true`, locks `outsideProject.read: "deny"`, restricts subagent network egress, and denies read/write access to cloud credentials (`~/.aws`, `~/.azure`, `~/.config/gcloud`).
|
|
189
|
+
- **Subagent Posture Control (`subagent.network`):** Prevents subagents from opening external network sockets or exfiltrating private codebase data (`"deny"` or `"research-only"`).
|
|
190
|
+
|
|
191
|
+
### Activating Cloud Auto Mode in Your Repository or CI:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# 1. Copy the shipped Cloud Auto Mode template to your repository:
|
|
195
|
+
cp node_modules/auto-permission-system/sandbox.cloud.json .pi/sandbox.json
|
|
196
|
+
|
|
197
|
+
# 2. Run Pi headlessly with complete automated protection:
|
|
198
|
+
pi -p "Review pull request changes and execute test suite"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Security for Pi & How to Secure Pi
|
|
204
|
+
|
|
205
|
+
AI coding agents are granted powerful system tools—arbitrary bash command execution, filesystem reads and edits, URL fetching, and MCP integration. Without guardrails, prompt injections or untrusted code can compromise developer environments.
|
|
206
|
+
|
|
207
|
+
### The 3-Layer Defense in Depth:
|
|
208
|
+
|
|
209
|
+
1. **OS-Level Kernel Sandbox (Layer 1):** Uses `bubblewrap` (Linux) or `sandbox-exec` (macOS) to confine bash processes at the OS kernel boundary. Bash subprocesses cannot write outside designated directories or read forbidden directories even if child processes are spawned.
|
|
210
|
+
2. **In-Process Tool Guard (Layer 2):** Guards the agent's internal tools (`read`, `write`, `edit`, `grep`, `find`, `ls`, `fetch_content`, `web_search`). Denied files are filtered out of directory listings and search outputs before the LLM can observe them.
|
|
211
|
+
3. **Secret Detection & Output Redactor (Auto Secure):** Analyzes tool outputs and bash output streams in real time. Credentials, private keys, API tokens, and environment secrets are masked before reaching the model context window.
|
|
212
|
+
|
|
213
|
+
### Quick Checklist: How to Secure Pi in 3 Steps
|
|
214
|
+
- **Step 1:** Install `auto-permission-system` (`pi install npm:auto-permission-system`).
|
|
215
|
+
- **Step 2:** Ensure `/permission-mode` is set to **Advanced Secure** (Auto Secure).
|
|
216
|
+
- **Step 3:** For CI/CD and cloud runs, adopt `sandbox.cloud.json` to enforce strict domain and path boundaries.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Permission System for Pi (Pi Permission System)
|
|
221
|
+
|
|
222
|
+
The **Pi permission system** in `auto-permission-system` offers granular allow/ask/deny control with session memory and repository-level persistence:
|
|
223
|
+
|
|
224
|
+
| Policy Tier | Behavior | Example Subjects |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| **Absolute Deny** | Always blocked. Never prompts. | `~/.ssh`, `~/.aws`, `auth.json`, `*.pem`, `*.key` |
|
|
227
|
+
| **Ask (Interactive)** | Prompts user with 10s countdown timer (`Allow once`, `Always for project`, `Always global`). | `.env`, unlisted domains (`api.example.com`), bash `printenv` |
|
|
228
|
+
| **Allowed Roots** | Allowed transparently without prompting. | Project root `.`, `/tmp`, trusted package registries |
|
|
229
|
+
| **Cloud Auto Mode** | In headless runs (`-p`), unapproved operations fail closed without blocking execution. | Headless subagents, Docker containers, GitHub Actions |
|
|
230
|
+
|
|
231
|
+
### Permission Modes Available:
|
|
232
|
+
- **`advanced-secure` (Auto Secure / Recommended):** Full rule engine + real-time AST/regex secret detection + MCP tool risk scoring.
|
|
233
|
+
- **`default`:** Rule-based sandbox without inspecting payload content.
|
|
234
|
+
- **`yolo`:** Emergency escape hatch that temporarily bypasses all security layers.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## PyPI & Python Ecosystem Security
|
|
239
|
+
|
|
240
|
+
`auto-permission-system` includes dedicated, out-of-the-box safeguards for Python developers and PyPI packaging workflows:
|
|
241
|
+
|
|
242
|
+
- **PyPI Token Protection:** Scans all tool outputs and bash streams in real time for exposed PyPI API tokens (matching `pypi-AgEI...` token signatures) and redacts them before the language model can read or exfiltrate them.
|
|
243
|
+
- **Sensitive Credentials Guard (`.pypirc`):** `~/.pypirc` and package registry credentials are gated under strict deny-read rules, preventing AI coding agents from accessing PyPI deployment credentials.
|
|
244
|
+
- **Governed `pip install` Network Egress:** Outgoing requests to `pypi.org` and `*.pypi.org` are pre-configured in the baseline domain allowlist (`sandbox.default.json`), allowing standard package resolution while blocking unvetted third-party download mirrors or suspicious domains.
|
|
245
|
+
- **Shield Against Malicious Package Setup Scripts:** When installing unfamiliar dependencies or running untrusted Python code, Layer 1 OS-level sandboxing (`bubblewrap` / `sandbox-exec`) confines `python setup.py` and bash commands within the project root, stopping arbitrary filesystem tampering outside allowed directories.
|
|
246
|
+
|
|
167
247
|
## Commands
|
|
168
248
|
|
|
169
249
|
| Command | Description |
|
|
@@ -200,6 +280,12 @@ Every block/allow/always decision is appended to `~/.pi/agent/audit.log` as a JS
|
|
|
200
280
|
|
|
201
281
|
## FAQ
|
|
202
282
|
|
|
283
|
+
### How does Cloud Auto Mode secure unattended or headless runs?
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
### How does this package secure PyPI tokens and Python packages?
|
|
287
|
+
Auto Permission System includes native pattern recognizers for PyPI API tokens (`pypi-*`), hard-blocks model access to `~/.pypirc`, and gates `pip install` network requests to trusted hosts (`pypi.org`, `*.pypi.org`), preventing AI agents from leaking deployment secrets or pulling unvetted dependencies.
|
|
288
|
+
|
|
203
289
|
### How do I allow the agent to reach a new domain?
|
|
204
290
|
Add it to `network.allowedDomains` in a policy file (global `~/.pi/agent/extensions/sandbox.json` or project `<cwd>/.pi/sandbox.json`), or just run the tool (or a bash `curl`) once and pick an **always** option in the ask prompt — that writes the domain to the project file for you.
|
|
205
291
|
|
package/extensions/guard.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { isAbsolute, join, resolve } from "node:path";
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { isToolCallEventType, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { filterGrepOutput, policyFileError, forgetProjectTrust, isProjectFileDeclined, recordProjectDeclined, recordProjectTrust } from "../src/core/index";
|
|
14
|
-
import { classifyFilename, evaluateMcpCall, type JsonSchemaLike, type McpToolAnnotations } from "../src/detect/index";
|
|
14
|
+
import { classifyFilename, evaluateMcpCall, type JsonSchemaLike, type McpPolicy, type McpToolAnnotations } from "../src/detect/index";
|
|
15
15
|
import { collectExposure } from "../src/l2-guard/exposure";
|
|
16
16
|
import { askExposure, askMain, withheldNotice } from "../src/ui/ask-flow";
|
|
17
17
|
import { loadPolicy, projectPolicyPath, projectTrusted, setPiDeclinedTrust, untrustedProjectChanges, bullets, TRUST_STORE } from "../src/l2-guard/policy";
|
|
@@ -77,6 +77,7 @@ async function detectMcpAsk(
|
|
|
77
77
|
toolName: string,
|
|
78
78
|
input: Record<string, unknown>,
|
|
79
79
|
ctx: UICtx,
|
|
80
|
+
mcpPolicy?: McpPolicy,
|
|
80
81
|
): Promise<{ block: true; reason: string } | null> {
|
|
81
82
|
const info = pi.getAllTools().find((t) => t.name === toolName);
|
|
82
83
|
const result = evaluateMcpCall({
|
|
@@ -87,6 +88,7 @@ async function detectMcpAsk(
|
|
|
87
88
|
annotations: /* SAFETY: pi's ToolAnnotations mirrors the MCP hints McpToolAnnotations names. */ info?.annotations as McpToolAnnotations | undefined,
|
|
88
89
|
},
|
|
89
90
|
call: { name: toolName, arguments: input },
|
|
91
|
+
policy: mcpPolicy,
|
|
90
92
|
});
|
|
91
93
|
if (result.decision !== "ask") return null;
|
|
92
94
|
return askDetection(ctx, "tool", toolName, result.summary);
|
|
@@ -101,10 +103,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
101
103
|
active = policyEnabled && !runtimeYolo;
|
|
102
104
|
};
|
|
103
105
|
|
|
104
|
-
// `--yolo` and the `/permission-mode` toggle both arrive on pi's process-wide
|
|
105
|
-
// bus; `sandbox.ts` is the only extension that registers the CLI flags and
|
|
106
|
-
// broadcasts the state (ADR-020). The entrypoints share no memory, so this is
|
|
107
|
-
// the only channel that reaches Layer 2.
|
|
108
106
|
onYolo(pi.events, (enabled) => {
|
|
109
107
|
runtimeYolo = enabled;
|
|
110
108
|
applyActive();
|
|
@@ -112,8 +110,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
112
110
|
});
|
|
113
111
|
|
|
114
112
|
pi.on("session_start", (_event, ctx) => {
|
|
115
|
-
// A policy file that does not parse is skipped and the defaults apply.
|
|
116
|
-
// Say so loudly: otherwise one trailing comma silently disables the whole file.
|
|
117
113
|
for (const p of [`${getAgentDir()}/extensions/sandbox.json`, `${ctx.cwd}/.pi/sandbox.json`]) {
|
|
118
114
|
const err = policyFileError(p);
|
|
119
115
|
if (!err) continue;
|
|
@@ -151,14 +147,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
151
147
|
if (!active) return;
|
|
152
148
|
const policy = loadPolicy(ctx.cwd);
|
|
153
149
|
|
|
154
|
-
// Capture the program id (binary for bash, MCP server for mcp__ tools) so the
|
|
155
|
-
// Advanced Secure output gate can label the prompt and honor a session grant
|
|
156
|
-
// keyed by that program. Falls back to the tool name when nothing else fits.
|
|
157
150
|
const programId = programIdForToolCall(event.toolName, event.input);
|
|
158
151
|
if (event.toolCallId && programId) programIdByCallId.set(event.toolCallId, programId);
|
|
159
152
|
|
|
160
|
-
// --- Path-based gates (with ask-tier prompt) ---
|
|
161
|
-
// One gate for every read: the read policy first, then the project boundary.
|
|
162
153
|
const gateRead = async (rawPath: string, tool: string) => {
|
|
163
154
|
const abs = canonicalize(rawPath, ctx.cwd);
|
|
164
155
|
let reason = isDeniedRead(rawPath, ctx.cwd, policy);
|
|
@@ -190,10 +181,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
190
181
|
}
|
|
191
182
|
}
|
|
192
183
|
}
|
|
193
|
-
// Read-only search tools. grep returns file contents and runs ripgrep in
|
|
194
|
-
// process, so neither the bash sandbox nor the read gate saw it before.
|
|
195
|
-
// Gate the search root here; tool_result below drops output lines from
|
|
196
|
-
// denied files beneath the root.
|
|
197
184
|
const searchRoot = isToolCallEventType("grep", event) || isToolCallEventType("find", event) || isToolCallEventType("ls", event)
|
|
198
185
|
? (event.input.path ?? ".")
|
|
199
186
|
: null;
|
|
@@ -218,7 +205,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
205
|
}
|
|
219
206
|
}
|
|
220
207
|
|
|
221
|
-
// --- URL/domain gates (best-effort by tool name; tools are extension-defined) ---
|
|
222
208
|
const input = event.input as Record<string, unknown> | undefined;
|
|
223
209
|
const collectUrls = (): string[] => {
|
|
224
210
|
if (!input) return [];
|
|
@@ -234,8 +220,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
234
220
|
const saReason = subagentNetworkBlock(ctx as { hasUI?: boolean; sessionManager?: unknown }, policy);
|
|
235
221
|
if (saReason) return { block: true, reason: saReason };
|
|
236
222
|
for (const u of collectUrls()) {
|
|
237
|
-
// deniedDomains is the hard-deny tier: the bash proxy refuses it before
|
|
238
|
-
// any ask, so Layer 2 must not offer to override it either.
|
|
239
223
|
const denied = deniedUrlReason(u, policy);
|
|
240
224
|
if (denied) {
|
|
241
225
|
audit({ layer: 2, tool: event.toolName, subject: u, reason: denied, decision: "no", note: "denied-domain", cwd: ctx.cwd });
|
|
@@ -245,7 +229,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
245
229
|
if (!reason) continue;
|
|
246
230
|
const host = hostnameOf(u) ?? u;
|
|
247
231
|
const result = await askOrBlock(/* SAFETY: pi's ctx carries cwd/UI at runtime. */ ctx as unknown as UICtx, { layer: 2, tool: event.toolName, subject: u, reason, overrideKind: "allowDomains", overrideValue: host }, null, (kind, value) => {
|
|
248
|
-
// Layer 1's proxy does not re-read the file: apply the remembered host live (ADR-023).
|
|
249
232
|
if (kind === "allowDomains") emitDomainGrant(pi.events, value);
|
|
250
233
|
});
|
|
251
234
|
if (result) return result;
|
|
@@ -255,22 +238,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
255
238
|
if (event.toolName === "web_search") {
|
|
256
239
|
const saReason = subagentNetworkBlock(ctx as { hasUI?: boolean; sessionManager?: unknown }, policy);
|
|
257
240
|
if (saReason) return { block: true, reason: saReason };
|
|
258
|
-
// web_search itself goes to the search provider (out of scope for v1
|
|
259
|
-
// allowlist). The follow-on fetch_content for individual results is
|
|
260
|
-
// the catchable surface.
|
|
261
241
|
}
|
|
262
242
|
|
|
263
|
-
// Advanced Secure (ADR-018): classify MCP tool calls before they run.
|
|
264
243
|
if (policy.mode === "advanced-secure" && event.toolName.startsWith("mcp__")) {
|
|
265
|
-
const det = await detectMcpAsk(pi, event.toolName, (event.input ?? {}) as Record<string, unknown>, /* SAFETY: pi's ctx carries cwd/UI at runtime. */ ctx as unknown as UICtx);
|
|
244
|
+
const det = await detectMcpAsk(pi, event.toolName, (event.input ?? {}) as Record<string, unknown>, /* SAFETY: pi's ctx carries cwd/UI at runtime. */ ctx as unknown as UICtx, policy.mcp);
|
|
266
245
|
if (det) return det;
|
|
267
246
|
}
|
|
268
247
|
});
|
|
269
248
|
|
|
270
|
-
// grep output filter. A root that passed the gate can still contain denied
|
|
271
|
-
// files (`.env`, `*.pem`, `~/.ssh` under a search of `~`). Approving the
|
|
272
|
-
// root does not approve the denied files beneath it. Runs before the
|
|
273
|
-
// result reaches the model.
|
|
274
249
|
pi.on("tool_result", (event, ctx) => {
|
|
275
250
|
if (!active || event.toolName !== "grep" || event.isError) return;
|
|
276
251
|
const input = event.input as { path?: string } | undefined;
|
|
@@ -282,7 +257,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
282
257
|
} catch {
|
|
283
258
|
return;
|
|
284
259
|
}
|
|
285
|
-
// A single-file search prints only the basename; its root gate already decided.
|
|
286
260
|
if (!rootIsDir) return;
|
|
287
261
|
let removedLines = 0;
|
|
288
262
|
const removedFiles = new Set<string>();
|
|
@@ -299,10 +273,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
299
273
|
return { content };
|
|
300
274
|
});
|
|
301
275
|
|
|
302
|
-
// Advanced Secure (ADR-018): before any tool output reaches the model, ask
|
|
303
|
-
// the user when it looks like it carries a secret. Block withholds it and
|
|
304
|
-
// tells the model plainly, so the model does not read it as a failure or an
|
|
305
|
-
// empty result; Allow send passes the real values through.
|
|
306
276
|
pi.on("tool_result", async (event, ctx) => {
|
|
307
277
|
if (!active || event.isError) return;
|
|
308
278
|
const policy = loadPolicy(ctx.cwd);
|
|
@@ -415,6 +385,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
415
385
|
` allowWrite: ${(overrides.allowWrite ?? []).join(", ") || "(none)"}`,
|
|
416
386
|
` allowDomains: ${(overrides.allowDomains ?? []).join(", ") || "(none)"}`,
|
|
417
387
|
];
|
|
388
|
+
if (policy.mode === "advanced-secure") {
|
|
389
|
+
const mcp = policy.mcp ?? {};
|
|
390
|
+
lines.push(
|
|
391
|
+
"",
|
|
392
|
+
"Advanced Secure (MCP gate, ADR-032):",
|
|
393
|
+
` allowTools: ${(mcp.allowTools ?? []).join(", ") || "(none)"}`,
|
|
394
|
+
` allowPrefixes: ${(mcp.allowPrefixes ?? []).join(", ") || "(none)"}`,
|
|
395
|
+
` askTools: ${(mcp.askTools ?? []).join(", ") || "(none)"}`,
|
|
396
|
+
` trustAnnotations: ${mcp.trustAnnotations === true}`,
|
|
397
|
+
` allowSimpleUpdates: ${mcp.allowSimpleUpdates !== false}`,
|
|
398
|
+
` askThreshold: ${mcp.askThreshold ?? 30}`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
418
401
|
try {
|
|
419
402
|
const tail = readFileSync(AUDIT_PATH, "utf-8").trim().split("\n").slice(-10);
|
|
420
403
|
if (tail.length && tail[0]) {
|
|
@@ -422,7 +405,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
422
405
|
for (const l of tail) lines.push(` ${l}`);
|
|
423
406
|
}
|
|
424
407
|
} catch {
|
|
425
|
-
/* no audit log yet */
|
|
426
408
|
}
|
|
427
409
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
428
410
|
},
|
|
@@ -84,9 +84,17 @@ async function pickMode(pi: ExtensionAPI, ctx: ExtensionContext, yolo: { active:
|
|
|
84
84
|
MODES.map((m) => m.label),
|
|
85
85
|
{ timeout: 120_000 },
|
|
86
86
|
);
|
|
87
|
-
if (picked === undefined) return;
|
|
87
|
+
if (picked === undefined) return;
|
|
88
88
|
const next = MODES.find((m) => m.label === picked)?.value ?? current;
|
|
89
89
|
if (next === current) {
|
|
90
|
+
if (next === "yolo" && !yolo.active) {
|
|
91
|
+
yolo.active = true;
|
|
92
|
+
emitYolo(pi.events, true);
|
|
93
|
+
audit({ layer: 0, event: "permission-mode", mode: "yolo", cwd: ctx.cwd, note: "runtime-sync" });
|
|
94
|
+
renderSandboxChip(ctx);
|
|
95
|
+
ctx.ui.notify("⚠️ YOLO — all security layers disabled for this session.", "warning");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
90
98
|
ctx.ui.notify(`Permission mode is already ${modeLabel(next)}.`, "info");
|
|
91
99
|
return;
|
|
92
100
|
}
|
|
@@ -98,8 +106,6 @@ async function pickMode(pi: ExtensionAPI, ctx: ExtensionContext, yolo: { active:
|
|
|
98
106
|
return;
|
|
99
107
|
}
|
|
100
108
|
yolo.active = next === "yolo";
|
|
101
|
-
// Layers off/on in place, without a restart. `--yolo` keeps its own flag
|
|
102
|
-
// value; this only flips the runtime state.
|
|
103
109
|
emitYolo(pi.events, yolo.active);
|
|
104
110
|
audit({ layer: 0, event: "permission-mode", mode: next, cwd: ctx.cwd });
|
|
105
111
|
renderSandboxChip(ctx);
|
|
@@ -109,8 +115,6 @@ async function pickMode(pi: ExtensionAPI, ctx: ExtensionContext, yolo: { active:
|
|
|
109
115
|
|
|
110
116
|
export default function (pi: ExtensionAPI) {
|
|
111
117
|
const yolo = { active: false };
|
|
112
|
-
// `sandbox.ts` owns the CLI flags and broadcasts the startup state, so the
|
|
113
|
-
// menu shows the truth even when `--yolo` was passed for this run only.
|
|
114
118
|
onYolo(pi.events, (enabled) => {
|
|
115
119
|
yolo.active = enabled;
|
|
116
120
|
});
|
package/extensions/sandbox.ts
CHANGED
|
@@ -30,21 +30,17 @@
|
|
|
30
30
|
|
|
31
31
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
32
32
|
import { createBashTool } from "@earendil-works/pi-coding-agent";
|
|
33
|
-
import { createSandboxedBashOps } from "../src/l1-sandbox/bash-ops";
|
|
33
|
+
import { clearFilesystemSessionGrants, createSandboxedBashOps, filesystemSessionGrantSummary } from "../src/l1-sandbox/bash-ops";
|
|
34
34
|
import { applyNetworkGrant, initSandbox, persistLayer1Override, reloadSandbox, resetSandbox, writeNetworkOverride } from "../src/l1-sandbox/manager";
|
|
35
35
|
import { clearNetworkSessionGrants, type NetworkAskCtx, type NetworkAskDeps } from "../src/l1-sandbox/network-ask";
|
|
36
|
-
import { loadConfig, setPiDeclinedTrust } from "../src/l1-sandbox/config";
|
|
36
|
+
import { loadConfig, projectTrusted, setPiDeclinedTrust } from "../src/l1-sandbox/config";
|
|
37
37
|
import { audit } from "../src/shared/audit";
|
|
38
38
|
import { onDomainGrant } from "../src/shared/network-grants";
|
|
39
39
|
import { emitYolo, onYolo, registerYoloFlags, yoloFromFlags, YOLO_STATUS } from "../src/shared/yolo";
|
|
40
40
|
|
|
41
41
|
export default function (pi: ExtensionAPI) {
|
|
42
|
-
// pi scopes flags per extension (ADR-020); registering here makes
|
|
43
|
-
// `yoloFromFlags(pi)` below read the real CLI value.
|
|
44
42
|
registerYoloFlags(pi);
|
|
45
43
|
|
|
46
|
-
// A domain remembered from the Layer 2 guard prompt is written to
|
|
47
|
-
// sandbox.json by the guard; apply it to the running proxy here (ADR-023).
|
|
48
44
|
onDomainGrant(pi.events, applyNetworkGrant);
|
|
49
45
|
|
|
50
46
|
const localCwd = process.cwd();
|
|
@@ -68,10 +64,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
68
64
|
persist: (host, scope) => writeNetworkOverride(cwd, host, scope),
|
|
69
65
|
applyLive: applyNetworkGrant,
|
|
70
66
|
audit,
|
|
71
|
-
|
|
72
|
-
// recent bash command. Without these, the values captured at initSandbox()
|
|
73
|
-
// would be stale.
|
|
74
|
-
get projectTrusted() { return latestCtx?.isProjectTrusted?.(); },
|
|
67
|
+
get projectTrusted() { return projectTrusted(cwd); },
|
|
75
68
|
get command() { return latestBashCommand; },
|
|
76
69
|
});
|
|
77
70
|
|
|
@@ -119,17 +112,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
119
112
|
}
|
|
120
113
|
};
|
|
121
114
|
|
|
122
|
-
// Runtime YOLO from `/permission-mode` (ADR-020): stop or restart the
|
|
123
|
-
// sandbox in place, without a session restart.
|
|
124
115
|
onYolo(pi.events, (enabled) => {
|
|
125
|
-
// The startup broadcast originates here; ignore our own echo.
|
|
126
116
|
if (enabled === runtimeYolo) return;
|
|
127
117
|
runtimeYolo = enabled;
|
|
128
118
|
const ctx = latestCtx;
|
|
129
119
|
if (!ctx) return;
|
|
130
120
|
if (!enabled) {
|
|
131
|
-
// Gate bash until the sandbox is back up, so turning YOLO off cannot
|
|
132
|
-
// leave a window where a command runs unfenced (ADR-020 follow-up).
|
|
133
121
|
sandboxStarting = startSandbox(ctx).finally(() => {
|
|
134
122
|
sandboxStarting = null;
|
|
135
123
|
});
|
|
@@ -162,7 +150,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
162
150
|
return { operations: createSandboxedBashOps({ ctx: activeCtx, onAlways: persistAndReload, onAlwaysRead: persistAndReloadRead }) };
|
|
163
151
|
});
|
|
164
152
|
|
|
165
|
-
// Capture the latest bash command so the network ask body can show it.
|
|
166
153
|
pi.on("tool_call", (event, _ctx) => {
|
|
167
154
|
if (event.toolName === "bash") {
|
|
168
155
|
const input = event.input as { command?: string };
|
|
@@ -174,10 +161,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
174
161
|
latestCtx = ctx;
|
|
175
162
|
activeCtx = /* SAFETY: pi's runtime ctx carries cwd/hasUI/ui; the local type only names the fields we use. */ ctx as unknown as typeof activeCtx;
|
|
176
163
|
clearNetworkSessionGrants();
|
|
164
|
+
clearFilesystemSessionGrants();
|
|
177
165
|
setPiDeclinedTrust((ctx as { isProjectTrusted?: () => boolean }).isProjectTrusted?.() === false);
|
|
178
166
|
runtimeYolo = yoloFromFlags(pi) || loadConfig(ctx.cwd).mode === "yolo";
|
|
179
|
-
// Broadcast before any await: guard.ts consumes this on the same bus to
|
|
180
|
-
// learn YOLO is on (ADR-020).
|
|
181
167
|
emitYolo(pi.events, runtimeYolo);
|
|
182
168
|
|
|
183
169
|
if (runtimeYolo) {
|
|
@@ -232,6 +218,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
232
218
|
` Allow Write: ${config.filesystem?.allowWrite?.join(", ") || "(none)"}`,
|
|
233
219
|
` Deny Write: ${config.filesystem?.denyWrite?.join(", ") || "(none)"}`,
|
|
234
220
|
` Outside project reads: ${config.filesystem?.outsideProject?.read ?? "allow"}`,
|
|
221
|
+
` Session grants: ${filesystemSessionGrantSummary() || "(none)"}`,
|
|
235
222
|
];
|
|
236
223
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
237
224
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-permission-system",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.8.0",
|
|
4
|
+
"description": "Cloud auto mode for Pi & security for Pi. A zero-trust permission system for Pi to secure Pi coding agent workflows with tool guard, sandbox, and PyPI security.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Navin Kumar",
|
|
7
7
|
"keywords": [
|
|
@@ -9,18 +9,40 @@
|
|
|
9
9
|
"pi",
|
|
10
10
|
"pi-coding-agent",
|
|
11
11
|
"pi-extension",
|
|
12
|
+
"pi-permission",
|
|
13
|
+
"pi-permissions",
|
|
14
|
+
"pi-permission-system",
|
|
15
|
+
"pi-security",
|
|
16
|
+
"cloud-auto-mode-for-pi",
|
|
17
|
+
"security-for-pi",
|
|
18
|
+
"permission-system-for-pi",
|
|
19
|
+
"secure-pi",
|
|
20
|
+
"cloud-auto-mode",
|
|
21
|
+
"pi-cloud-auto-mode",
|
|
22
|
+
"auto-permission-system",
|
|
23
|
+
"auto-secure",
|
|
24
|
+
"secure",
|
|
25
|
+
"secure-it",
|
|
26
|
+
"pypi",
|
|
27
|
+
"pypi-security",
|
|
28
|
+
"pypi-secure-it",
|
|
29
|
+
"pypi-permission-system",
|
|
30
|
+
"cloud",
|
|
31
|
+
"cloud-security",
|
|
32
|
+
"auto-mode",
|
|
33
|
+
"permission",
|
|
34
|
+
"permissions",
|
|
35
|
+
"permission-system",
|
|
12
36
|
"security",
|
|
13
37
|
"sandbox",
|
|
14
|
-
"permission-system",
|
|
15
38
|
"tool-guard",
|
|
16
|
-
"allowlist",
|
|
17
|
-
"access-control",
|
|
18
39
|
"secret-detection",
|
|
19
40
|
"audit-log",
|
|
20
41
|
"bubblewrap",
|
|
21
42
|
"sandbox-exec",
|
|
22
43
|
"ai-agent",
|
|
23
|
-
"ai-coding-agent"
|
|
44
|
+
"ai-coding-agent",
|
|
45
|
+
"zero-trust"
|
|
24
46
|
],
|
|
25
47
|
"license": "MIT",
|
|
26
48
|
"repository": {
|
|
@@ -67,6 +89,7 @@
|
|
|
67
89
|
"skills/",
|
|
68
90
|
"sandbox.example.json",
|
|
69
91
|
"sandbox.default.json",
|
|
92
|
+
"sandbox.cloud.json",
|
|
70
93
|
"banner.png",
|
|
71
94
|
"README.md",
|
|
72
95
|
"CHANGELOG.md",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"mode": "advanced-secure",
|
|
4
|
+
"network": {
|
|
5
|
+
"strictAllowlist": true,
|
|
6
|
+
"allowedDomains": [
|
|
7
|
+
"registry.npmjs.org",
|
|
8
|
+
"registry.yarnpkg.com",
|
|
9
|
+
"npmjs.org",
|
|
10
|
+
"*.npmjs.org",
|
|
11
|
+
"pypi.org",
|
|
12
|
+
"*.pypi.org",
|
|
13
|
+
"files.pythonhosted.org",
|
|
14
|
+
"github.com",
|
|
15
|
+
"*.github.com",
|
|
16
|
+
"api.github.com",
|
|
17
|
+
"raw.githubusercontent.com",
|
|
18
|
+
"objects.githubusercontent.com",
|
|
19
|
+
"codeload.github.com",
|
|
20
|
+
"crates.io",
|
|
21
|
+
"proxy.golang.org",
|
|
22
|
+
"repo.maven.apache.org",
|
|
23
|
+
"rubygems.org"
|
|
24
|
+
],
|
|
25
|
+
"deniedDomains": []
|
|
26
|
+
},
|
|
27
|
+
"filesystem": {
|
|
28
|
+
"outsideProject": {
|
|
29
|
+
"read": "deny"
|
|
30
|
+
},
|
|
31
|
+
"allowWrite": [
|
|
32
|
+
".",
|
|
33
|
+
"/tmp"
|
|
34
|
+
],
|
|
35
|
+
"denyRead": [
|
|
36
|
+
"~/.ssh",
|
|
37
|
+
"~/.aws",
|
|
38
|
+
"~/.azure",
|
|
39
|
+
"~/.config/gcloud",
|
|
40
|
+
"~/.gnupg",
|
|
41
|
+
"~/.pi/agent/auth.json"
|
|
42
|
+
],
|
|
43
|
+
"denyWrite": [
|
|
44
|
+
".env",
|
|
45
|
+
".env.*",
|
|
46
|
+
"*.pem",
|
|
47
|
+
"*.key",
|
|
48
|
+
".pypirc",
|
|
49
|
+
".npmrc"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"subagent": {
|
|
53
|
+
"network": "deny"
|
|
54
|
+
},
|
|
55
|
+
"commands": {
|
|
56
|
+
"ask": [
|
|
57
|
+
"printenv",
|
|
58
|
+
"env",
|
|
59
|
+
"/proc/*/environ"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
package/sandbox.example.json
CHANGED
|
@@ -119,6 +119,16 @@
|
|
|
119
119
|
"network": "allow"
|
|
120
120
|
},
|
|
121
121
|
|
|
122
|
+
"_comment_mcp": "Advanced Secure only. Tune the MCP tool-call gate (ADR-032). allowTools = exact tool names that never ask; allowPrefixes = name prefixes that never ask (e.g. 'mcp__ticktick__get_'); askTools = tool names that always ask; trustAnnotations = a server's readOnlyHint allows the call without asking; allowSimpleUpdates = narrow field updates skip the ask (default true); askThreshold = risk score at which calls ask (default 30, lower = stricter). Detected destructive calls always ask, unless an allowTools/allowPrefixes entry covers them (an explicit override). Unclassified names with a real description start at 25; a tool with no description counts as opaque and still asks. An untrusted project file may only tighten: it may add askTools, lower askThreshold, and turn allowSimpleUpdates/trustAnnotations off; allow lists need /security trust.",
|
|
123
|
+
"mcp": {
|
|
124
|
+
"allowTools": [],
|
|
125
|
+
"allowPrefixes": [],
|
|
126
|
+
"askTools": [],
|
|
127
|
+
"trustAnnotations": false,
|
|
128
|
+
"allowSimpleUpdates": true,
|
|
129
|
+
"askThreshold": 30
|
|
130
|
+
},
|
|
131
|
+
|
|
122
132
|
"_comment_overrides": "Additive overrides written by ask-tier prompts. You can also edit by hand. These are merged on top of the main policy.",
|
|
123
133
|
"overrides": {
|
|
124
134
|
"allowRead": [],
|