@aliou/pi-guardrails 0.11.2 → 0.12.1

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.
Files changed (95) hide show
  1. package/README.md +72 -167
  2. package/extensions/guardrails/commands/examples/index.ts +520 -0
  3. package/extensions/guardrails/commands/onboarding/config.ts +54 -0
  4. package/{src/commands/onboarding-command.ts → extensions/guardrails/commands/onboarding/index.ts} +5 -31
  5. package/extensions/guardrails/commands/settings/add-rule-wizard.ts +267 -0
  6. package/extensions/guardrails/commands/settings/examples.ts +399 -0
  7. package/extensions/guardrails/commands/settings/index.ts +596 -0
  8. package/extensions/guardrails/commands/settings/path-list-editor.ts +158 -0
  9. package/extensions/guardrails/commands/settings/scope-picker-submenu.ts +69 -0
  10. package/extensions/guardrails/commands/settings/utils.ts +108 -0
  11. package/extensions/guardrails/components/onboarding-choice-step.ts +140 -0
  12. package/extensions/guardrails/components/onboarding-finish-step.ts +50 -0
  13. package/extensions/guardrails/components/onboarding-intro-step.ts +30 -0
  14. package/extensions/guardrails/components/onboarding-types.ts +10 -0
  15. package/extensions/guardrails/components/onboarding-wizard.ts +116 -0
  16. package/{src → extensions/guardrails}/components/pattern-editor.ts +11 -10
  17. package/extensions/guardrails/index.ts +106 -0
  18. package/extensions/guardrails/rules.test.ts +107 -0
  19. package/extensions/guardrails/rules.ts +119 -0
  20. package/extensions/guardrails/targets.test.ts +44 -0
  21. package/extensions/guardrails/targets.ts +66 -0
  22. package/extensions/path-access/grants.test.ts +47 -0
  23. package/extensions/path-access/grants.ts +68 -0
  24. package/extensions/path-access/index.ts +143 -0
  25. package/extensions/path-access/prompt.ts +196 -0
  26. package/extensions/path-access/rules.test.ts +46 -0
  27. package/extensions/path-access/rules.ts +37 -0
  28. package/extensions/path-access/targets.test.ts +40 -0
  29. package/extensions/path-access/targets.ts +19 -0
  30. package/extensions/permission-gate/grants.ts +21 -0
  31. package/extensions/permission-gate/index.ts +122 -0
  32. package/extensions/permission-gate/prompt.ts +222 -0
  33. package/extensions/permission-gate/rules.test.ts +132 -0
  34. package/extensions/permission-gate/rules.ts +72 -0
  35. package/package.json +18 -20
  36. package/schema.json +286 -0
  37. package/src/core/check.test.ts +169 -0
  38. package/src/core/check.ts +38 -0
  39. package/src/{hooks/permission-gate/dangerous-commands.test.ts → core/commands/dangerous.test.ts} +134 -2
  40. package/src/{hooks/permission-gate/dangerous-commands.ts → core/commands/dangerous.ts} +119 -1
  41. package/src/core/commands/index.ts +15 -0
  42. package/src/core/index.ts +13 -0
  43. package/src/{utils/path-access.test.ts → core/paths/access.test.ts} +1 -5
  44. package/src/core/paths/index.ts +14 -0
  45. package/src/core/shell/command-args.test.ts +142 -0
  46. package/src/{utils → core/shell}/command-args.ts +71 -0
  47. package/src/core/shell/index.ts +2 -0
  48. package/src/core/types.ts +55 -0
  49. package/src/shared/config/defaults.ts +118 -0
  50. package/src/shared/config/index.ts +17 -0
  51. package/src/shared/config/loader.ts +64 -0
  52. package/src/shared/config/migration/001-v0-format-upgrade.ts +107 -0
  53. package/src/shared/config/migration/002-strip-toolchain-fields.ts +39 -0
  54. package/src/shared/config/migration/003-strip-command-explainer-fields.ts +42 -0
  55. package/src/shared/config/migration/004-env-files-to-policies.ts +87 -0
  56. package/src/shared/config/migration/005-normalize-allowed-paths.ts +43 -0
  57. package/src/shared/config/migration/006-apply-builtin-defaults.ts +19 -0
  58. package/src/shared/config/migration/007-mark-onboarding-done.ts +25 -0
  59. package/src/shared/config/migration/index.ts +44 -0
  60. package/src/shared/config/migration/version.ts +7 -0
  61. package/src/shared/config/types.ts +141 -0
  62. package/src/shared/events.ts +100 -0
  63. package/src/shared/index.ts +6 -0
  64. package/src/shared/matching.test.ts +86 -0
  65. package/src/{utils → shared}/matching.ts +4 -4
  66. package/src/{utils → shared/paths}/bash-paths.test.ts +32 -2
  67. package/src/{utils → shared/paths}/bash-paths.ts +4 -4
  68. package/src/shared/paths/index.ts +1 -0
  69. package/src/shared/warnings.ts +17 -0
  70. package/docs/defaults.md +0 -140
  71. package/docs/examples.md +0 -170
  72. package/src/commands/onboarding.ts +0 -390
  73. package/src/commands/settings-command.ts +0 -1616
  74. package/src/config.ts +0 -392
  75. package/src/hooks/index.ts +0 -11
  76. package/src/hooks/path-access.ts +0 -395
  77. package/src/hooks/permission-gate/index.test.ts +0 -332
  78. package/src/hooks/permission-gate/index.ts +0 -595
  79. package/src/hooks/policies.ts +0 -322
  80. package/src/index.ts +0 -96
  81. package/src/lib/executor.ts +0 -280
  82. package/src/lib/index.ts +0 -16
  83. package/src/lib/model-resolver.ts +0 -47
  84. package/src/lib/timing.ts +0 -42
  85. package/src/lib/types.ts +0 -115
  86. package/src/utils/command-args.test.ts +0 -83
  87. package/src/utils/events.ts +0 -32
  88. package/src/utils/migration.test.ts +0 -58
  89. package/src/utils/migration.ts +0 -340
  90. package/src/utils/warnings.ts +0 -7
  91. /package/src/{utils/path-access.ts → core/paths/access.ts} +0 -0
  92. /package/src/{utils → core/paths}/path.test.ts +0 -0
  93. /package/src/{utils → core/paths}/path.ts +0 -0
  94. /package/src/{utils/shell-utils.ts → core/shell/ast.ts} +0 -0
  95. /package/src/{utils/glob-expander.ts → shared/glob.ts} +0 -0
