@aliou/pi-guardrails 0.9.5 → 0.11.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 +51 -3
- package/docs/defaults.md +140 -0
- package/docs/examples.md +170 -0
- package/package.json +7 -3
- package/src/commands/onboarding-command.ts +76 -0
- package/src/commands/onboarding.ts +390 -0
- package/src/commands/settings-command.ts +158 -3
- package/src/config.ts +102 -3
- package/src/hooks/index.ts +4 -2
- package/src/hooks/path-access.ts +396 -0
- package/src/hooks/permission-gate/dangerous-commands.test.ts +336 -0
- package/src/hooks/permission-gate/dangerous-commands.ts +345 -0
- package/src/hooks/permission-gate/index.test.ts +332 -0
- package/src/hooks/{permission-gate.ts → permission-gate/index.ts} +275 -159
- package/src/hooks/policies.ts +20 -4
- package/src/index.ts +62 -3
- package/src/utils/bash-paths.test.ts +91 -0
- package/src/utils/bash-paths.ts +96 -0
- package/src/utils/events.ts +1 -1
- package/src/utils/migration.ts +55 -1
- package/src/utils/path-access.test.ts +154 -0
- package/src/utils/path-access.ts +62 -0
- package/src/utils/path.test.ts +177 -0
- package/src/utils/path.ts +74 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSettingsTheme,
|
|
3
|
+
type SettingsTheme,
|
|
4
|
+
Wizard,
|
|
5
|
+
} from "@aliou/pi-utils-settings";
|
|
6
|
+
import { getMarkdownTheme, type Theme } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import type { Component } from "@mariozechner/pi-tui";
|
|
8
|
+
import { Box, Key, Markdown, matchesKey, Text } from "@mariozechner/pi-tui";
|
|
9
|
+
import type { GuardrailsConfig } from "../config";
|
|
10
|
+
import { CURRENT_VERSION } from "../utils/migration";
|
|
11
|
+
|
|
12
|
+
interface OnboardingState {
|
|
13
|
+
applyBuiltinDefaults: boolean | null;
|
|
14
|
+
pathAccessEnabled: boolean | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface OnboardingResult {
|
|
18
|
+
completed: boolean;
|
|
19
|
+
applyBuiltinDefaults: boolean | null;
|
|
20
|
+
pathAccessEnabled: boolean | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class IntroStep implements Component {
|
|
24
|
+
private readonly introText = new Text("", 2, 0);
|
|
25
|
+
|
|
26
|
+
constructor(private readonly onNext: () => void) {}
|
|
27
|
+
|
|
28
|
+
invalidate() {
|
|
29
|
+
this.introText.invalidate();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
render(width: number): string[] {
|
|
33
|
+
this.introText.setText(
|
|
34
|
+
"Guardrails helps prevent accidental exposure of secrets and risky actions.\n\nIt gives you two protections:\n- Policies: file access rules (`noAccess` or `readOnly`)\n- Permission gate: confirmation before dangerous commands run\n\nYou are choosing the starting defaults now. You can change them later in `/guardrails:settings`.",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return [
|
|
38
|
+
" Welcome to Guardrails",
|
|
39
|
+
"",
|
|
40
|
+
...this.introText.render(Math.max(1, width)),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handleInput(data: string): void {
|
|
45
|
+
if (matchesKey(data, Key.enter)) {
|
|
46
|
+
this.onNext();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class DefaultsChoiceStep implements Component {
|
|
52
|
+
private selectedIndex = 0;
|
|
53
|
+
private readonly settingsTheme: SettingsTheme;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
private readonly theme: Theme,
|
|
57
|
+
private readonly state: OnboardingState,
|
|
58
|
+
private readonly onSelect: () => void,
|
|
59
|
+
) {
|
|
60
|
+
this.settingsTheme = getSettingsTheme(theme);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
invalidate() {}
|
|
64
|
+
|
|
65
|
+
render(width: number): string[] {
|
|
66
|
+
const options = ["Recommended defaults", "Minimal setup"];
|
|
67
|
+
const explanations = [
|
|
68
|
+
[
|
|
69
|
+
"Use built-ins for common safety needs:",
|
|
70
|
+
"",
|
|
71
|
+
"- Protect secret files like `.env`, `.env.local`, `.env.production`, and `.dev.vars`",
|
|
72
|
+
"- Keep safe exceptions like `.env.example` and `*.sample.env`",
|
|
73
|
+
"- Require confirmation before running dangerous commands like `rm -rf`, `sudo`, and `dd of=`",
|
|
74
|
+
].join("\n"),
|
|
75
|
+
[
|
|
76
|
+
"Start with no built-in file policy defaults.",
|
|
77
|
+
"",
|
|
78
|
+
"- Configure your own policies in `/guardrails:settings`",
|
|
79
|
+
"- Browse policy and command examples in `/guardrails:settings`",
|
|
80
|
+
].join("\n"),
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const lines: string[] = [
|
|
84
|
+
" Pick how much built-in protection to start with.",
|
|
85
|
+
"",
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < options.length; i++) {
|
|
89
|
+
const option = options[i];
|
|
90
|
+
if (!option) continue;
|
|
91
|
+
const selected = i === this.selectedIndex;
|
|
92
|
+
const prefix = selected ? this.settingsTheme.cursor : " ";
|
|
93
|
+
const label = this.settingsTheme.value(` ${option}`, selected);
|
|
94
|
+
lines.push(`${prefix}${label}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lines.push("");
|
|
98
|
+
|
|
99
|
+
const explanationBox = new Box(1, 0, (s: string) => s);
|
|
100
|
+
explanationBox.addChild(
|
|
101
|
+
new Markdown(
|
|
102
|
+
explanations[this.selectedIndex] ?? "",
|
|
103
|
+
0,
|
|
104
|
+
0,
|
|
105
|
+
getMarkdownTheme(),
|
|
106
|
+
{
|
|
107
|
+
color: (s: string) => this.theme.fg("text", s),
|
|
108
|
+
},
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
lines.push(...explanationBox.render(Math.max(1, width)));
|
|
113
|
+
|
|
114
|
+
return lines;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
handleInput(data: string): void {
|
|
118
|
+
if (matchesKey(data, Key.up) || data === "k") {
|
|
119
|
+
this.selectedIndex = this.selectedIndex === 0 ? 1 : 0;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (matchesKey(data, Key.down) || data === "j") {
|
|
123
|
+
this.selectedIndex = this.selectedIndex === 1 ? 0 : 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (matchesKey(data, Key.enter)) {
|
|
128
|
+
this.state.applyBuiltinDefaults = this.selectedIndex === 0;
|
|
129
|
+
this.onSelect();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class PathAccessStep implements Component {
|
|
135
|
+
private selectedIndex = 0;
|
|
136
|
+
private readonly settingsTheme: SettingsTheme;
|
|
137
|
+
|
|
138
|
+
constructor(
|
|
139
|
+
private readonly theme: Theme,
|
|
140
|
+
private readonly state: OnboardingState,
|
|
141
|
+
private readonly onSelect: () => void,
|
|
142
|
+
) {
|
|
143
|
+
this.settingsTheme = getSettingsTheme(theme);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
invalidate() {}
|
|
147
|
+
|
|
148
|
+
render(width: number): string[] {
|
|
149
|
+
const options = ["Ask before accessing outside files", "No restrictions"];
|
|
150
|
+
const explanations = [
|
|
151
|
+
[
|
|
152
|
+
"When enabled, guardrails will prompt you before the agent accesses files outside the current working directory.",
|
|
153
|
+
"",
|
|
154
|
+
"- You can grant access per-file or per-directory",
|
|
155
|
+
"- Grants can be session-only or permanent",
|
|
156
|
+
"- In non-interactive mode, outside access is blocked",
|
|
157
|
+
].join("\n"),
|
|
158
|
+
[
|
|
159
|
+
"The agent can access any path on your system without prompting.",
|
|
160
|
+
"",
|
|
161
|
+
"- You can enable path access later in `/guardrails:settings`",
|
|
162
|
+
].join("\n"),
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const lines: string[] = [
|
|
166
|
+
" Restrict access to your project directory?",
|
|
167
|
+
"",
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < options.length; i++) {
|
|
171
|
+
const option = options[i];
|
|
172
|
+
if (!option) continue;
|
|
173
|
+
const selected = i === this.selectedIndex;
|
|
174
|
+
const prefix = selected ? this.settingsTheme.cursor : " ";
|
|
175
|
+
const label = this.settingsTheme.value(` ${option}`, selected);
|
|
176
|
+
lines.push(`${prefix}${label}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
lines.push("");
|
|
180
|
+
|
|
181
|
+
const explanationBox = new Box(1, 0, (s: string) => s);
|
|
182
|
+
explanationBox.addChild(
|
|
183
|
+
new Markdown(
|
|
184
|
+
explanations[this.selectedIndex] ?? "",
|
|
185
|
+
0,
|
|
186
|
+
0,
|
|
187
|
+
getMarkdownTheme(),
|
|
188
|
+
{
|
|
189
|
+
color: (s: string) => this.theme.fg("text", s),
|
|
190
|
+
},
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
lines.push(...explanationBox.render(Math.max(1, width)));
|
|
195
|
+
|
|
196
|
+
return lines;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
handleInput(data: string): void {
|
|
200
|
+
if (matchesKey(data, Key.up) || data === "k") {
|
|
201
|
+
this.selectedIndex = this.selectedIndex === 0 ? 1 : 0;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (matchesKey(data, Key.down) || data === "j") {
|
|
205
|
+
this.selectedIndex = this.selectedIndex === 1 ? 0 : 1;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (matchesKey(data, Key.enter)) {
|
|
210
|
+
this.state.pathAccessEnabled = this.selectedIndex === 0;
|
|
211
|
+
this.onSelect();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
class FinishStep implements Component {
|
|
217
|
+
private readonly recapMarkdown = new Markdown("", 2, 0, getMarkdownTheme());
|
|
218
|
+
|
|
219
|
+
constructor(
|
|
220
|
+
private readonly state: OnboardingState,
|
|
221
|
+
private readonly onFinish: () => void,
|
|
222
|
+
) {}
|
|
223
|
+
|
|
224
|
+
invalidate() {
|
|
225
|
+
this.recapMarkdown.invalidate();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
render(width: number): string[] {
|
|
229
|
+
const defaultsPart =
|
|
230
|
+
this.state.applyBuiltinDefaults === true
|
|
231
|
+
? [
|
|
232
|
+
"You selected **Recommended defaults**.",
|
|
233
|
+
"",
|
|
234
|
+
"Guardrails will start with built-in protection, including:",
|
|
235
|
+
"- secret files like `.env`, `.env.local`, `.env.production`, `.dev.vars`",
|
|
236
|
+
"- safe exceptions like `.env.example` and `*.sample.env`",
|
|
237
|
+
"- confirmation before running dangerous commands like `rm -rf`, `sudo`, `dd of=`",
|
|
238
|
+
].join("\n")
|
|
239
|
+
: [
|
|
240
|
+
"You selected **Minimal setup**.",
|
|
241
|
+
"",
|
|
242
|
+
"No built-in file policy defaults will be applied.",
|
|
243
|
+
"",
|
|
244
|
+
"You can configure policies later with `/guardrails:settings`.",
|
|
245
|
+
].join("\n");
|
|
246
|
+
|
|
247
|
+
const pathAccessPart = this.state.pathAccessEnabled
|
|
248
|
+
? "\n\n**Path access**: enabled (ask mode). The agent will prompt before accessing files outside the working directory."
|
|
249
|
+
: "\n\n**Path access**: disabled. No path restrictions.";
|
|
250
|
+
|
|
251
|
+
const content = defaultsPart + pathAccessPart;
|
|
252
|
+
|
|
253
|
+
this.recapMarkdown.setText(content);
|
|
254
|
+
return [...this.recapMarkdown.render(Math.max(1, width)), ""];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
handleInput(data: string): void {
|
|
258
|
+
if (matchesKey(data, Key.enter)) {
|
|
259
|
+
this.onFinish();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createOnboardingWizard(
|
|
265
|
+
theme: Theme,
|
|
266
|
+
done: (result: OnboardingResult) => void,
|
|
267
|
+
): Component {
|
|
268
|
+
const state: OnboardingState = {
|
|
269
|
+
applyBuiltinDefaults: null,
|
|
270
|
+
pathAccessEnabled: null,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
let markWelcomeComplete: (() => void) | null = null;
|
|
274
|
+
let settled = false;
|
|
275
|
+
|
|
276
|
+
const finalize = (result: OnboardingResult) => {
|
|
277
|
+
if (settled) return;
|
|
278
|
+
settled = true;
|
|
279
|
+
done(result);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const wizard = new Wizard({
|
|
283
|
+
title: "Guardrails onboarding",
|
|
284
|
+
theme,
|
|
285
|
+
steps: [
|
|
286
|
+
{
|
|
287
|
+
label: "Welcome",
|
|
288
|
+
build: (ctx) => {
|
|
289
|
+
markWelcomeComplete = ctx.markComplete;
|
|
290
|
+
return new IntroStep(() => {
|
|
291
|
+
ctx.markComplete();
|
|
292
|
+
ctx.goNext();
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
label: "Defaults",
|
|
298
|
+
build: (ctx) =>
|
|
299
|
+
new DefaultsChoiceStep(theme, state, () => {
|
|
300
|
+
ctx.markComplete();
|
|
301
|
+
ctx.goNext();
|
|
302
|
+
}),
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
label: "Path access",
|
|
306
|
+
build: (ctx) =>
|
|
307
|
+
new PathAccessStep(theme, state, () => {
|
|
308
|
+
ctx.markComplete();
|
|
309
|
+
ctx.goNext();
|
|
310
|
+
}),
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
label: "Recap",
|
|
314
|
+
build: (ctx) =>
|
|
315
|
+
new FinishStep(state, () => {
|
|
316
|
+
if (state.applyBuiltinDefaults === null) return;
|
|
317
|
+
ctx.markComplete();
|
|
318
|
+
finalize({
|
|
319
|
+
completed: true,
|
|
320
|
+
applyBuiltinDefaults: state.applyBuiltinDefaults,
|
|
321
|
+
pathAccessEnabled: state.pathAccessEnabled,
|
|
322
|
+
});
|
|
323
|
+
}),
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
onComplete: () => {
|
|
327
|
+
if (state.applyBuiltinDefaults === null) {
|
|
328
|
+
finalize({
|
|
329
|
+
completed: false,
|
|
330
|
+
applyBuiltinDefaults: null,
|
|
331
|
+
pathAccessEnabled: null,
|
|
332
|
+
});
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
finalize({
|
|
336
|
+
completed: true,
|
|
337
|
+
applyBuiltinDefaults: state.applyBuiltinDefaults,
|
|
338
|
+
pathAccessEnabled: state.pathAccessEnabled,
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
onCancel: () =>
|
|
342
|
+
finalize({
|
|
343
|
+
completed: false,
|
|
344
|
+
applyBuiltinDefaults: null,
|
|
345
|
+
pathAccessEnabled: null,
|
|
346
|
+
}),
|
|
347
|
+
hintSuffix: "Enter select/continue",
|
|
348
|
+
minContentHeight: 12,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
render: (width) => wizard.render(width),
|
|
353
|
+
invalidate: () => wizard.invalidate(),
|
|
354
|
+
handleInput: (data: string) => {
|
|
355
|
+
if (
|
|
356
|
+
matchesKey(data, Key.tab) &&
|
|
357
|
+
wizard.getActiveIndex() === 0 &&
|
|
358
|
+
markWelcomeComplete
|
|
359
|
+
) {
|
|
360
|
+
markWelcomeComplete();
|
|
361
|
+
}
|
|
362
|
+
wizard.handleInput(data);
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function buildOnboardedConfig(
|
|
368
|
+
applyBuiltinDefaults: boolean,
|
|
369
|
+
pathAccessEnabled?: boolean | null,
|
|
370
|
+
): GuardrailsConfig {
|
|
371
|
+
const config: GuardrailsConfig = {
|
|
372
|
+
version: CURRENT_VERSION,
|
|
373
|
+
applyBuiltinDefaults,
|
|
374
|
+
onboarding: {
|
|
375
|
+
completed: true,
|
|
376
|
+
completedAt: new Date().toISOString(),
|
|
377
|
+
version: CURRENT_VERSION,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
if (pathAccessEnabled) {
|
|
381
|
+
config.features = { ...config.features, pathAccess: true };
|
|
382
|
+
config.pathAccess = { mode: "ask" };
|
|
383
|
+
}
|
|
384
|
+
return config;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function isOnboardingPending(config: GuardrailsConfig | null): boolean {
|
|
388
|
+
if (!config) return true;
|
|
389
|
+
return config.onboarding?.completed !== true;
|
|
390
|
+
}
|
|
@@ -40,6 +40,10 @@ const FEATURE_UI: Record<FeatureKey, { label: string; description: string }> = {
|
|
|
40
40
|
description:
|
|
41
41
|
"Prompt for confirmation on dangerous commands (rm -rf, sudo, etc.)",
|
|
42
42
|
},
|
|
43
|
+
pathAccess: {
|
|
44
|
+
label: "Path access",
|
|
45
|
+
description: "Restrict tool access to the current working directory",
|
|
46
|
+
},
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
const POLICY_EXAMPLES: Array<{
|
|
@@ -258,6 +262,118 @@ const COMMAND_EXAMPLES: Array<{
|
|
|
258
262
|
description: "Require confirmation for table drops",
|
|
259
263
|
pattern: { pattern: "DROP TABLE", description: "SQL table drop" },
|
|
260
264
|
},
|
|
265
|
+
{
|
|
266
|
+
label: "dbt run",
|
|
267
|
+
description: "Require confirmation for dbt model runs",
|
|
268
|
+
pattern: {
|
|
269
|
+
pattern: "dbt run",
|
|
270
|
+
description: "dbt model execution",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
label: "dbt seed",
|
|
275
|
+
description: "Require confirmation for dbt seed data loading",
|
|
276
|
+
pattern: {
|
|
277
|
+
pattern: "dbt seed",
|
|
278
|
+
description: "dbt seed data loading",
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
label: "aws s3 rm",
|
|
283
|
+
description: "Require confirmation for AWS S3 deletions",
|
|
284
|
+
pattern: {
|
|
285
|
+
pattern: "aws s3 rm",
|
|
286
|
+
description: "AWS S3 object deletion",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
label: "aws iam",
|
|
291
|
+
description: "Require confirmation for AWS IAM changes",
|
|
292
|
+
pattern: {
|
|
293
|
+
pattern: "aws iam",
|
|
294
|
+
description: "AWS IAM permission changes",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
label: "aws ec2 terminate",
|
|
299
|
+
description: "Require confirmation for EC2 instance termination",
|
|
300
|
+
pattern: {
|
|
301
|
+
pattern: "aws ec2 terminate-instances",
|
|
302
|
+
description: "AWS EC2 instance termination",
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
label: "kubectl apply",
|
|
307
|
+
description: "Require confirmation for k8s resource application",
|
|
308
|
+
pattern: {
|
|
309
|
+
pattern: "kubectl apply",
|
|
310
|
+
description: "Kubernetes resource application",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
label: "kubectl scale",
|
|
315
|
+
description: "Require confirmation for k8s scaling operations",
|
|
316
|
+
pattern: {
|
|
317
|
+
pattern: "kubectl scale",
|
|
318
|
+
description: "Kubernetes scaling operation",
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
label: "docker rm",
|
|
323
|
+
description: "Require confirmation for Docker container removal",
|
|
324
|
+
pattern: {
|
|
325
|
+
pattern: "docker rm",
|
|
326
|
+
description: "Docker container removal",
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
label: "docker rmi",
|
|
331
|
+
description: "Require confirmation for Docker image removal",
|
|
332
|
+
pattern: {
|
|
333
|
+
pattern: "docker rmi",
|
|
334
|
+
description: "Docker image removal",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
label: "docker compose down",
|
|
339
|
+
description: "Require confirmation for Docker Compose teardown",
|
|
340
|
+
pattern: {
|
|
341
|
+
pattern: "docker compose down",
|
|
342
|
+
description: "Docker Compose service teardown",
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
label: "terraform import",
|
|
347
|
+
description: "Require confirmation for Terraform resource import",
|
|
348
|
+
pattern: {
|
|
349
|
+
pattern: "terraform import",
|
|
350
|
+
description: "Terraform resource import",
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
label: "gcloud compute delete",
|
|
355
|
+
description: "Require confirmation for GCP compute instance deletion",
|
|
356
|
+
pattern: {
|
|
357
|
+
pattern: "gcloud compute instances delete",
|
|
358
|
+
description: "GCP compute instance deletion",
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
label: "gcloud iam",
|
|
363
|
+
description: "Require confirmation for GCP IAM changes",
|
|
364
|
+
pattern: {
|
|
365
|
+
pattern: "gcloud iam",
|
|
366
|
+
description: "GCP IAM permission changes",
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
label: "gcloud sql delete",
|
|
371
|
+
description: "Require confirmation for GCP SQL instance deletion",
|
|
372
|
+
pattern: {
|
|
373
|
+
pattern: "gcloud sql instances delete",
|
|
374
|
+
description: "GCP Cloud SQL instance deletion",
|
|
375
|
+
},
|
|
376
|
+
},
|
|
261
377
|
];
|
|
262
378
|
|
|
263
379
|
function toKebabCase(input: string): string {
|
|
@@ -845,7 +961,7 @@ export function registerGuardrailsSettings(pi: ExtensionAPI): void {
|
|
|
845
961
|
buildSections: (
|
|
846
962
|
tabConfig: GuardrailsConfig | null,
|
|
847
963
|
_resolved: ResolvedConfig,
|
|
848
|
-
{ setDraft, theme },
|
|
964
|
+
{ setDraft, theme, scope },
|
|
849
965
|
): SettingsSection[] => {
|
|
850
966
|
const settingsTheme = theme;
|
|
851
967
|
let scopedConfig = structuredClone(tabConfig ?? {}) as GuardrailsConfig;
|
|
@@ -1005,9 +1121,11 @@ export function registerGuardrailsSettings(pi: ExtensionAPI): void {
|
|
|
1005
1121
|
return scopedConfig.permissionGate?.explainTimeout ?? null;
|
|
1006
1122
|
}
|
|
1007
1123
|
|
|
1008
|
-
const featureItems = (
|
|
1124
|
+
const featureItems: SettingItem[] = (
|
|
1125
|
+
Object.keys(FEATURE_UI) as FeatureKey[]
|
|
1126
|
+
)
|
|
1009
1127
|
.filter((key) => key !== "policies")
|
|
1010
|
-
.map((key) => {
|
|
1128
|
+
.map((key): SettingItem => {
|
|
1011
1129
|
const scopedValue = scopedConfig.features?.[key];
|
|
1012
1130
|
return {
|
|
1013
1131
|
id: `features.${key}`,
|
|
@@ -1023,6 +1141,18 @@ export function registerGuardrailsSettings(pi: ExtensionAPI): void {
|
|
|
1023
1141
|
};
|
|
1024
1142
|
});
|
|
1025
1143
|
|
|
1144
|
+
if (scope === "global") {
|
|
1145
|
+
featureItems.push({
|
|
1146
|
+
id: "onboarding.run",
|
|
1147
|
+
label: "Onboarding status",
|
|
1148
|
+
description: "Use /guardrails:onboarding to run onboarding",
|
|
1149
|
+
currentValue:
|
|
1150
|
+
scopedConfig.onboarding?.completed === true
|
|
1151
|
+
? "completed"
|
|
1152
|
+
: "pending",
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1026
1156
|
const policyRules = getPolicyRules();
|
|
1027
1157
|
|
|
1028
1158
|
const openPolicyEditor = (
|
|
@@ -1084,6 +1214,31 @@ export function registerGuardrailsSettings(pi: ExtensionAPI): void {
|
|
|
1084
1214
|
label: `Policies (${policyRules.length})`,
|
|
1085
1215
|
items: policyItems,
|
|
1086
1216
|
},
|
|
1217
|
+
{
|
|
1218
|
+
label: "Path Access",
|
|
1219
|
+
items: [
|
|
1220
|
+
{
|
|
1221
|
+
id: "pathAccess.mode",
|
|
1222
|
+
label: "Mode",
|
|
1223
|
+
description:
|
|
1224
|
+
"allow: no restrictions, ask: prompt for outside paths, block: deny all outside paths",
|
|
1225
|
+
currentValue: scopedConfig.pathAccess?.mode ?? "(inherited)",
|
|
1226
|
+
values: ["allow", "ask", "block"],
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
id: "pathAccess.allowedPaths",
|
|
1230
|
+
label: "Allowed paths",
|
|
1231
|
+
description:
|
|
1232
|
+
"Paths always allowed (trailing / for directories). Supports ~/",
|
|
1233
|
+
currentValue: count("pathAccess.allowedPaths"),
|
|
1234
|
+
submenu: patternConfigSubmenu(
|
|
1235
|
+
"pathAccess.allowedPaths",
|
|
1236
|
+
"Allowed Paths",
|
|
1237
|
+
"file",
|
|
1238
|
+
),
|
|
1239
|
+
},
|
|
1240
|
+
],
|
|
1241
|
+
},
|
|
1087
1242
|
{
|
|
1088
1243
|
label: "Permission Gate",
|
|
1089
1244
|
items: [
|