@aliou/pi-guardrails 0.11.2 → 0.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/README.md +72 -167
- package/extensions/guardrails/commands/examples/index.ts +520 -0
- package/extensions/guardrails/commands/onboarding/config.ts +54 -0
- package/{src/commands/onboarding-command.ts → extensions/guardrails/commands/onboarding/index.ts} +5 -31
- package/extensions/guardrails/commands/settings/add-rule-wizard.ts +267 -0
- package/extensions/guardrails/commands/settings/examples.ts +399 -0
- package/extensions/guardrails/commands/settings/index.ts +596 -0
- package/extensions/guardrails/commands/settings/path-list-editor.ts +158 -0
- package/extensions/guardrails/commands/settings/scope-picker-submenu.ts +69 -0
- package/extensions/guardrails/commands/settings/utils.ts +108 -0
- package/extensions/guardrails/components/onboarding-choice-step.ts +140 -0
- package/extensions/guardrails/components/onboarding-finish-step.ts +50 -0
- package/extensions/guardrails/components/onboarding-intro-step.ts +30 -0
- package/extensions/guardrails/components/onboarding-types.ts +10 -0
- package/extensions/guardrails/components/onboarding-wizard.ts +116 -0
- package/{src → extensions/guardrails}/components/pattern-editor.ts +11 -10
- package/extensions/guardrails/index.ts +106 -0
- package/extensions/guardrails/rules.test.ts +107 -0
- package/extensions/guardrails/rules.ts +119 -0
- package/extensions/guardrails/targets.test.ts +44 -0
- package/extensions/guardrails/targets.ts +66 -0
- package/extensions/path-access/grants.test.ts +47 -0
- package/extensions/path-access/grants.ts +68 -0
- package/extensions/path-access/index.ts +143 -0
- package/extensions/path-access/prompt.ts +196 -0
- package/extensions/path-access/rules.test.ts +46 -0
- package/extensions/path-access/rules.ts +37 -0
- package/extensions/path-access/targets.test.ts +40 -0
- package/extensions/path-access/targets.ts +19 -0
- package/extensions/permission-gate/grants.ts +21 -0
- package/extensions/permission-gate/index.ts +122 -0
- package/extensions/permission-gate/prompt.ts +222 -0
- package/extensions/permission-gate/rules.test.ts +132 -0
- package/extensions/permission-gate/rules.ts +72 -0
- package/package.json +18 -20
- package/schema.json +286 -0
- package/src/core/check.test.ts +169 -0
- package/src/core/check.ts +38 -0
- package/src/{hooks/permission-gate/dangerous-commands.test.ts → core/commands/dangerous.test.ts} +134 -2
- package/src/{hooks/permission-gate/dangerous-commands.ts → core/commands/dangerous.ts} +119 -1
- package/src/core/commands/index.ts +15 -0
- package/src/core/index.ts +13 -0
- package/src/{utils/path-access.test.ts → core/paths/access.test.ts} +1 -5
- package/src/core/paths/index.ts +14 -0
- package/src/{utils → core/shell}/command-args.test.ts +31 -20
- package/src/core/shell/index.ts +2 -0
- package/src/core/types.ts +55 -0
- package/src/shared/config/defaults.ts +118 -0
- package/src/shared/config/index.ts +17 -0
- package/src/shared/config/loader.ts +64 -0
- package/src/shared/config/migration/001-v0-format-upgrade.ts +107 -0
- package/src/shared/config/migration/002-strip-toolchain-fields.ts +39 -0
- package/src/shared/config/migration/003-strip-command-explainer-fields.ts +42 -0
- package/src/shared/config/migration/004-env-files-to-policies.ts +87 -0
- package/src/shared/config/migration/005-normalize-allowed-paths.ts +43 -0
- package/src/shared/config/migration/006-apply-builtin-defaults.ts +19 -0
- package/src/shared/config/migration/007-mark-onboarding-done.ts +25 -0
- package/src/shared/config/migration/index.ts +44 -0
- package/src/shared/config/migration/version.ts +7 -0
- package/src/shared/config/types.ts +141 -0
- package/src/shared/events.ts +100 -0
- package/src/shared/index.ts +6 -0
- package/src/shared/matching.test.ts +86 -0
- package/src/{utils → shared}/matching.ts +4 -4
- package/src/{utils → shared/paths}/bash-paths.test.ts +11 -2
- package/src/{utils → shared/paths}/bash-paths.ts +4 -4
- package/src/shared/paths/index.ts +1 -0
- package/src/shared/warnings.ts +17 -0
- package/docs/defaults.md +0 -140
- package/docs/examples.md +0 -170
- package/src/commands/onboarding.ts +0 -390
- package/src/commands/settings-command.ts +0 -1616
- package/src/config.ts +0 -392
- package/src/hooks/index.ts +0 -11
- package/src/hooks/path-access.ts +0 -395
- package/src/hooks/permission-gate/index.test.ts +0 -332
- package/src/hooks/permission-gate/index.ts +0 -595
- package/src/hooks/policies.ts +0 -322
- package/src/index.ts +0 -96
- package/src/lib/executor.ts +0 -280
- package/src/lib/index.ts +0 -16
- package/src/lib/model-resolver.ts +0 -47
- package/src/lib/timing.ts +0 -42
- package/src/lib/types.ts +0 -115
- package/src/utils/events.ts +0 -32
- package/src/utils/migration.test.ts +0 -58
- package/src/utils/migration.ts +0 -340
- package/src/utils/warnings.ts +0 -7
- /package/src/{utils/path-access.ts → core/paths/access.ts} +0 -0
- /package/src/{utils → core/paths}/path.test.ts +0 -0
- /package/src/{utils → core/paths}/path.ts +0 -0
- /package/src/{utils/shell-utils.ts → core/shell/ast.ts} +0 -0
- /package/src/{utils → core/shell}/command-args.ts +0 -0
- /package/src/{utils/glob-expander.ts → shared/glob.ts} +0 -0
package/src/hooks/path-access.ts
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
|
-
import {
|
|
5
|
-
Container,
|
|
6
|
-
Key,
|
|
7
|
-
matchesKey,
|
|
8
|
-
Spacer,
|
|
9
|
-
Text,
|
|
10
|
-
visibleWidth,
|
|
11
|
-
} from "@mariozechner/pi-tui";
|
|
12
|
-
import { configLoader } from "../config";
|
|
13
|
-
import { extractBashPathCandidates } from "../utils/bash-paths";
|
|
14
|
-
import { emitBlocked } from "../utils/events";
|
|
15
|
-
import { normalizeAllowedPaths } from "../utils/migration";
|
|
16
|
-
import {
|
|
17
|
-
normalizeForDisplay,
|
|
18
|
-
resolveFromCwd,
|
|
19
|
-
toStorageForm,
|
|
20
|
-
} from "../utils/path";
|
|
21
|
-
import { checkPathAccess, type PathAccessState } from "../utils/path-access";
|
|
22
|
-
|
|
23
|
-
// Grant result type from the UI prompt
|
|
24
|
-
type PromptResult =
|
|
25
|
-
| "allow-file-once"
|
|
26
|
-
| "allow-dir-once"
|
|
27
|
-
| "allow-file-session"
|
|
28
|
-
| "allow-dir-session"
|
|
29
|
-
| "allow-file-always"
|
|
30
|
-
| "allow-dir-always"
|
|
31
|
-
| "deny";
|
|
32
|
-
|
|
33
|
-
// Pending grant to be persisted after all targets pass
|
|
34
|
-
interface PendingGrant {
|
|
35
|
-
storagePath: string; // in storage form (~/..., trailing / for dirs)
|
|
36
|
-
scope: "memory" | "local";
|
|
37
|
-
absolutePath: string; // for in-loop matching
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Resolve allowedPaths from config to absolute paths, preserving trailing-slash convention.
|
|
42
|
-
*/
|
|
43
|
-
function resolveAllowedPaths(allowedPaths: string[], cwd: string): string[] {
|
|
44
|
-
return allowedPaths.map((p) => {
|
|
45
|
-
const isDir = p.endsWith("/");
|
|
46
|
-
const resolved = resolveFromCwd(isDir ? p.slice(0, -1) : p, cwd);
|
|
47
|
-
return isDir ? `${resolved}/` : resolved;
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Check if a grant path would be too broad (/ or home directory).
|
|
53
|
-
*/
|
|
54
|
-
function isGrantTooBroad(absPath: string): boolean {
|
|
55
|
-
const home = homedir();
|
|
56
|
-
const normalized = absPath.replace(/[\\/]+$/, "");
|
|
57
|
-
return normalized === "/" || normalized === home;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Collapse home directory to ~ for display.
|
|
62
|
-
*/
|
|
63
|
-
function displayCwd(cwd: string): string {
|
|
64
|
-
const home = homedir();
|
|
65
|
-
if (cwd === home) return "~";
|
|
66
|
-
if (cwd.startsWith(`${home}/`) || cwd.startsWith(`${home}\\`)) {
|
|
67
|
-
return `~${cwd.slice(home.length)}`;
|
|
68
|
-
}
|
|
69
|
-
return cwd;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface PromptOption {
|
|
73
|
-
label: string;
|
|
74
|
-
result: PromptResult;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const FILE_OPTIONS: PromptOption[] = [
|
|
78
|
-
{ label: "Allow once", result: "allow-file-once" },
|
|
79
|
-
{ label: "Allow file this session", result: "allow-file-session" },
|
|
80
|
-
{ label: "Allow file always", result: "allow-file-always" },
|
|
81
|
-
{ label: "Allow directory this session", result: "allow-dir-session" },
|
|
82
|
-
{ label: "Allow directory always", result: "allow-dir-always" },
|
|
83
|
-
{ label: "Deny", result: "deny" },
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const DIR_OPTIONS: PromptOption[] = [
|
|
87
|
-
{ label: "Allow once", result: "allow-dir-once" },
|
|
88
|
-
{ label: "Allow directory this session", result: "allow-dir-session" },
|
|
89
|
-
{ label: "Allow directory always", result: "allow-dir-always" },
|
|
90
|
-
{ label: "Deny", result: "deny" },
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Build the confirmation UI component.
|
|
95
|
-
* For directory-oriented tools (ls, find): only directory grant options.
|
|
96
|
-
* For file tools and bash: both file and directory options.
|
|
97
|
-
* Options rendered as highlighted tabs (selected = accent bg, unselected = dim),
|
|
98
|
-
* navigable with ←/→/Tab/Shift+Tab.
|
|
99
|
-
*/
|
|
100
|
-
function createPromptComponent(
|
|
101
|
-
toolName: string,
|
|
102
|
-
displayPath: string,
|
|
103
|
-
displayDir: string,
|
|
104
|
-
cwd: string,
|
|
105
|
-
showFileOptions: boolean,
|
|
106
|
-
) {
|
|
107
|
-
return (
|
|
108
|
-
tui: { terminal: { columns: number }; requestRender(): void },
|
|
109
|
-
theme: {
|
|
110
|
-
fg(color: string, text: string): string;
|
|
111
|
-
bg(color: string, text: string): string;
|
|
112
|
-
bold(text: string): string;
|
|
113
|
-
},
|
|
114
|
-
_kb: unknown,
|
|
115
|
-
done: (result: PromptResult) => void,
|
|
116
|
-
) => {
|
|
117
|
-
const options = showFileOptions ? FILE_OPTIONS : DIR_OPTIONS;
|
|
118
|
-
let selectedIndex = 0;
|
|
119
|
-
|
|
120
|
-
const container = new Container();
|
|
121
|
-
const border = (s: string) => theme.fg("warning", s);
|
|
122
|
-
const cwdDisplay = displayCwd(cwd);
|
|
123
|
-
|
|
124
|
-
container.addChild(
|
|
125
|
-
new Text(
|
|
126
|
-
theme.fg("warning", theme.bold("Outside Workspace Access")),
|
|
127
|
-
1,
|
|
128
|
-
0,
|
|
129
|
-
),
|
|
130
|
-
);
|
|
131
|
-
container.addChild(new Spacer(1));
|
|
132
|
-
container.addChild(
|
|
133
|
-
new Text(
|
|
134
|
-
theme.fg(
|
|
135
|
-
"text",
|
|
136
|
-
`\`${toolName}\` targets a path outside the working directory.`,
|
|
137
|
-
),
|
|
138
|
-
1,
|
|
139
|
-
0,
|
|
140
|
-
),
|
|
141
|
-
);
|
|
142
|
-
container.addChild(new Spacer(1));
|
|
143
|
-
container.addChild(
|
|
144
|
-
new Text(theme.fg("dim", ` Cwd: ${cwdDisplay}`), 1, 0),
|
|
145
|
-
);
|
|
146
|
-
container.addChild(
|
|
147
|
-
new Text(theme.fg("dim", ` Path: ${displayPath}`), 1, 0),
|
|
148
|
-
);
|
|
149
|
-
container.addChild(
|
|
150
|
-
new Text(theme.fg("dim", ` Dir: ${displayDir}`), 1, 0),
|
|
151
|
-
);
|
|
152
|
-
container.addChild(new Spacer(1));
|
|
153
|
-
|
|
154
|
-
// Dynamically rendered option lines
|
|
155
|
-
const optionLines: Text[] = options.map(() => new Text("", 1, 0));
|
|
156
|
-
for (const line of optionLines) {
|
|
157
|
-
container.addChild(line);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
container.addChild(new Spacer(1));
|
|
161
|
-
container.addChild(
|
|
162
|
-
new Text(
|
|
163
|
-
theme.fg("dim", "↑/↓/Tab select · Enter select · Esc deny"),
|
|
164
|
-
1,
|
|
165
|
-
0,
|
|
166
|
-
),
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const renderOptions = () => {
|
|
170
|
-
for (let i = 0; i < options.length; i++) {
|
|
171
|
-
const label = options[i].label;
|
|
172
|
-
if (i === selectedIndex) {
|
|
173
|
-
optionLines[i].setText(
|
|
174
|
-
theme.bg("selectedBg", theme.fg("accent", ` ${label} `)),
|
|
175
|
-
);
|
|
176
|
-
} else {
|
|
177
|
-
optionLines[i].setText(theme.fg("dim", ` ${label} `));
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
renderOptions();
|
|
183
|
-
|
|
184
|
-
const moveSelection = (direction: number) => {
|
|
185
|
-
selectedIndex =
|
|
186
|
-
(selectedIndex + direction + options.length) % options.length;
|
|
187
|
-
renderOptions();
|
|
188
|
-
tui.requestRender();
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
render: (width: number) => {
|
|
193
|
-
const innerWidth = Math.max(1, width - 2);
|
|
194
|
-
const contentWidth = Math.max(1, width - 4);
|
|
195
|
-
const raw = container.render(contentWidth);
|
|
196
|
-
const top = border(`╭${"─".repeat(innerWidth)}╮`);
|
|
197
|
-
const bottom = border(`╰${"─".repeat(innerWidth)}╯`);
|
|
198
|
-
const left = border("│");
|
|
199
|
-
const right = border("│");
|
|
200
|
-
const lines = raw.map((line) => {
|
|
201
|
-
const visible = visibleWidth(line);
|
|
202
|
-
const pad = Math.max(0, contentWidth - visible);
|
|
203
|
-
return `${left} ${line}${" ".repeat(pad)} ${right}`;
|
|
204
|
-
});
|
|
205
|
-
return [top, ...lines, bottom];
|
|
206
|
-
},
|
|
207
|
-
invalidate: () => container.invalidate(),
|
|
208
|
-
handleInput: (data: string) => {
|
|
209
|
-
if (
|
|
210
|
-
matchesKey(data, Key.up) ||
|
|
211
|
-
data === "k" ||
|
|
212
|
-
matchesKey(data, Key.shift("tab"))
|
|
213
|
-
) {
|
|
214
|
-
moveSelection(-1);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (
|
|
218
|
-
matchesKey(data, Key.down) ||
|
|
219
|
-
data === "j" ||
|
|
220
|
-
matchesKey(data, Key.tab)
|
|
221
|
-
) {
|
|
222
|
-
moveSelection(1);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (matchesKey(data, Key.enter)) {
|
|
226
|
-
done(options[selectedIndex].result);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (matchesKey(data, Key.escape)) {
|
|
230
|
-
done("deny");
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Persist a grant to the given config scope.
|
|
239
|
-
* Re-reads raw config before saving to avoid clobbering concurrent changes.
|
|
240
|
-
*/
|
|
241
|
-
async function persistGrant(
|
|
242
|
-
storagePath: string,
|
|
243
|
-
scope: "memory" | "local",
|
|
244
|
-
): Promise<void> {
|
|
245
|
-
const raw = (configLoader.getRawConfig(scope) ?? {}) as Record<
|
|
246
|
-
string,
|
|
247
|
-
unknown
|
|
248
|
-
>;
|
|
249
|
-
const pa = (raw.pathAccess ?? {}) as Record<string, unknown>;
|
|
250
|
-
const existing = normalizeAllowedPaths(pa.allowedPaths);
|
|
251
|
-
|
|
252
|
-
if (existing.includes(storagePath)) return;
|
|
253
|
-
|
|
254
|
-
await configLoader.save(scope, {
|
|
255
|
-
...raw,
|
|
256
|
-
pathAccess: { ...pa, allowedPaths: [...existing, storagePath] },
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export function setupPathAccessHook(pi: ExtensionAPI): void {
|
|
261
|
-
pi.on("tool_call", async (event, ctx) => {
|
|
262
|
-
// Read config live on every invocation
|
|
263
|
-
const config = configLoader.getConfig();
|
|
264
|
-
if (!config.features.pathAccess || config.pathAccess.mode === "allow")
|
|
265
|
-
return;
|
|
266
|
-
|
|
267
|
-
const toolName = event.toolName;
|
|
268
|
-
let absolutePaths: string[] = [];
|
|
269
|
-
|
|
270
|
-
const input = event.input as Record<string, unknown>;
|
|
271
|
-
|
|
272
|
-
if (["read", "write", "edit", "grep", "find", "ls"].includes(toolName)) {
|
|
273
|
-
const raw = String(input.file_path ?? input.path ?? "").trim();
|
|
274
|
-
if (raw) absolutePaths = [resolveFromCwd(raw, ctx.cwd)];
|
|
275
|
-
} else if (toolName === "bash") {
|
|
276
|
-
const command = String(input.command ?? "");
|
|
277
|
-
absolutePaths = await extractBashPathCandidates(command, ctx.cwd);
|
|
278
|
-
} else {
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (absolutePaths.length === 0) return;
|
|
283
|
-
|
|
284
|
-
// Deduplicate paths
|
|
285
|
-
absolutePaths = [...new Set(absolutePaths)];
|
|
286
|
-
|
|
287
|
-
const pendingGrants: PendingGrant[] = [];
|
|
288
|
-
const isDirectoryTool = toolName === "ls" || toolName === "find";
|
|
289
|
-
|
|
290
|
-
for (const absPath of absolutePaths) {
|
|
291
|
-
// Build state with live config + pending grants from this loop
|
|
292
|
-
const resolvedAllowed = resolveAllowedPaths(
|
|
293
|
-
config.pathAccess.allowedPaths,
|
|
294
|
-
ctx.cwd,
|
|
295
|
-
);
|
|
296
|
-
const pendingAllowedPaths = pendingGrants.map((g) => {
|
|
297
|
-
const isDir = g.storagePath.endsWith("/");
|
|
298
|
-
return isDir ? `${g.absolutePath}/` : g.absolutePath;
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const state: PathAccessState = {
|
|
302
|
-
cwd: ctx.cwd,
|
|
303
|
-
mode: config.pathAccess.mode,
|
|
304
|
-
allowedPaths: [...resolvedAllowed, ...pendingAllowedPaths],
|
|
305
|
-
hasUI: ctx.hasUI,
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const displayPath = normalizeForDisplay(absPath, ctx.cwd);
|
|
309
|
-
const decision = checkPathAccess(absPath, displayPath, state);
|
|
310
|
-
|
|
311
|
-
if (decision.kind === "allow") continue;
|
|
312
|
-
|
|
313
|
-
if (decision.kind === "deny") {
|
|
314
|
-
emitBlocked(pi, {
|
|
315
|
-
feature: "pathAccess",
|
|
316
|
-
toolName,
|
|
317
|
-
input: event.input,
|
|
318
|
-
reason: decision.reason,
|
|
319
|
-
});
|
|
320
|
-
return { block: true, reason: decision.reason };
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// decision.kind === "ask"
|
|
324
|
-
const parentDir = dirname(absPath);
|
|
325
|
-
const displayDir = normalizeForDisplay(parentDir, ctx.cwd);
|
|
326
|
-
const showFileOptions = !isDirectoryTool;
|
|
327
|
-
|
|
328
|
-
const result = await ctx.ui.custom<PromptResult>(
|
|
329
|
-
createPromptComponent(
|
|
330
|
-
toolName,
|
|
331
|
-
displayPath,
|
|
332
|
-
displayDir,
|
|
333
|
-
ctx.cwd,
|
|
334
|
-
showFileOptions,
|
|
335
|
-
),
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
// Handle "once" grants: just continue, do NOT add to pending
|
|
339
|
-
if (result === "allow-file-once" || result === "allow-dir-once") {
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Handle session/always grants
|
|
344
|
-
if (result === "allow-file-session" || result === "allow-file-always") {
|
|
345
|
-
const scope = result === "allow-file-session" ? "memory" : "local";
|
|
346
|
-
const storage = toStorageForm(absPath, false);
|
|
347
|
-
pendingGrants.push({
|
|
348
|
-
storagePath: storage,
|
|
349
|
-
scope,
|
|
350
|
-
absolutePath: absPath,
|
|
351
|
-
});
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (result === "allow-dir-session" || result === "allow-dir-always") {
|
|
356
|
-
const scope = result === "allow-dir-session" ? "memory" : "local";
|
|
357
|
-
const dirPath = isDirectoryTool ? absPath : parentDir;
|
|
358
|
-
|
|
359
|
-
if (isGrantTooBroad(dirPath)) {
|
|
360
|
-
ctx.ui.notify(
|
|
361
|
-
`Cannot grant access to ${normalizeForDisplay(dirPath, ctx.cwd)}/ — too broad. Treating as allow once.`,
|
|
362
|
-
"warning",
|
|
363
|
-
);
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const storage = toStorageForm(dirPath, true);
|
|
368
|
-
pendingGrants.push({
|
|
369
|
-
storagePath: storage,
|
|
370
|
-
scope,
|
|
371
|
-
absolutePath: dirPath,
|
|
372
|
-
});
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// result === "deny"
|
|
377
|
-
const reason = "User denied access outside working directory";
|
|
378
|
-
emitBlocked(pi, {
|
|
379
|
-
feature: "pathAccess",
|
|
380
|
-
toolName,
|
|
381
|
-
input: event.input,
|
|
382
|
-
reason,
|
|
383
|
-
userDenied: true,
|
|
384
|
-
});
|
|
385
|
-
return { block: true, reason };
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Persist grants only after ALL targets passed
|
|
389
|
-
for (const grant of pendingGrants) {
|
|
390
|
-
await persistGrant(grant.storagePath, grant.scope);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return;
|
|
394
|
-
});
|
|
395
|
-
}
|