@aliou/pi-guardrails 0.7.7 → 0.9.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 +98 -150
- package/package.json +13 -3
- package/src/commands/settings-command.ts +916 -118
- package/src/components/pattern-editor.ts +9 -7
- package/src/config.ts +110 -45
- package/src/hooks/index.ts +2 -2
- package/src/hooks/permission-gate.ts +149 -12
- package/src/hooks/policies.ts +297 -0
- package/src/index.ts +1 -1
- package/src/lib/executor.ts +280 -0
- package/src/lib/index.ts +16 -0
- package/src/lib/model-resolver.ts +47 -0
- package/src/lib/timing.ts +42 -0
- package/src/lib/types.ts +115 -0
- package/src/utils/events.ts +1 -1
- package/src/utils/migration.ts +106 -1
- package/src/hooks/protect-env-files.ts +0 -220
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Guardrails
|
|
2
2
|
|
|
3
|
-
Security hooks to
|
|
3
|
+
Security hooks for Pi to reduce accidental destructive actions and secret-file access.
|
|
4
4
|
|
|
5
5
|
## Demo
|
|
6
6
|
|
|
7
|
-
<video src="https://assets.aliou.me/pi-extensions/
|
|
7
|
+
<video src="https://assets.aliou.me/pi-extensions/demos/pi-guardrails.mp4" controls playsinline muted></video>
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
pi install npm:@aliou/pi-guardrails
|
|
@@ -18,66 +18,57 @@ Or from git:
|
|
|
18
18
|
pi install git:github.com/aliou/pi-guardrails
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## What it does
|
|
22
22
|
|
|
23
|
-
- **
|
|
24
|
-
- **permission-gate**:
|
|
23
|
+
- **policies**: named file-protection rules with per-rule protection levels.
|
|
24
|
+
- **permission-gate**: detects dangerous bash commands and asks for confirmation.
|
|
25
|
+
- **optional command explainer**: can call a small LLM to explain a dangerous command inline in the confirmation dialog.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
## Config locations
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
Guardrails reads and merges config from:
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
- Global: `~/.pi/agent/extensions/guardrails.json`
|
|
32
|
+
- Project: `.pi/extensions/guardrails.json`
|
|
33
|
+
- Memory (session): internal temporary scope used by settings/commands
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
Priority: `memory > local > global > defaults`.
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
- **Project**: `.pi/extensions/guardrails.json`
|
|
37
|
+
Use `/guardrails:settings` to edit config interactively.
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Run `/guardrails:settings` to open an interactive settings UI with two tabs:
|
|
40
|
-
- **Local**: edit project-scoped config (`.pi/extensions/guardrails.json`)
|
|
41
|
-
- **Global**: edit global config (`~/.pi/agent/extensions/guardrails.json`)
|
|
42
|
-
|
|
43
|
-
Use `Tab` / `Shift+Tab` to switch tabs. Boolean settings can be toggled directly.
|
|
44
|
-
|
|
45
|
-
### Migration from v0
|
|
46
|
-
|
|
47
|
-
Configs without a `version` field are automatically migrated on first load. The migration:
|
|
48
|
-
- Backs up the original as `guardrails.v0.json`
|
|
49
|
-
- Converts all string patterns to `{ pattern, regex: true }` to preserve behavior
|
|
50
|
-
- Adds a `version` field
|
|
51
|
-
|
|
52
|
-
### Configuration Schema
|
|
39
|
+
## Current schema
|
|
53
40
|
|
|
54
41
|
```json
|
|
55
42
|
{
|
|
56
43
|
"enabled": true,
|
|
57
44
|
"features": {
|
|
58
|
-
"
|
|
45
|
+
"policies": true,
|
|
59
46
|
"permissionGate": true
|
|
60
47
|
},
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
48
|
+
"policies": {
|
|
49
|
+
"rules": [
|
|
50
|
+
{
|
|
51
|
+
"id": "secret-files",
|
|
52
|
+
"description": "Files containing secrets",
|
|
53
|
+
"patterns": [
|
|
54
|
+
{ "pattern": ".env" },
|
|
55
|
+
{ "pattern": ".env.local" },
|
|
56
|
+
{ "pattern": ".env.production" },
|
|
57
|
+
{ "pattern": ".env.prod" },
|
|
58
|
+
{ "pattern": ".dev.vars" }
|
|
59
|
+
],
|
|
60
|
+
"allowedPatterns": [
|
|
61
|
+
{ "pattern": ".env.example" },
|
|
62
|
+
{ "pattern": ".env.sample" },
|
|
63
|
+
{ "pattern": ".env.test" },
|
|
64
|
+
{ "pattern": "*.example.env" },
|
|
65
|
+
{ "pattern": "*.sample.env" },
|
|
66
|
+
{ "pattern": "*.test.env" }
|
|
67
|
+
],
|
|
68
|
+
"protection": "noAccess",
|
|
69
|
+
"onlyIfExists": true
|
|
70
|
+
}
|
|
71
|
+
]
|
|
81
72
|
},
|
|
82
73
|
"permissionGate": {
|
|
83
74
|
"patterns": [
|
|
@@ -87,110 +78,93 @@ Configs without a `version` field are automatically migrated on first load. The
|
|
|
87
78
|
"customPatterns": [],
|
|
88
79
|
"requireConfirmation": true,
|
|
89
80
|
"allowedPatterns": [],
|
|
90
|
-
"autoDenyPatterns": []
|
|
81
|
+
"autoDenyPatterns": [],
|
|
82
|
+
"explainCommands": false,
|
|
83
|
+
"explainModel": null,
|
|
84
|
+
"explainTimeout": 5000
|
|
91
85
|
}
|
|
92
86
|
}
|
|
93
87
|
```
|
|
94
88
|
|
|
95
|
-
All fields
|
|
89
|
+
All fields optional. Missing fields use defaults.
|
|
96
90
|
|
|
97
|
-
|
|
91
|
+
## Policies
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
Each rule has:
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
- `
|
|
95
|
+
- `id`: stable identifier used for overrides across scopes.
|
|
96
|
+
- `patterns`: files to match (glob by default, regex if `regex: true`).
|
|
97
|
+
- `allowedPatterns`: exceptions.
|
|
98
|
+
- `protection`:
|
|
99
|
+
- `noAccess`: block `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`
|
|
100
|
+
- `readOnly`: block `write`, `edit`, `bash`
|
|
101
|
+
- `none`: explicit no protection
|
|
102
|
+
- `onlyIfExists` (default true)
|
|
103
|
+
- `blockMessage` with `{file}` placeholder
|
|
104
|
+
- `enabled` (default true)
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `regex: true`: full regex against the raw command string. Example: `{ "pattern": "rm\\s+-rf", "regex": true }`.
|
|
106
|
+
When multiple rules match the same file, strongest protection wins:
|
|
107
|
+
`noAccess > readOnly > none`.
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
### Add rule with AI
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Use:
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
```text
|
|
114
|
+
/guardrails:add-policy
|
|
115
|
+
```
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|---|---|---|
|
|
117
|
-
| `protectEnvFiles` | `true` | Block access to `.env` files containing secrets |
|
|
118
|
-
| `permissionGate` | `true` | Prompt for confirmation on dangerous commands |
|
|
117
|
+
This starts a subagent that helps build and save one policy rule.
|
|
119
118
|
|
|
120
|
-
|
|
119
|
+
## Permission gate
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|---|---|---|
|
|
124
|
-
| `protectedPatterns` | `[".env", ".env.local", ...]` | Patterns for files to protect (glob by default) |
|
|
125
|
-
| `allowedPatterns` | `[".env.example", "*.example.env", ...]` | Patterns for allowed exceptions |
|
|
126
|
-
| `protectedDirectories` | `[]` | Patterns for directories to protect |
|
|
127
|
-
| `protectedTools` | `["read", "write", "edit", "bash", "grep", "find", "ls"]` | Tools to intercept |
|
|
128
|
-
| `onlyBlockIfExists` | `true` | Only block if the file exists on disk |
|
|
129
|
-
| `blockMessage` | See defaults | Message shown when blocked. Supports `{file}` placeholder |
|
|
121
|
+
Detects dangerous bash commands and prompts user confirmation.
|
|
130
122
|
|
|
131
|
-
|
|
123
|
+
Built-in dangerous patterns are matched structurally (AST-based) for better accuracy:
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| `autoDenyPatterns` | `[]` | Patterns that are blocked immediately without dialog |
|
|
125
|
+
- `rm -rf`
|
|
126
|
+
- `sudo`
|
|
127
|
+
- `dd if=`
|
|
128
|
+
- `mkfs.`
|
|
129
|
+
- `chmod -R 777`
|
|
130
|
+
- `chown -R`
|
|
140
131
|
|
|
141
|
-
|
|
132
|
+
You can also add custom dangerous patterns.
|
|
142
133
|
|
|
143
|
-
|
|
134
|
+
### Explain commands (opt-in)
|
|
144
135
|
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
"permissionGate": {
|
|
148
|
-
"patterns": [
|
|
149
|
-
{ "pattern": "rm -rf", "description": "recursive force delete" },
|
|
150
|
-
{ "pattern": "sudo", "description": "superuser command" },
|
|
151
|
-
{ "pattern": "docker system prune", "description": "docker system prune" }
|
|
152
|
-
]
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
136
|
+
If enabled, guardrails calls an LLM before showing the confirmation dialog and displays a short explanation.
|
|
156
137
|
|
|
157
|
-
|
|
138
|
+
Config fields:
|
|
158
139
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
"patterns": [
|
|
163
|
-
{ "pattern": "rm\\s+-rf\\s+/(?!tmp)", "description": "rm -rf outside /tmp", "regex": true }
|
|
164
|
-
]
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
```
|
|
140
|
+
- `permissionGate.explainCommands` (boolean)
|
|
141
|
+
- `permissionGate.explainModel` (`provider/model-id`)
|
|
142
|
+
- `permissionGate.explainTimeout` (ms)
|
|
168
143
|
|
|
169
|
-
|
|
144
|
+
Failures/timeouts degrade gracefully: dialog still shows without explanation.
|
|
170
145
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
146
|
+
## Migration notes
|
|
147
|
+
|
|
148
|
+
Legacy fields are auto-migrated:
|
|
149
|
+
|
|
150
|
+
- `features.protectEnvFiles` -> `features.policies`
|
|
151
|
+
- `envFiles` -> `policies.rules` (migrated into `secret-files`)
|
|
152
|
+
|
|
153
|
+
`config.version` is a schema marker, not npm package version.
|
|
154
|
+
|
|
155
|
+
Also note:
|
|
156
|
+
|
|
157
|
+
- `preventBrew`, `preventPython`, `enforcePackageManager`, `packageManager` were removed from guardrails and moved to `@aliou/pi-toolchain`.
|
|
182
158
|
|
|
183
159
|
## Events
|
|
184
160
|
|
|
185
|
-
|
|
161
|
+
Guardrails emits events for other extensions:
|
|
186
162
|
|
|
187
163
|
### `guardrails:blocked`
|
|
188
164
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
165
|
+
```ts
|
|
192
166
|
interface GuardrailsBlockedEvent {
|
|
193
|
-
feature: "
|
|
167
|
+
feature: "policies" | "permissionGate";
|
|
194
168
|
toolName: string;
|
|
195
169
|
input: Record<string, unknown>;
|
|
196
170
|
reason: string;
|
|
@@ -200,36 +174,10 @@ interface GuardrailsBlockedEvent {
|
|
|
200
174
|
|
|
201
175
|
### `guardrails:dangerous`
|
|
202
176
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
177
|
+
```ts
|
|
206
178
|
interface GuardrailsDangerousEvent {
|
|
207
179
|
command: string;
|
|
208
180
|
description: string;
|
|
209
181
|
pattern: string;
|
|
210
182
|
}
|
|
211
183
|
```
|
|
212
|
-
|
|
213
|
-
The [presenter extension](https://github.com/aliou/pi-extensions/tree/main/extensions/presenter) listens for `guardrails:dangerous` events and plays a notification sound.
|
|
214
|
-
|
|
215
|
-
## Hooks
|
|
216
|
-
|
|
217
|
-
### protect-env-files
|
|
218
|
-
|
|
219
|
-
Prevents accessing `.env` files that might contain secrets. Only allows access to safe variants like `.env.example`, `.env.sample`, `.env.test`.
|
|
220
|
-
|
|
221
|
-
Shell globs (e.g. `.env*`) are expanded via `fd` to check if any expanded path matches a protected pattern.
|
|
222
|
-
|
|
223
|
-
Covers tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls` (configurable).
|
|
224
|
-
|
|
225
|
-
### permission-gate
|
|
226
|
-
|
|
227
|
-
Prompts user confirmation before executing dangerous commands:
|
|
228
|
-
- `rm -rf` (recursive force delete)
|
|
229
|
-
- `sudo` (superuser command)
|
|
230
|
-
- `dd if=` (disk write operation)
|
|
231
|
-
- `mkfs.` (filesystem format)
|
|
232
|
-
- `chmod -R 777` (insecure recursive permissions)
|
|
233
|
-
- `chown -R` (recursive ownership change)
|
|
234
|
-
|
|
235
|
-
Built-in patterns are matched structurally (AST-based). Custom patterns use substring or regex matching. Supports allow-lists and auto-deny lists.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-guardrails",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -29,17 +29,21 @@
|
|
|
29
29
|
"README.md"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@aliou/pi-utils-settings": "^0.
|
|
32
|
+
"@aliou/pi-utils-settings": "^0.8.0",
|
|
33
33
|
"@aliou/sh": "^0.1.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@mariozechner/pi-
|
|
36
|
+
"@mariozechner/pi-agent-core": ">=0.52.7",
|
|
37
|
+
"@mariozechner/pi-ai": ">=0.52.7",
|
|
38
|
+
"@mariozechner/pi-coding-agent": ">=0.52.7",
|
|
37
39
|
"@mariozechner/pi-tui": ">=0.51.0"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"@aliou/biome-plugins": "^0.3.2",
|
|
41
43
|
"@biomejs/biome": "^2.3.13",
|
|
42
44
|
"@changesets/cli": "^2.27.11",
|
|
45
|
+
"@mariozechner/pi-agent-core": "0.52.7",
|
|
46
|
+
"@mariozechner/pi-ai": "0.52.7",
|
|
43
47
|
"@mariozechner/pi-coding-agent": "0.52.7",
|
|
44
48
|
"@sinclair/typebox": "^0.34.48",
|
|
45
49
|
"@types/node": "^25.0.10",
|
|
@@ -47,6 +51,12 @@
|
|
|
47
51
|
"typescript": "^5.9.3"
|
|
48
52
|
},
|
|
49
53
|
"peerDependenciesMeta": {
|
|
54
|
+
"@mariozechner/pi-agent-core": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@mariozechner/pi-ai": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
50
60
|
"@mariozechner/pi-coding-agent": {
|
|
51
61
|
"optional": true
|
|
52
62
|
},
|