auto-permission-system 3.12.0 → 3.15.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/extensions/guard.ts +2 -2
- package/extensions/permission-mode.ts +28 -23
- package/package.json +1 -1
- package/sandbox.default.json +1 -0
- package/skills/auto-permission-system/SKILL.md +10 -1
- package/src/core/policy/patterns.ts +0 -4
- package/src/l1-sandbox/bash-ops.ts +22 -52
- package/src/l1-sandbox/config.ts +1 -0
- package/src/l1-sandbox/manager.ts +2 -1
- package/src/ui/ask.ts +55 -5
package/extensions/guard.ts
CHANGED
|
@@ -34,8 +34,8 @@ import { onYolo } from "../src/shared/yolo";
|
|
|
34
34
|
const detectionGrants = new Set<string>();
|
|
35
35
|
|
|
36
36
|
function detectionBlock(action: "read" | "tool", why: string): string {
|
|
37
|
-
if (action === "read") return `Read blocked by policy: ${why}. Nothing was read — ask the user.`;
|
|
38
|
-
return `Tool call blocked by policy: ${why}. Nothing was run — ask the user.`;
|
|
37
|
+
if (action === "read") return `Read blocked by policy: ${why}. Nothing was read — ask the user to adjust the permission mode (/permission-mode) if needed. Do not attempt workarounds or inspect plugin files.`;
|
|
38
|
+
return `Tool call blocked by policy: ${why}. Nothing was run — ask the user to adjust the permission mode (/permission-mode) if needed. Do not attempt workarounds or inspect plugin files.`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** Maps each in-flight tool call to the program id we'll show in the
|
|
@@ -19,37 +19,32 @@ import { emitYolo, onYolo, YOLO_STATUS } from "../src/shared/yolo";
|
|
|
19
19
|
|
|
20
20
|
const GLOBAL_POLICY = `${getAgentDir()}/extensions/sandbox.json`;
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const MODE_DEFS: Array<{
|
|
23
|
+
value: PermissionMode;
|
|
24
|
+
icon: string;
|
|
25
|
+
name: string;
|
|
26
|
+
desc: string;
|
|
27
|
+
}> = [
|
|
23
28
|
{
|
|
24
|
-
label: "Advanced Secure (recommended) — detect secrets in reads, MCP calls, and output",
|
|
25
29
|
value: "advanced-secure",
|
|
30
|
+
icon: "🧠",
|
|
31
|
+
name: "Advanced Secure",
|
|
32
|
+
desc: "Blocks sensitive data leaks from file reads, MCP tools & command outputs",
|
|
26
33
|
},
|
|
27
34
|
{
|
|
28
|
-
label: "Default — rules only",
|
|
29
35
|
value: "default",
|
|
36
|
+
icon: "🔒",
|
|
37
|
+
name: "Default",
|
|
38
|
+
desc: "Restricts file paths & network access at the sandbox level",
|
|
30
39
|
},
|
|
31
40
|
{
|
|
32
|
-
label: "YOLO — disable ALL security layers",
|
|
33
41
|
value: "yolo",
|
|
42
|
+
icon: "⚠️",
|
|
43
|
+
name: "YOLO",
|
|
44
|
+
desc: "Turns off all file sandboxing and leak protections",
|
|
34
45
|
},
|
|
35
46
|
];
|
|
36
47
|
|
|
37
|
-
const EXPLANATION = [
|
|
38
|
-
"Advanced Secure (recommended)",
|
|
39
|
-
" Adds secret/credential detection on top of the rules:",
|
|
40
|
-
" • File reads — scans contents for keys, tokens, and passwords",
|
|
41
|
-
" • MCP calls — flags risky tool calls before they run",
|
|
42
|
-
" • Tool/command/file output — redacts secrets before they reach the model",
|
|
43
|
-
"",
|
|
44
|
-
"Default",
|
|
45
|
-
" Rule-based only: sandbox.json paths, domains, and commands.",
|
|
46
|
-
" No file contents are inspected.",
|
|
47
|
-
"",
|
|
48
|
-
"YOLO",
|
|
49
|
-
" Turns every layer off. Saved like the other modes, so new conversations",
|
|
50
|
-
" start with it too until you pick Default or Advanced Secure again.",
|
|
51
|
-
].join("\n");
|
|
52
|
-
|
|
53
48
|
/**
|
|
54
49
|
* The single sandbox footer chip: `Sandbox: <icon> N domains, M paths`.
|
|
55
50
|
* `🧠` marks advanced-secure, `🔒` default; YOLO replaces it with the warning.
|
|
@@ -79,13 +74,23 @@ function modeNotice(mode: PermissionMode): { text: string; level: "info" | "warn
|
|
|
79
74
|
|
|
80
75
|
async function pickMode(pi: ExtensionAPI, ctx: ExtensionContext, yolo: { active: boolean }): Promise<void> {
|
|
81
76
|
const current: PermissionMode = yolo.active ? "yolo" : normalizeMode(loadPolicy(ctx.cwd).mode);
|
|
77
|
+
|
|
78
|
+
const options = MODE_DEFS.map((m) => {
|
|
79
|
+
const isActive = m.value === current;
|
|
80
|
+
const nameWithStatus = isActive ? `${m.name} (active)` : m.name;
|
|
81
|
+
const labelCol = nameWithStatus.padEnd(26);
|
|
82
|
+
return `${m.icon} ${labelCol} — ${m.desc}`;
|
|
83
|
+
});
|
|
84
|
+
|
|
82
85
|
const picked = await ctx.ui.select(
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
"Select permission mode:",
|
|
87
|
+
options,
|
|
85
88
|
{ timeout: 120_000 },
|
|
86
89
|
);
|
|
87
90
|
if (picked === undefined) return;
|
|
88
|
-
|
|
91
|
+
|
|
92
|
+
const pickedIndex = options.indexOf(picked);
|
|
93
|
+
const next = pickedIndex >= 0 ? MODE_DEFS[pickedIndex].value : current;
|
|
89
94
|
if (next === current) {
|
|
90
95
|
if (next === "yolo" && !yolo.active) {
|
|
91
96
|
yolo.active = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-permission-system",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.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",
|
package/sandbox.default.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: auto-permission-system
|
|
3
|
-
description: Use this skill when the user asks about auto-permission-system, security for Pi,
|
|
3
|
+
description: Use this skill ONLY when the user explicitly asks about auto-permission-system, security for Pi, configuring security policies, or permission modes. Do NOT invoke this skill automatically when an action or tool is blocked during regular coding tasks.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Security for Pi (Pi Permission System) — Skill
|
|
7
7
|
|
|
8
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
|
+
## Handling Blocked Operations (Agent Behavior Guideline)
|
|
11
|
+
|
|
12
|
+
When a command, file operation, or tool call is blocked by the security policy:
|
|
13
|
+
- **Do not inspect plugin internals**: Do NOT read files inside `auto-permission-system`, do NOT inspect sandbox runtime code, and do NOT attempt to find bypasses or workarounds.
|
|
14
|
+
- **Explain clearly**: Calmly and concisely inform the user what specific action was restricted and why.
|
|
15
|
+
- **Ask to change permission mode**: Ask the user if they wish to adjust the permission mode using `/permission-mode` to allow the action.
|
|
16
|
+
- **Wait for user input**: Do not proceed with speculative workarounds or retry loops until the user responds or updates the mode.
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
## What this extension does
|
|
11
20
|
|
|
12
21
|
**Layer 1 — Bash sandbox (OS-level)**
|
|
@@ -57,13 +57,11 @@ export interface LoopbackEndpointInfo {
|
|
|
57
57
|
export function extractLoopbackEndpoint(output: string, command?: string): LoopbackEndpointInfo | undefined {
|
|
58
58
|
if (!isLoopbackConnectionRefused(output)) return undefined;
|
|
59
59
|
|
|
60
|
-
// Check for Diffusion Studio specific error or command
|
|
61
60
|
const isDiffusion = /diffusion studio is not running/i.test(output) || (command && /\b(?:dapi|diffusion)\b/i.test(command));
|
|
62
61
|
if (isDiffusion) {
|
|
63
62
|
return { host: "127.0.0.1", port: "3274", service: "Diffusion Studio" };
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
// Match "Failed to connect to 127.0.0.1 port 3274" or "localhost port 8080"
|
|
67
65
|
const curlMatch = output.match(/Failed to connect to (127\.0\.0\.1|localhost|::1) port (\d+)/i);
|
|
68
66
|
if (curlMatch) {
|
|
69
67
|
return {
|
|
@@ -73,7 +71,6 @@ export function extractLoopbackEndpoint(output: string, command?: string): Loopb
|
|
|
73
71
|
};
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
// Match "connect ECONNREFUSED 127.0.0.1:3274"
|
|
77
74
|
const nodeMatch = output.match(/(?:ECONNREFUSED|connect to)\s+(127\.0\.0\.1|localhost|::1)(?::| port )(\d+)/i);
|
|
78
75
|
if (nodeMatch) {
|
|
79
76
|
return {
|
|
@@ -83,7 +80,6 @@ export function extractLoopbackEndpoint(output: string, command?: string): Loopb
|
|
|
83
80
|
};
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
// Generic host match
|
|
87
83
|
const hostMatch = output.match(/(127\.0\.0\.1|localhost|::1)/i);
|
|
88
84
|
return { host: hostMatch ? hostMatch[1] : "127.0.0.1" };
|
|
89
85
|
}
|
|
@@ -30,7 +30,7 @@ export interface SandboxedBashOpts {
|
|
|
30
30
|
|
|
31
31
|
/** One denial shape for Layer 1, mirroring denyMessage() in Layer 2. */
|
|
32
32
|
function blockedLine(action: "Read" | "Write", why: string, outcome: "read" | "written" | "run"): string {
|
|
33
|
-
return
|
|
33
|
+
return `🛡️ pi-sandbox: ${action} blocked by policy: ${why}. Nothing was ${outcome}. Assistant guideline: Inform the user and ask if they wish to adjust the permission mode (/permission-mode); do not attempt workarounds or inspect plugin internals.`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/** `Link` body row for a symlinked block path; empty when the spellings agree (ADR-035). */
|
|
@@ -327,32 +327,21 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
|
|
|
327
327
|
}
|
|
328
328
|
readDenied = hardDenied || outsideDenied;
|
|
329
329
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
blockedHint = `\n💡 pi-sandbox: filesystem access blocked.\n`;
|
|
330
|
+
blockedHint = `\n🛡️ Security Policy Notice: filesystem access blocked by sandbox policy.\n`;
|
|
333
331
|
if (offending) {
|
|
334
332
|
let why = "";
|
|
335
|
-
if (writeDenied && !hardDenied) why = " (
|
|
333
|
+
if (writeDenied && !hardDenied) why = " (writes outside allowWrite are read-only)";
|
|
336
334
|
else if (hardDenied) why = ` (${hardWhy})`;
|
|
337
|
-
else if (outsideDenied) why = " (outside the project
|
|
338
|
-
blockedHint += `
|
|
339
|
-
if (spelledOffending && spelledOffending !== offending) blockedHint += ` Link:
|
|
340
|
-
}
|
|
341
|
-
blockedHint += ` This is the pi sandbox (Layer 1), NOT macOS Full Disk Access / TCC.\n`;
|
|
342
|
-
if (configDirHint) {
|
|
343
|
-
blockedHint +=
|
|
344
|
-
` Looks like a tool's own config dir. To allow this tool in the\n` +
|
|
345
|
-
` current project, add a project-local policy:\n` +
|
|
346
|
-
` mkdir -p ${cwd}/.pi && cat > ${cwd}/.pi/sandbox.json <<'JSON'\n` +
|
|
347
|
-
` { "filesystem": { "allowWrite": [".", "${offending?.replace(/^~/, "$HOME") ?? "~/.config/<tool>"}"] } }\n` +
|
|
348
|
-
` JSON\n` +
|
|
349
|
-
` Or run pi with --yolo for one-off elevated access (disables ALL layers).\n`;
|
|
350
|
-
} else {
|
|
351
|
-
blockedHint +=
|
|
352
|
-
` Use $TMPDIR (= ${piTmp}/) for scratch files,\n` +
|
|
353
|
-
` or write inside the project directory (${cwd}).\n`;
|
|
335
|
+
else if (outsideDenied) why = " (outside the project directory)";
|
|
336
|
+
blockedHint += ` Target: ${offending}${why}\n`;
|
|
337
|
+
if (spelledOffending && spelledOffending !== offending) blockedHint += ` Link: ${spelledOffending}\n`;
|
|
354
338
|
}
|
|
355
|
-
blockedHint +=
|
|
339
|
+
blockedHint +=
|
|
340
|
+
` Explanation: The current sandbox policy restricts this operation to protect your system.\n` +
|
|
341
|
+
` Guideline for Assistant:\n` +
|
|
342
|
+
` • Stop and inform the user clearly about what was blocked.\n` +
|
|
343
|
+
` • Ask the user if they wish to adjust the permission mode (/permission-mode) to allow this action.\n` +
|
|
344
|
+
` • Do not inspect security plugin internals or attempt unauthorized bypasses.\n`;
|
|
356
345
|
} else if (code !== 0 && isLoopbackConnectionRefused(outputTail)) {
|
|
357
346
|
const endpointInfo = extractLoopbackEndpoint(outputTail, command);
|
|
358
347
|
const endpointStr = endpointInfo?.port ? `${endpointInfo.host}:${endpointInfo.port}` : (endpointInfo?.host ?? "127.0.0.1");
|
|
@@ -361,42 +350,23 @@ export function createSandboxedBashOps(opts?: SandboxedBashOpts): BashOperations
|
|
|
361
350
|
auditL1({
|
|
362
351
|
subject: endpointStr,
|
|
363
352
|
decision: "loopback-refused",
|
|
364
|
-
reason: "
|
|
353
|
+
reason: "Network namespace isolates host loopback under current policy",
|
|
365
354
|
cwd: opts?.ctx?.cwd,
|
|
366
355
|
note: serviceName ? `service:${serviceName}` : undefined,
|
|
367
356
|
});
|
|
368
357
|
|
|
369
|
-
const notifyMsg = serviceName
|
|
370
|
-
?
|
|
371
|
-
: `pi-sandbox: connection to host ${endpointStr}
|
|
358
|
+
const notifyMsg = serviceName
|
|
359
|
+
? `pi-sandbox: connection to ${serviceName} on ${endpointStr} was restricted by the sandbox.`
|
|
360
|
+
: `pi-sandbox: connection to host ${endpointStr} was restricted by the sandbox.`;
|
|
372
361
|
opts?.ctx?.ui?.notify?.(notifyMsg, "warning");
|
|
373
362
|
|
|
374
|
-
blockedHint = `\n
|
|
363
|
+
blockedHint = `\n🛡️ Security Policy Notice: Local network connection restricted.\n` +
|
|
375
364
|
` Target: ${endpointStr}${serviceName ? ` (${serviceName})` : ""}\n` +
|
|
376
|
-
`
|
|
377
|
-
`
|
|
378
|
-
`
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
blockedHint +=
|
|
382
|
-
` Resolution for Diffusion Studio:\n` +
|
|
383
|
-
` • Diffusion Studio exposes an MCP server on http://127.0.0.1:3274/mcp\n` +
|
|
384
|
-
` • Add it to ~/.pi/agent/mcp.json so Pi connects directly on the host:\n` +
|
|
385
|
-
` {\n` +
|
|
386
|
-
` "mcpServers": {\n` +
|
|
387
|
-
` "diffusion-studio": { "url": "http://127.0.0.1:3274/mcp" }\n` +
|
|
388
|
-
` }\n` +
|
|
389
|
-
` }\n` +
|
|
390
|
-
` • Once added, restart or reload Pi: the agent gets native MCP tools\n` +
|
|
391
|
-
` (context, check, capture, probe) with Layer 2 security, no bash needed.\n` +
|
|
392
|
-
` • Or run pi with --yolo for one-off host access.\n`;
|
|
393
|
-
} else {
|
|
394
|
-
blockedHint +=
|
|
395
|
-
` Resolution:\n` +
|
|
396
|
-
` • If this is an MCP service, configure it in ~/.pi/agent/mcp.json.\n` +
|
|
397
|
-
` • Pi runs on the host and can communicate with local MCP servers directly.\n` +
|
|
398
|
-
` • Or run pi with --yolo if this task requires interacting with host network services.\n`;
|
|
399
|
-
}
|
|
365
|
+
` Explanation: The sandbox policy restricted access to this local host address.\n` +
|
|
366
|
+
` Guideline for Assistant:\n` +
|
|
367
|
+
` • Stop and inform the user clearly about what connection was restricted.\n` +
|
|
368
|
+
` • Ask the user if they wish to adjust the permission mode (/permission-mode) to allow this action.\n` +
|
|
369
|
+
` • Do not inspect security plugin internals or attempt unauthorized bypasses.\n`;
|
|
400
370
|
}
|
|
401
371
|
/**
|
|
402
372
|
* Emit the block explanation only when the command is not re-run with a
|
package/src/l1-sandbox/config.ts
CHANGED
|
@@ -67,10 +67,11 @@ type Layer1FilesystemKind = typeof OverrideKind.AllowWrite | typeof OverrideKind
|
|
|
67
67
|
*/
|
|
68
68
|
function runtimeConfig(cwd: string, config: SandboxConfig): SandboxRuntimeConfig {
|
|
69
69
|
const allowLocalhost = config.allowLocalhost ?? config.network?.allowLocalhost ?? true;
|
|
70
|
+
const allowAllUnixSockets = config.allowAllUnixSockets ?? config.network?.allowAllUnixSockets ?? true;
|
|
70
71
|
const network = config.network
|
|
71
72
|
? {
|
|
72
73
|
...config.network,
|
|
73
|
-
allowAllUnixSockets
|
|
74
|
+
allowAllUnixSockets,
|
|
74
75
|
allowLocalBinding: config.allowLocalBinding ?? config.network.allowLocalBinding ?? allowLocalhost,
|
|
75
76
|
}
|
|
76
77
|
: undefined;
|
package/src/ui/ask.ts
CHANGED
|
@@ -67,9 +67,59 @@ function actionOf(ask: AskKind): AskAction {
|
|
|
67
67
|
return AskAction.Read;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/**
|
|
70
|
+
/** Format the reason into plain, user-friendly language for non-technical users. */
|
|
71
71
|
export function displayWhy(reason: string): string {
|
|
72
|
-
|
|
72
|
+
const raw = reason.replace(/\s*→\s*.*$/, "").trim();
|
|
73
|
+
|
|
74
|
+
if (raw.startsWith("absolute-deny matched")) {
|
|
75
|
+
const pat = raw.match(/"([^"]+)"/)?.[1] ?? "";
|
|
76
|
+
if (pat.includes("ssh")) return `contains private SSH keys (${pat})`;
|
|
77
|
+
if (pat.includes("aws")) return `contains AWS credentials (${pat})`;
|
|
78
|
+
if (pat.includes("gnupg")) return `contains private encryption keys (${pat})`;
|
|
79
|
+
if (pat.includes("pem") || pat.includes("key")) return `contains private security keys (${pat})`;
|
|
80
|
+
if (pat.includes("auth.json")) return "holds login credentials";
|
|
81
|
+
return `contains sensitive credentials (${pat || "credentials"})`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (raw.startsWith("askRead matched")) {
|
|
85
|
+
const pat = raw.match(/"([^"]+)"/)?.[1] ?? "";
|
|
86
|
+
if (pat.includes(".env")) return ".env files usually hold passwords and API keys";
|
|
87
|
+
return `this file usually holds private passwords or API keys (${pat})`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (raw.startsWith("denyRead matched")) {
|
|
91
|
+
const pat = raw.match(/"([^"]+)"/)?.[1] ?? "";
|
|
92
|
+
if (pat.includes(".env")) return ".env files usually hold passwords and API keys";
|
|
93
|
+
return `protected by security rules from being read (${pat})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (raw.startsWith("modelDenyRead matched")) {
|
|
97
|
+
const pat = raw.match(/"([^"]+)"/)?.[1] ?? "";
|
|
98
|
+
return `contains private data hidden from the AI (${pat})`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (raw.startsWith("denyWrite matched")) {
|
|
102
|
+
const pat = raw.match(/"([^"]+)"/)?.[1] ?? "";
|
|
103
|
+
return `protected file that cannot be modified (${pat})`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (raw.includes("not under any allowWrite root") || raw.includes("not under an allowWrite root")) {
|
|
107
|
+
return "saving files outside your project workspace requires permission";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (raw.startsWith("outside the project")) {
|
|
111
|
+
return "reading files outside your project folder requires permission";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (raw.startsWith("domain not in allowlist")) {
|
|
115
|
+
return "this site isn't on your allowed list yet";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (raw === "blocked by the sandbox filesystem policy") {
|
|
119
|
+
return "restricted by your project's security policy";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return raw;
|
|
73
123
|
}
|
|
74
124
|
|
|
75
125
|
/** A domain reason without its `: host` suffix, so it can sit next to `Site:` without repeating it. */
|
|
@@ -79,9 +129,9 @@ export function networkWhy(reason: string): string {
|
|
|
79
129
|
|
|
80
130
|
/** The model-facing denial text: one pattern for every block. */
|
|
81
131
|
export function denyMessage(overrideKind: OverrideKind, reason: string): string {
|
|
82
|
-
if (overrideKind === "allowDomains") return `Network blocked by policy: ${networkWhy(reason)}. Nothing was fetched \u2014 ask the user.`;
|
|
83
|
-
if (overrideKind === "allowWrite") return `Write blocked by policy: ${displayWhy(reason)}. Nothing was written \u2014 ask the user.`;
|
|
84
|
-
return `Read blocked by policy: ${displayWhy(reason)}. Nothing was read \u2014 ask the user.`;
|
|
132
|
+
if (overrideKind === "allowDomains") return `Network blocked by policy: ${networkWhy(reason)}. Nothing was fetched \u2014 ask the user to adjust the permission mode (/permission-mode) if needed. Do not attempt workarounds or inspect plugin files.`;
|
|
133
|
+
if (overrideKind === "allowWrite") return `Write blocked by policy: ${displayWhy(reason)}. Nothing was written \u2014 ask the user to adjust the permission mode (/permission-mode) if needed. Do not attempt workarounds or inspect plugin files.`;
|
|
134
|
+
return `Read blocked by policy: ${displayWhy(reason)}. Nothing was read \u2014 ask the user to adjust the permission mode (/permission-mode) if needed. Do not attempt workarounds or inspect plugin files.`;
|
|
85
135
|
}
|
|
86
136
|
|
|
87
137
|
/**
|