package/src/config.ts DELETED
@@ -1,392 +0,0 @@
1
- /**
2
- * Configuration schema for the guardrails extension.
3
- *
4
- * GuardrailsConfig is the user-facing schema (all fields optional).
5
- * ResolvedConfig is the internal schema (all fields required, defaults applied).
6
- */
7
-
8
- /**
9
- * A pattern with explicit matching mode.
10
- * Default: glob for files, substring for commands.
11
- * regex: true means full regex matching.
12
- */
13
- export interface PatternConfig {
14
- pattern: string;
15
- regex?: boolean;
16
- }
17
-
18
- /**
19
- * Permission gate pattern. When regex is false (default), the pattern
20
- * is matched as substring against the raw command string.
21
- * When regex is true, uses full regex against the raw string.
22
- */
23
- export interface DangerousPattern extends PatternConfig {
24
- description: string;
25
- }
26
-
27
- /**
28
- * Protection level for a policy rule.
29
- */
30
- export type Protection = "none" | "readOnly" | "noAccess";
31
-
32
- /**
33
- * A named policy rule. Matches files by patterns and enforces a protection level.
34
- */
35
- export interface PolicyRule {
36
- /** Stable identifier used for deduplication across scopes. */
37
- id: string;
38
- /** Optional display name for settings/UI. */
39
- name?: string;
40
- /** Human-readable description. */
41
- description?: string;
42
- /** File patterns to protect. */
43
- patterns: PatternConfig[];
44
- /** Optional exceptions. */
45
- allowedPatterns?: PatternConfig[];
46
- /** Protection level. */
47
- protection: Protection;
48
- /** Block only when file exists on disk. Default true. */
49
- onlyIfExists?: boolean;
50
- /** Message shown when blocked; supports {file} placeholder. */
51
- blockMessage?: string;
52
- /** Per-rule toggle. Default true. */
53
- enabled?: boolean;
54
- }
55
-
56
- export type PathAccessMode = "allow" | "ask" | "block";
57
-
58
- export interface PathAccessConfig {
59
- mode?: PathAccessMode;
60
- allowedPaths?: string[];
61
- }
62
-
63
- export interface GuardrailsConfig {
64
- version?: string;
65
- enabled?: boolean;
66
- /** Deprecated-defaults bridge: when true, applies built-in policy defaults. */
67
- applyBuiltinDefaults?: boolean;
68
- onboarding?: {
69
- completed?: boolean;
70
- completedAt?: string;
71
- version?: string;
72
- };
73
- features?: {
74
- policies?: boolean;
75
- permissionGate?: boolean;
76
- pathAccess?: boolean;
77
- // Deprecated. Kept only for migration.
78
- protectEnvFiles?: boolean;
79
- };
80
- policies?: {
81
- rules?: PolicyRule[];
82
- };
83
- pathAccess?: PathAccessConfig;
84
- // Deprecated. Kept only for migration.
85
- envFiles?: {
86
- protectedPatterns?: PatternConfig[];
87
- allowedPatterns?: PatternConfig[];
88
- protectedDirectories?: PatternConfig[];
89
- protectedTools?: string[];
90
- onlyBlockIfExists?: boolean;
91
- blockMessage?: string;
92
- };
93
- permissionGate?: {
94
- patterns?: DangerousPattern[];
95
- /** If set, replaces the default patterns entirely. */
96
- customPatterns?: DangerousPattern[];
97
- requireConfirmation?: boolean;
98
- allowedPatterns?: PatternConfig[];
99
- autoDenyPatterns?: PatternConfig[];
100
- explainCommands?: boolean;
101
- explainModel?: string;
102
- explainTimeout?: number;
103
- };
104
- }
105
-
106
- export interface ResolvedConfig {
107
- version: string;
108
- enabled: boolean;
109
- applyBuiltinDefaults: boolean;
110
- features: {
111
- policies: boolean;
112
- permissionGate: boolean;
113
- pathAccess: boolean;
114
- };
115
- policies: {
116
- rules: PolicyRule[];
117
- };
118
- pathAccess: {
119
- mode: PathAccessMode;
120
- allowedPaths: string[];
121
- };
122
- permissionGate: {
123
- patterns: DangerousPattern[];
124
- /** When true, use hardcoded structural matchers for built-in patterns.
125
- * Set to false when customPatterns replaces the defaults. */
126
- useBuiltinMatchers: boolean;
127
- requireConfirmation: boolean;
128
- allowedPatterns: PatternConfig[];
129
- autoDenyPatterns: PatternConfig[];
130
- explainCommands: boolean;
131
- explainModel: string | null;
132
- explainTimeout: number;
133
- };
134
- }
135
-
136
- import { ConfigLoader, type Migration } from "@aliou/pi-utils-settings";
137
- import {
138
- backupConfig,
139
- CURRENT_VERSION,
140
- migrateAllowedPaths,
141
- migrateEnvFilesToPolicies,
142
- migrateV0,
143
- needsAllowedPathsMigration,
144
- needsEnvFilesToPoliciesMigration,
145
- needsMigration,
146
- normalizeAllowedPaths,
147
- } from "./utils/migration";
148
- import { pendingWarnings } from "./utils/warnings";
149
-
150
- /**
151
- * Config fields removed in the toolchain extraction.
152
- * Old configs containing these are auto-cleaned on first load.
153
- */
154
- const REMOVED_FEATURE_KEYS = [
155
- "preventBrew",
156
- "preventPython",
157
- "enforcePackageManager",
158
- ] as const;
159
-
160
- function hasRemovedFields(config: GuardrailsConfig): boolean {
161
- const raw = config as Record<string, unknown>;
162
- const features = raw.features as Record<string, unknown> | undefined;
163
- if (features) {
164
- for (const key of REMOVED_FEATURE_KEYS) {
165
- if (key in features) return true;
166
- }
167
- }
168
- return "packageManager" in raw;
169
- }
170
-
171
- function stripRemovedFields(config: GuardrailsConfig): GuardrailsConfig {
172
- const cleaned = structuredClone(config) as Record<string, unknown>;
173
- const features = cleaned.features as Record<string, unknown> | undefined;
174
- if (features) {
175
- for (const key of REMOVED_FEATURE_KEYS) {
176
- delete features[key];
177
- }
178
- }
179
- delete cleaned.packageManager;
180
- cleaned.version = CURRENT_VERSION;
181
- return cleaned as GuardrailsConfig;
182
- }
183
-
184
- const migrations: Migration<GuardrailsConfig>[] = [
185
- {
186
- name: "v0-format-upgrade",
187
- shouldRun: (config) => needsMigration(config),
188
- run: async (config, filePath) => {
189
- await backupConfig(filePath);
190
- return migrateV0(config);
191
- },
192
- },
193
- {
194
- name: "strip-toolchain-fields",
195
- shouldRun: (config) => hasRemovedFields(config),
196
- run: (config) => {
197
- pendingWarnings.push(
198
- "[guardrails] preventBrew, preventPython, enforcePackageManager, and packageManager " +
199
- "have been removed from guardrails and moved to @aliou/pi-toolchain. " +
200
- "These fields will be stripped from your config.",
201
- );
202
- return stripRemovedFields(config);
203
- },
204
- },
205
- {
206
- name: "envFiles-to-policies",
207
- shouldRun: (config) => needsEnvFilesToPoliciesMigration(config),
208
- run: (config) => migrateEnvFilesToPolicies(config),
209
- },
210
- {
211
- name: "normalize-allowed-paths",
212
- shouldRun: (config) => needsAllowedPathsMigration(config),
213
- run: (config) => migrateAllowedPaths(config),
214
- },
215
- ];
216
-
217
- const DEFAULT_CONFIG: ResolvedConfig = {
218
- version: CURRENT_VERSION,
219
- enabled: true,
220
- applyBuiltinDefaults: true,
221
- features: {
222
- policies: true,
223
- permissionGate: true,
224
- pathAccess: false,
225
- },
226
- pathAccess: {
227
- mode: "ask",
228
- allowedPaths: [],
229
- },
230
- policies: {
231
- rules: [
232
- {
233
- id: "secret-files",
234
- description: "Files containing secrets",
235
- patterns: [
236
- { pattern: ".env" },
237
- { pattern: ".env.local" },
238
- { pattern: ".env.production" },
239
- { pattern: ".env.prod" },
240
- { pattern: ".dev.vars" },
241
- ],
242
- allowedPatterns: [
243
- { pattern: "*.example.env" },
244
- { pattern: "*.sample.env" },
245
- { pattern: "*.test.env" },
246
- { pattern: ".env.example" },
247
- { pattern: ".env.sample" },
248
- { pattern: ".env.test" },
249
- ],
250
- protection: "noAccess",
251
- onlyIfExists: true,
252
- blockMessage:
253
- "Accessing {file} is not allowed. This file contains secrets. " +
254
- "Explain to the user why you want to access this file, and if changes are needed ask the user to make them.",
255
- },
256
- {
257
- id: "home-ssh",
258
- description: "SSH directory and keys",
259
- enabled: false,
260
- patterns: [
261
- { pattern: "~/.ssh/**" },
262
- { pattern: "~/.ssh/*_rsa" },
263
- { pattern: "~/.ssh/*_ed25519" },
264
- { pattern: "~/.ssh/*.pem" },
265
- ],
266
- allowedPatterns: [{ pattern: "~/.ssh/*.pub" }],
267
- protection: "noAccess",
268
- onlyIfExists: true,
269
- blockMessage:
270
- "Accessing {file} is not allowed. This file is part of your SSH configuration and may contain private keys or sensitive host information.",
271
- },
272
- {
273
- id: "home-config",
274
- description: "Sensitive user configuration directories",
275
- enabled: false,
276
- patterns: [
277
- { pattern: "~/.config/gh/**" },
278
- { pattern: "~/.config/gcloud/**" },
279
- { pattern: "~/.config/op/**" },
280
- { pattern: "~/.config/sops/**" },
281
- ],
282
- protection: "noAccess",
283
- onlyIfExists: true,
284
- blockMessage:
285
- "Accessing {file} is not allowed. This file is in a sensitive user configuration directory and may contain credentials or tokens.",
286
- },
287
- {
288
- id: "home-gpg",
289
- description: "GPG keys and configuration",
290
- enabled: false,
291
- patterns: [
292
- { pattern: "~/.gnupg/**" },
293
- { pattern: "~/*.gpg" },
294
- { pattern: "~/.gpg-agent.conf" },
295
- ],
296
- protection: "noAccess",
297
- onlyIfExists: true,
298
- blockMessage:
299
- "Accessing {file} is not allowed. This file is part of your GPG configuration and may contain private keys or trust settings.",
300
- },
301
- ],
302
- },
303
- permissionGate: {
304
- patterns: [
305
- { pattern: "rm -rf", description: "recursive force delete" },
306
- { pattern: "sudo", description: "superuser command" },
307
- { pattern: "dd of=", description: "disk write operation" },
308
- { pattern: "mkfs.", description: "filesystem format" },
309
- {
310
- pattern: "chmod -R 777",
311
- description: "insecure recursive permissions",
312
- },
313
- { pattern: "chown -R", description: "recursive ownership change" },
314
- { pattern: "doas", description: "privileged command execution" },
315
- { pattern: "pkexec", description: "privileged command execution" },
316
- { pattern: "shred", description: "secure file overwrite" },
317
- { pattern: "wipefs", description: "filesystem signature wipe" },
318
- { pattern: "blkdiscard", description: "block device discard" },
319
- { pattern: "fdisk", description: "disk partitioning" },
320
- { pattern: "parted", description: "disk partitioning" },
321
- {
322
- pattern: "docker run --privileged",
323
- description: "container with privileged mode",
324
- },
325
- ],
326
- useBuiltinMatchers: true,
327
- requireConfirmation: true,
328
- allowedPatterns: [],
329
- autoDenyPatterns: [],
330
- explainCommands: false,
331
- explainModel: null,
332
- explainTimeout: 5000,
333
- },
334
- };
335
-
336
- export const configLoader = new ConfigLoader<GuardrailsConfig, ResolvedConfig>(
337
- "guardrails",
338
- DEFAULT_CONFIG,
339
- {
340
- scopes: ["global", "local", "memory"],
341
- migrations,
342
- afterMerge: (resolved, global, local, memory) => {
343
- const ruleMap = new Map<string, PolicyRule>();
344
-
345
- if (resolved.applyBuiltinDefaults) {
346
- for (const rule of DEFAULT_CONFIG.policies.rules) {
347
- ruleMap.set(rule.id, rule);
348
- }
349
- }
350
- if (global?.policies?.rules) {
351
- for (const rule of global.policies.rules) {
352
- ruleMap.set(rule.id, rule);
353
- }
354
- }
355
- if (local?.policies?.rules) {
356
- for (const rule of local.policies.rules) {
357
- ruleMap.set(rule.id, rule);
358
- }
359
- }
360
- if (memory?.policies?.rules) {
361
- for (const rule of memory.policies.rules) {
362
- ruleMap.set(rule.id, rule);
363
- }
364
- }
365
- resolved.policies.rules = [...ruleMap.values()];
366
-
367
- // customPatterns replaces the entire patterns array and disables
368
- // built-in structural matchers (user owns all matching).
369
- // Priority: memory > local > global
370
- const customPatterns =
371
- memory?.permissionGate?.customPatterns ??
372
- local?.permissionGate?.customPatterns ??
373
- global?.permissionGate?.customPatterns;
374
- if (customPatterns) {
375
- resolved.permissionGate.patterns = customPatterns;
376
- resolved.permissionGate.useBuiltinMatchers = false;
377
- }
378
- // Merge allowedPaths across scopes (additive)
379
- const mergedPaths = new Set<string>();
380
- for (const paths of [
381
- global?.pathAccess?.allowedPaths,
382
- local?.pathAccess?.allowedPaths,
383
- memory?.pathAccess?.allowedPaths,
384
- ]) {
385
- for (const p of normalizeAllowedPaths(paths)) mergedPaths.add(p);
386
- }
387
- resolved.pathAccess.allowedPaths = [...mergedPaths];
388
-
389
- return resolved;
390
- },
391
- },
392
- );
@@ -1,11 +0,0 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import type { ResolvedConfig } from "../config";
3
- import { setupPathAccessHook } from "./path-access";
4
- import { setupPermissionGateHook } from "./permission-gate";
5
- import { setupPoliciesHook } from "./policies";
6
-
7
- export function setupGuardrailsHooks(pi: ExtensionAPI, config: ResolvedConfig) {
8
- setupPathAccessHook(pi); // boundary check — runs first
9
- setupPoliciesHook(pi, config); // policy rules — runs second
10
- setupPermissionGateHook(pi, config); // dangerous commands — runs third
11
- }