@agenttrail/guardrails 0.2.2 → 0.2.4
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 +132 -116
- package/dist/{chunk-GNN4TGRY.js → chunk-6YROGDEH.js} +103 -29
- package/dist/chunk-6YROGDEH.js.map +1 -0
- package/dist/chunk-T6MJIV52.js +72 -0
- package/dist/chunk-T6MJIV52.js.map +1 -0
- package/dist/{chunk-FTTMZCDH.js → chunk-U7KZ447W.js} +65 -4
- package/dist/chunk-U7KZ447W.js.map +1 -0
- package/dist/guardrails.cjs +171 -28
- package/dist/guardrails.cjs.map +1 -1
- package/dist/guardrails.d.cts +60 -65
- package/dist/guardrails.d.ts +60 -65
- package/dist/guardrails.js +17 -1
- package/dist/index.cjs +230 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +20 -2
- package/dist/schema-B2pYoi9Z.d.cts +951 -0
- package/dist/schema-B2pYoi9Z.d.ts +951 -0
- package/dist/schema.cjs +91 -3
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +2 -892
- package/dist/schema.d.ts +2 -892
- package/dist/schema.js +4 -1
- package/package.json +30 -38
- package/dist/chunk-FTTMZCDH.js.map +0 -1
- package/dist/chunk-GNN4TGRY.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</picture>
|
|
10
10
|
</a>
|
|
11
11
|
|
|
12
|
-
# AgentTrail Guardrails: open-source
|
|
12
|
+
# AgentTrail Guardrails: an open-source guardrail library for AI coding agents
|
|
13
13
|
|
|
14
|
-
**A library of
|
|
14
|
+
**A library of guardrails for catching dangerous commands before an AI coding agent runs them.**<br />
|
|
15
15
|
Plain data you can read, test, and change. Enforced in **Claude Code**, **Cursor**, and **Codex CLI**.
|
|
16
16
|
|
|
17
17
|
[](https://github.com/agenttrailhq/guardrails/actions/workflows/ci.yml)
|
|
@@ -22,13 +22,13 @@ Plain data you can read, test, and change. Enforced in **Claude Code**, **Cursor
|
|
|
22
22
|
[Quick start](#quick-start) ·
|
|
23
23
|
[The packs](#the-eleven-packs) ·
|
|
24
24
|
[OWASP coverage](#owasp-coverage) ·
|
|
25
|
-
[
|
|
26
|
-
[Contribute a
|
|
25
|
+
[Guardrail format](#a-guardrail-start-to-finish) ·
|
|
26
|
+
[Contribute a guardrail](#contributing-a-guardrail) ·
|
|
27
27
|
[Changelog](CHANGELOG.md)
|
|
28
28
|
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
|
-
[74
|
|
31
|
+
[74 guardrails](https://www.agenttrail.sh/guardrails), grouped into 11 packs. Apache-2.0. Published
|
|
32
32
|
as `@agenttrail/guardrails` and enforced by [AgentTrail Guard](https://github.com/agenttrailhq/guard).
|
|
33
33
|
|
|
34
34
|

|
|
@@ -48,28 +48,28 @@ for, how serious it is, and what should happen: allow it, ask a human first, or
|
|
|
48
48
|
**That is all this package does.** It contains no code that watches your machine and nothing that
|
|
49
49
|
talks to the network. Something else has to read the list and act on it: normally
|
|
50
50
|
[`agenttrail-guard`](https://github.com/agenttrailhq/guard), which runs on your laptop and checks each
|
|
51
|
-
command an agent proposes against these
|
|
51
|
+
command an agent proposes against these guardrails.
|
|
52
52
|
|
|
53
|
-
## Why these
|
|
53
|
+
## Why these guardrails?
|
|
54
54
|
|
|
55
|
-
- **Readable.** Every
|
|
55
|
+
- **Readable.** Every guardrail is plain data with a title, a severity, a default action, and a
|
|
56
56
|
description that says what it catches **and what it misses**.
|
|
57
|
-
- **Proven in both directions.** Every
|
|
58
|
-
leave alone, and a shared corpus of everyday commands no
|
|
57
|
+
- **Proven in both directions.** Every guardrail ships commands it must catch and near-misses it must
|
|
58
|
+
leave alone, and a shared corpus of everyday commands no guardrail may match. A guardrail that fails
|
|
59
59
|
either side does not build.
|
|
60
60
|
- **Quiet on purpose.** A commit message or a `grep` that only *mentions* `rm -rf /` is not
|
|
61
61
|
treated as running it. See [Talking about a command is not running it](#talking-about-a-command-is-not-running-it).
|
|
62
62
|
- **Mapped to known risks.** The packs line up with the
|
|
63
63
|
[OWASP Top 10s for agentic and LLM applications](#owasp-coverage), and the gaps are stated, not
|
|
64
64
|
hidden.
|
|
65
|
-
- **Separate from the enforcer.** You can read, disagree with, and change any
|
|
65
|
+
- **Separate from the enforcer.** You can read, disagree with, and change any guardrail without
|
|
66
66
|
trusting anything about the tool that enforces it.
|
|
67
67
|
|
|
68
68
|
## Quick start
|
|
69
69
|
|
|
70
70
|
**You need:** Node.js 22.13 or newer.
|
|
71
71
|
|
|
72
|
-
### Enforce the
|
|
72
|
+
### Enforce the guardrails in your agent
|
|
73
73
|
|
|
74
74
|
Most people never install this package directly. [AgentTrail Guard](https://github.com/agenttrailhq/guard)
|
|
75
75
|
bundles it, so installing Guard is all it takes:
|
|
@@ -77,18 +77,18 @@ bundles it, so installing Guard is all it takes:
|
|
|
77
77
|
```bash
|
|
78
78
|
npm install -g @agenttrail/guard
|
|
79
79
|
agenttrail-guard init --agent claude # or: --agent cursor, --agent codex
|
|
80
|
-
agenttrail-guard guardrails list # every
|
|
81
|
-
agenttrail-guard guardrails show wt.reset-hard # one
|
|
82
|
-
agenttrail-guard guardrails set-action <id> warn # change what a
|
|
83
|
-
agenttrail-guard guardrails allow <id> <pattern> # silence one
|
|
80
|
+
agenttrail-guard guardrails list # every guardrail and its current action
|
|
81
|
+
agenttrail-guard guardrails show wt.reset-hard # one guardrail in full
|
|
82
|
+
agenttrail-guard guardrails set-action <id> warn # change what a guardrail does
|
|
83
|
+
agenttrail-guard guardrails allow <id> <pattern> # silence one guardrail for one command shape
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
See the [AgentTrail Guard README](https://github.com/agenttrailhq/guard#quick-start) for setup
|
|
87
87
|
and everything else Guard can do.
|
|
88
88
|
|
|
89
|
-
### Use the
|
|
89
|
+
### Use the guardrails in your own code
|
|
90
90
|
|
|
91
|
-
Install the package if you are writing
|
|
91
|
+
Install the package if you are writing guardrails, or building your own tool on top of the list:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
npm install @agenttrail/guardrails
|
|
@@ -99,24 +99,24 @@ import { RULES, getRule, rulesForPack } from "@agenttrail/guardrails/guardrails"
|
|
|
99
99
|
|
|
100
100
|
// Everything that refuses outright, rather than asking or warning.
|
|
101
101
|
const blocking = RULES.filter((rule) => rule.defaultAction === "block");
|
|
102
|
-
console.log(`${RULES.length}
|
|
102
|
+
console.log(`${RULES.length} guardrails, ${blocking.length} block by default`);
|
|
103
103
|
|
|
104
|
-
// One pack, with each
|
|
104
|
+
// One pack, with each guardrail's severity and default action.
|
|
105
105
|
for (const rule of rulesForPack("secret-exposure")) {
|
|
106
106
|
console.log(rule.id, rule.severity, rule.defaultAction);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// One
|
|
109
|
+
// One guardrail, including the limits its description admits to.
|
|
110
110
|
console.log(getRule("wt.reset-hard")?.description);
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
This package has no evaluator: it tells you what each
|
|
113
|
+
This package has no evaluator: it tells you what each guardrail looks for, not whether a given
|
|
114
114
|
command matches. Guard does the matching. [Two ways to import it](#two-ways-to-import-it)
|
|
115
115
|
explains the two entry points.
|
|
116
116
|
|
|
117
|
-
## A
|
|
117
|
+
## A guardrail, start to finish
|
|
118
118
|
|
|
119
|
-
Here is a complete
|
|
119
|
+
Here is a complete guardrail. Nothing is hidden; this is the actual shape.
|
|
120
120
|
|
|
121
121
|
```jsonc
|
|
122
122
|
{
|
|
@@ -126,6 +126,7 @@ Here is a complete rule. Nothing is hidden; this is the actual shape.
|
|
|
126
126
|
"defaultAction": "block",
|
|
127
127
|
"title": "git reset --hard discards uncommitted work",
|
|
128
128
|
"description": "Discards all uncommitted changes. Does not match `git restore` (see wt.restore-path).",
|
|
129
|
+
"owasp": ["ASI02", "LLM03"],
|
|
129
130
|
|
|
130
131
|
// What to look for. This one matches a Bash command against a regular expression.
|
|
131
132
|
"match": {
|
|
@@ -134,7 +135,7 @@ Here is a complete rule. Nothing is hidden; this is the actual shape.
|
|
|
134
135
|
]
|
|
135
136
|
},
|
|
136
137
|
|
|
137
|
-
// Proof it works, in both directions (see "Every
|
|
138
|
+
// Proof it works, in both directions (see "Every guardrail proves both directions" below).
|
|
138
139
|
"fixtures": {
|
|
139
140
|
"block": ["git reset --hard"],
|
|
140
141
|
"allow": ["git reset src/api.ts"]
|
|
@@ -142,32 +143,33 @@ Here is a complete rule. Nothing is hidden; this is the actual shape.
|
|
|
142
143
|
}
|
|
143
144
|
```
|
|
144
145
|
|
|
145
|
-
The real
|
|
146
|
+
The real guardrail is in [`wt-reset-hard.ts`](src/packs/working-tree/wt-reset-hard.ts); this is a
|
|
146
147
|
trimmed copy. With it enforced by Guard, an agent that proposes `git reset --hard` is refused
|
|
147
|
-
and told which
|
|
148
|
+
and told which guardrail stopped it, while `git reset src/api.ts`, which unstages one file, goes
|
|
148
149
|
ahead as normal.
|
|
149
150
|
|
|
150
151
|
Reading the fields:
|
|
151
152
|
|
|
152
153
|
| Field | What it means |
|
|
153
154
|
|---|---|
|
|
154
|
-
| `id` | A stable name. Users type it to disable or change a
|
|
155
|
+
| `id` | A stable name. Users type it to disable or change a guardrail, so it never changes. |
|
|
155
156
|
| `severity` | How bad the thing being caught is: `critical`, `high`, `medium`, `low`, `info`. **It is not a price**: this package ships no mapping from severity to money. |
|
|
156
157
|
| `defaultAction` | What should happen: `block` (refuse), `require_approval` (ask a human), or `warn` (allow, but say so). A user can override it. |
|
|
157
|
-
| `description` | What the
|
|
158
|
+
| `description` | What the guardrail catches **and what it misses**. The honest limits are part of the guardrail, not a footnote. |
|
|
159
|
+
| `owasp` | Optional. The [OWASP risks](#owasp-coverage) the guardrail addresses, as ids such as `ASI02` or `LLM06`, in list order. Every guardrail in this package has at least one. Versions up to and including 0.2.2 reject the field, so validate a guardrail that carries it with 0.2.3 or later. |
|
|
158
160
|
| `match` | The condition. `any_of` means "any one of these is enough". |
|
|
159
161
|
| `fixtures` | Examples that must match, and examples that must not. |
|
|
160
162
|
|
|
161
163
|
## The eleven packs
|
|
162
164
|
|
|
163
|
-
A
|
|
165
|
+
A guardrail is filed by **the harm it prevents**, never by the technique it uses to spot it.
|
|
164
166
|
|
|
165
|
-
That sounds like a detail and is not. The three
|
|
167
|
+
That sounds like a detail and is not. The three guardrails about production config, `.env` files and API
|
|
166
168
|
endpoints all work by matching file paths, but they are *not* in `file-scope`. Someone who turned
|
|
167
169
|
that pack off to stop path noise would otherwise silently lose their production and secret
|
|
168
170
|
protection, which they never asked to turn off and would not know they had.
|
|
169
171
|
|
|
170
|
-
| Pack |
|
|
172
|
+
| Pack | Guardrails | What it is about |
|
|
171
173
|
|---|---:|---|
|
|
172
174
|
| [`working-tree`](src/packs/working-tree/) | 9 | Destroying uncommitted work or published history: `git reset --hard`, `git clean -fd`, force-push, `rm -rf`. |
|
|
173
175
|
| [`destructive-data`](src/packs/destructive-data/) | 8 | Data git cannot bring back: a dropped volume, a dropped database, destructive DDL, a deleted shadow copy. |
|
|
@@ -183,10 +185,10 @@ protection, which they never asked to turn off and would not know they had.
|
|
|
183
185
|
|
|
184
186
|
Pack names appear in user config files, so renaming one is a breaking change, not a tidy-up.
|
|
185
187
|
|
|
186
|
-
Each pack name links to its source folder: one TypeScript file per
|
|
187
|
-
end to end. Every
|
|
188
|
+
Each pack name links to its source folder: one TypeScript file per guardrail, so you can read any guardrail
|
|
189
|
+
end to end. Every guardrail, with its full description and tested examples, is also in the
|
|
188
190
|
[guardrail library](https://www.agenttrail.sh/guardrails) on the AgentTrail website, where you
|
|
189
|
-
can paste a command to see which
|
|
191
|
+
can paste a command to see which guardrails match it.
|
|
190
192
|
|
|
191
193
|
## OWASP coverage
|
|
192
194
|
|
|
@@ -194,28 +196,39 @@ Several packs exist because of risks named in OWASP's two lists for AI systems:
|
|
|
194
196
|
[Top 10 for Agentic Applications](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/)
|
|
195
197
|
(ASI01 to ASI10, 2026 edition) and the
|
|
196
198
|
[Top 10 for LLM Applications](https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/)
|
|
197
|
-
(LLM01 to LLM10, 2026 edition). A
|
|
198
|
-
action that comes out of it, so these
|
|
199
|
+
(LLM01 to LLM10, 2026 edition). A guardrail cannot see a prompt or a model's reasoning, only the
|
|
200
|
+
action that comes out of it, so these guardrails address a risk at the point where it turns into a
|
|
199
201
|
command or a file change.
|
|
200
202
|
|
|
201
|
-
| Pack | Agentic risks | LLM risks |
|
|
202
|
-
|
|
203
|
-
| `working-tree`, `destructive-data`, `prod-infra`, `file-scope` | ASI02 Tool Misuse and Exploitation | LLM03 Excessive Agency |
|
|
204
|
-
| `rce-supply-chain` | ASI05 Unexpected Code Execution
|
|
205
|
-
| `privilege-supply-chain` | ASI03 Identity and Privilege Abuse, ASI04 Agentic Supply Chain | LLM04 Supply Chain |
|
|
206
|
-
| `secret-exposure`, `exfiltration` | ASI02 Tool Misuse and Exploitation, ASI03 Identity and Privilege Abuse | LLM02 Sensitive Information Disclosure |
|
|
207
|
-
| `safety-bypass` | ASI09 Human-Agent Trust Exploitation | LLM10 Improper Output Handling (`gb.ansi-terminal-forgery`) |
|
|
208
|
-
| `agent-context` | ASI06 Memory and Context Poisoning (the write, not its contents), ASI10 Rogue Agents | LLM03 Excessive Agency |
|
|
209
|
-
| `test-integrity` | ASI10 Rogue Agents, ASI09 Human-Agent Trust Exploitation | |
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
| Pack | Agentic risks | LLM risks | Default action |
|
|
204
|
+
|---|---|---|---|
|
|
205
|
+
| `working-tree`, `destructive-data`, `prod-infra`, `file-scope` | ASI02 Tool Misuse and Exploitation | LLM03 Excessive Agency, LLM10 Improper Output Handling (`block-destructive-sql`) | `block` or `require_approval` |
|
|
206
|
+
| `rce-supply-chain` | ASI04 Agentic Supply Chain, ASI05 Unexpected Code Execution | LLM04 Supply Chain, LLM07 Misinformation (`rce.unverified-package`) | `block` or `require_approval` |
|
|
207
|
+
| `privilege-supply-chain` | ASI03 Identity and Privilege Abuse, ASI04 Agentic Supply Chain | LLM03 Excessive Agency (`ps.iam-grant`), LLM04 Supply Chain, LLM07 Misinformation (`flag-dependency-install`) | `require_approval`, except `flag-dependency-install`, which is `warn` |
|
|
208
|
+
| `secret-exposure`, `exfiltration` | ASI02 Tool Misuse and Exploitation, ASI03 Identity and Privilege Abuse | LLM02 Sensitive Information Disclosure | `secret-exposure` is mostly `warn`; `exfiltration` is `block` or `require_approval` |
|
|
209
|
+
| `safety-bypass` | ASI09 Human-Agent Trust Exploitation | LLM10 Improper Output Handling (`gb.ansi-terminal-forgery`) | `require_approval`, except `gb.ansi-terminal-forgery`, which is `warn` |
|
|
210
|
+
| `agent-context` | ASI03 Identity and Privilege Abuse (`ac.agent-autonomy-flag`), ASI04 Agentic Supply Chain (`ac.mcp-server-add`), ASI06 Memory and Context Poisoning (the write, not its contents), ASI10 Rogue Agents | LLM03 Excessive Agency | `require_approval` |
|
|
211
|
+
| `test-integrity` | ASI10 Rogue Agents, ASI09 Human-Agent Trust Exploitation | | Mostly `warn`; deleting a test file or editing a test runner's configuration is `require_approval` |
|
|
212
|
+
|
|
213
|
+
Each guardrail lists the ids it addresses in its own `owasp` field, so
|
|
214
|
+
`RULES.filter((rule) => rule.owasp?.includes("ASI06"))` gives the guardrails behind one risk. Each row
|
|
215
|
+
above is the union of its packs' ids, with the guardrail named where only one guardrail in the pack carries
|
|
216
|
+
an id. A test keeps the table and the guardrails' ids equal.
|
|
217
|
+
|
|
218
|
+
Read the default action before relying on a risk. A `warn` guardrail lets the action proceed and records
|
|
219
|
+
it; only `require_approval` and `block` stop it. Reading a secret is a normal part of a normal day,
|
|
220
|
+
so most secret-exposure guardrails warn, and a dependency install is flagged, not held. A file tool
|
|
221
|
+
carries a path rather than content, so `ac.memory-store-edit` cannot see what was written to a
|
|
222
|
+
memory file. You can change any guardrail's action in your own configuration.
|
|
223
|
+
|
|
224
|
+
What the guardrails cannot address, by design:
|
|
225
|
+
|
|
226
|
+
- **Prompt injection and goal hijack (LLM01, ASI01).** The guardrails never see the input that
|
|
214
227
|
redirected the agent. They can only stop the action it led to.
|
|
215
|
-
- **Anything that needs memory across commands (LLM06, ASI08).** A
|
|
228
|
+
- **Anything that needs memory across commands (LLM06, ASI08).** A guardrail sees one command at a
|
|
216
229
|
time, so it cannot count, rate-limit, or spot a runaway loop.
|
|
217
230
|
- **Model, training and retrieval risks (LLM05, LLM08, LLM09), and traffic between agents
|
|
218
|
-
(ASI07).** These happen somewhere a command
|
|
231
|
+
(ASI07).** These happen somewhere a command guardrail never reaches.
|
|
219
232
|
|
|
220
233
|
For the item-by-item matrix, including how the rest of AgentTrail covers each risk, see the
|
|
221
234
|
[AgentTrail OWASP coverage page](https://www.agenttrail.sh/security/owasp). AgentTrail is an
|
|
@@ -223,10 +236,10 @@ independent project, not affiliated with or endorsed by OWASP.
|
|
|
223
236
|
|
|
224
237
|
## Talking about a command is not running it
|
|
225
238
|
|
|
226
|
-
This is the single most important thing to understand about how these
|
|
239
|
+
This is the single most important thing to understand about how these guardrails behave.
|
|
227
240
|
|
|
228
|
-
A
|
|
229
|
-
**runs** something from one that merely **mentions** it. Left alone, that makes the
|
|
241
|
+
A guardrail sees the command as one line of text. Nothing in that text distinguishes a command that
|
|
242
|
+
**runs** something from one that merely **mentions** it. Left alone, that makes the guardrail set unusable
|
|
230
243
|
by exactly the people most likely to install it:
|
|
231
244
|
|
|
232
245
|
```bash
|
|
@@ -234,7 +247,7 @@ git commit -m "fix: document rm -rf / risk" # would have been a hard refusal
|
|
|
234
247
|
grep -rn "rm -rf /" docs/ # so would this
|
|
235
248
|
```
|
|
236
249
|
|
|
237
|
-
So every command
|
|
250
|
+
So every command guardrail ignores four **carriers**, verbs that handle their arguments as text and never
|
|
238
251
|
execute them:
|
|
239
252
|
|
|
240
253
|
| Carrier | Example |
|
|
@@ -258,16 +271,16 @@ echo "rm -rf /" | bash # NOT exempt: the pipe runs it
|
|
|
258
271
|
echo "$(rm -rf /var)" # NOT exempt: the shell expands `$( )`
|
|
259
272
|
```
|
|
260
273
|
|
|
261
|
-
Five
|
|
262
|
-
`gb.git-no-verify` on a `git commit`, `se.token-print` on an `echo`, the `curl`/`wget`
|
|
263
|
-
HTTP body. Each
|
|
274
|
+
Five guardrails deliberately keep firing on one carrier each, because that carrier *is* their trigger:
|
|
275
|
+
`gb.git-no-verify` on a `git commit`, `se.token-print` on an `echo`, the `curl`/`wget` guardrails on an
|
|
276
|
+
HTTP body. Each guardrail's `description` says which, and why.
|
|
264
277
|
|
|
265
|
-
### The one
|
|
278
|
+
### The one guardrail where a mention IS the danger
|
|
266
279
|
|
|
267
280
|
`block-hardcoded-secrets` is the exception, and it is worth understanding.
|
|
268
281
|
|
|
269
282
|
Everywhere else a carrier is genuinely harmless: a commit message naming `rm -rf /` deletes nothing.
|
|
270
|
-
But that
|
|
283
|
+
But that guardrail's subject is a **string**, not an action, so two of the four carriers are not mentions
|
|
271
284
|
at all. They are the exposure itself:
|
|
272
285
|
|
|
273
286
|
| Carrier | What happens to the key | Exempt? |
|
|
@@ -280,43 +293,43 @@ at all. They are the exposure itself:
|
|
|
280
293
|
The cost, stated in the other direction: documenting a real-looking key in a commit message is still
|
|
281
294
|
blocked. Redact the body of the key, or use a placeholder short enough to fail the length check.
|
|
282
295
|
|
|
283
|
-
The finer limits of the carrier logic, and how command
|
|
296
|
+
The finer limits of the carrier logic, and how command guardrails apply to MCP tools, are in the
|
|
284
297
|
[Reference](#reference).
|
|
285
298
|
|
|
286
|
-
## What these
|
|
299
|
+
## What these guardrails deliberately do not catch
|
|
287
300
|
|
|
288
301
|
Stated here rather than discovered later. Every one is a real limit of the format, not something
|
|
289
302
|
somebody forgot.
|
|
290
303
|
|
|
291
|
-
- **Nothing about the web.** Pages an agent fetches are not checked, and there are no URL
|
|
304
|
+
- **Nothing about the web.** Pages an agent fetches are not checked, and there are no URL guardrails.
|
|
292
305
|
- **Nothing inside a file.** The checker sees a file's *path*, never its contents. A secret typed into
|
|
293
306
|
a source file, SQL built by string concatenation, a missing auth check: none of it is visible.
|
|
294
|
-
|
|
307
|
+
Guardrails that would need it are absent rather than approximated.
|
|
295
308
|
- **Nothing about where you are.** No working directory, no project root, no git branch, no cloud
|
|
296
309
|
profile reaches the checker; it gets one command and nothing else. So "the agent wrote outside the
|
|
297
|
-
project" **cannot be written as a
|
|
298
|
-
for good. For the same reason, a
|
|
310
|
+
project" **cannot be written as a guardrail**, and `file-scope` is limited to well-known absolute paths
|
|
311
|
+
for good. For the same reason, a guardrail cannot tell a scratch database from a production one.
|
|
299
312
|
- **Nothing hidden inside a quoted payload.** Where the danger is inside a quoted argument
|
|
300
|
-
(`psql -c "<sql>"`, `python -c "<code>"`), a text
|
|
313
|
+
(`psql -c "<sql>"`, `python -c "<code>"`), a text guardrail can only guess. In a long script, a match
|
|
301
314
|
says very little about what the script actually does.
|
|
302
315
|
- **Nothing a wrapper hides.** `./deploy.sh` that runs `terraform apply -auto-approve` inside it is
|
|
303
316
|
just a shell script from the outside.
|
|
304
317
|
- **Nothing recurring.** There is no counting. "The same mistake three times this week" needs memory
|
|
305
318
|
across commands, and a single command has none.
|
|
306
319
|
|
|
307
|
-
Each
|
|
320
|
+
Each guardrail's own `description` names its specific misses. Read those before trusting a guardrail to cover a
|
|
308
321
|
case; they are written to be believed, not to sell.
|
|
309
322
|
|
|
310
|
-
## Every
|
|
323
|
+
## Every guardrail proves both directions
|
|
311
324
|
|
|
312
|
-
Every
|
|
325
|
+
Every guardrail ships at least one **`block`** example and at least one **`allow`** example. A guardrail missing
|
|
313
326
|
either does not build.
|
|
314
327
|
|
|
315
|
-
- **`block` means "this
|
|
328
|
+
- **`block` means "this guardrail must match."** It does *not* mean the agent is refused; most packs
|
|
316
329
|
default to asking or warning.
|
|
317
|
-
- **`allow` means "this
|
|
318
|
-
|
|
319
|
-
day, and a
|
|
330
|
+
- **`allow` means "this guardrail must NOT match."** *This is the half that matters.* Anyone can write a
|
|
331
|
+
guardrail that catches `rm -rf /`. The hard part is not firing on `rm -rf ./node_modules` forty times a
|
|
332
|
+
day, and a guardrail with no negative example has not shown it can tell them apart.
|
|
320
333
|
|
|
321
334
|
Examples come in two kinds, and each must use the right one:
|
|
322
335
|
|
|
@@ -325,13 +338,13 @@ Examples come in two kinds, and each must use the right one:
|
|
|
325
338
|
| a command | `Bash`, `PowerShell`, a search query, an MCP tool's input |
|
|
326
339
|
| a file path | `Edit`, `Write`, `Read`, `MultiEdit`, `NotebookEdit` |
|
|
327
340
|
|
|
328
|
-
Giving a path-matching
|
|
329
|
-
nothing, which reads as proof of quietness and proves only that the path never reached the
|
|
341
|
+
Giving a path-matching guardrail a command example makes it pass **without testing anything**: it matches
|
|
342
|
+
nothing, which reads as proof of quietness and proves only that the path never reached the guardrail. CI
|
|
330
343
|
rejects that.
|
|
331
344
|
|
|
332
|
-
## Contributing a
|
|
345
|
+
## Contributing a guardrail
|
|
333
346
|
|
|
334
|
-
|
|
347
|
+
Guardrails are meant to be contributed. The bar is not "clever regex"; it is **does it fire on the real
|
|
335
348
|
thing, and stay quiet on the near-miss**.
|
|
336
349
|
|
|
337
350
|
**1. Write it**, following the shape above. Give it a `description` that says what it misses.
|
|
@@ -348,10 +361,10 @@ if (!result.success) console.error(result.error.issues);
|
|
|
348
361
|
Or, if you have the guard installed:
|
|
349
362
|
|
|
350
363
|
```bash
|
|
351
|
-
agenttrail-guard guardrails validate ./my-
|
|
364
|
+
agenttrail-guard guardrails validate ./my-guardrail.json
|
|
352
365
|
```
|
|
353
366
|
|
|
354
|
-
**3. Understand what that does and does not tell you.** It answers *"is this a well-formed
|
|
367
|
+
**3. Understand what that does and does not tell you.** It answers *"is this a well-formed guardrail?"* It
|
|
355
368
|
does **not** answer *"does it actually fire on the command I think it does?"* That needs the real
|
|
356
369
|
checker, which is not part of this package.
|
|
357
370
|
|
|
@@ -359,18 +372,18 @@ checker, which is not part of this package.
|
|
|
359
372
|
everyone. You get the shape check instantly here and the real answer there, which is where it has to
|
|
360
373
|
run to be trusted anyway.
|
|
361
374
|
|
|
362
|
-
Your
|
|
363
|
-
|
|
364
|
-
*you* thought of. The quiet corpus is what catches a Terraform
|
|
375
|
+
Your guardrail is also run against a **quiet corpus**: hundreds of everyday commands and paths that no
|
|
376
|
+
guardrail may match at all. Your own negative example only proves your guardrail is quiet on the near-miss
|
|
377
|
+
*you* thought of. The quiet corpus is what catches a Terraform guardrail firing on `pnpm test`.
|
|
365
378
|
|
|
366
379
|
### Shapes that will not validate
|
|
367
380
|
|
|
368
|
-
Three are rejected outright, each because it produces a
|
|
381
|
+
Three are rejected outright, each because it produces a guardrail that *looks* enforced and is not: the
|
|
369
382
|
worst failure a security tool can have.
|
|
370
383
|
|
|
371
384
|
| Rejected | Why |
|
|
372
385
|
|---|---|
|
|
373
|
-
| `scope` | It compares against ids that are always UUIDs, never a vendor name, so a scoped
|
|
386
|
+
| `scope` | It compares against ids that are always UUIDs, never a vendor name, so a scoped guardrail matches nothing, forever, silently. |
|
|
374
387
|
| Numeric conditions | Token counts and durations are all zero *before* a command runs. "Greater than" can never fire; "less than" fires on everything. |
|
|
375
388
|
| A command matcher and a file matcher in one condition | No real command carries both, so the condition can never be true. Split it into two under `any_of`. |
|
|
376
389
|
|
|
@@ -380,7 +393,7 @@ command through.
|
|
|
380
393
|
|
|
381
394
|
One more is caught in CI rather than by the shape check, because it cannot be caught earlier: **a
|
|
382
395
|
single-item brace list in a tool name.** `"{Bash}"` is a glob pattern, and it does not match `Bash`,
|
|
383
|
-
so the
|
|
396
|
+
so the guardrail matches nothing, forever, with no error anywhere. Write a single tool plainly as
|
|
384
397
|
`"Bash"`; braces are for real alternatives, `"{Bash,PowerShell}"`.
|
|
385
398
|
|
|
386
399
|
### Ready to open a pull request?
|
|
@@ -391,32 +404,32 @@ and report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
|
391
404
|
|
|
392
405
|
## Stability and versioning
|
|
393
406
|
|
|
394
|
-
|
|
407
|
+
Guardrail ids and pack names end up in people's config files, so they are treated as public API:
|
|
395
408
|
|
|
396
|
-
- **A
|
|
409
|
+
- **A guardrail id never changes.** Renaming or removing a guardrail would silently undo an override
|
|
397
410
|
someone set on it, so either one is a breaking change.
|
|
398
411
|
- **A pack name never changes** either, for the same reason: a renamed pack silently
|
|
399
412
|
re-enables a pack someone had turned off.
|
|
400
|
-
- **A new
|
|
401
|
-
|
|
413
|
+
- **A new guardrail or pack is a feature.** Guard turns every pack on unless you turned it off, so a
|
|
414
|
+
guardrail added in a release you install starts enforcing with no step of your own.
|
|
402
415
|
|
|
403
416
|
Releases are cut automatically from [Conventional Commits](https://www.conventionalcommits.org/).
|
|
404
|
-
Before 1.0, a breaking change ships as a minor release and a new
|
|
417
|
+
Before 1.0, a breaking change ships as a minor release and a new guardrail as a patch release, so
|
|
405
418
|
skim the [changelog](CHANGELOG.md) before you upgrade.
|
|
406
419
|
|
|
407
420
|
## FAQ
|
|
408
421
|
|
|
409
|
-
**Can I use these
|
|
422
|
+
**Can I use these guardrails without Guard?** Yes. They are plain data under Apache-2.0. You need your
|
|
410
423
|
own matcher to apply them, because this package does not contain one.
|
|
411
424
|
|
|
412
|
-
**A
|
|
413
|
-
allow <id> <pattern>` silences that one
|
|
425
|
+
**A guardrail fires on something legitimate. What do I do?** In Guard, `agenttrail-guard guardrails
|
|
426
|
+
allow <id> <pattern>` silences that one guardrail for that one command shape, without turning the guardrail
|
|
414
427
|
off. Then please [open an issue](https://github.com/agenttrailhq/guardrails/issues) with a
|
|
415
|
-
sanitized example, so the
|
|
428
|
+
sanitized example, so the guardrail can learn the near-miss.
|
|
416
429
|
|
|
417
|
-
**Why didn't a
|
|
418
|
-
|
|
419
|
-
To test a command against every
|
|
430
|
+
**Why didn't a guardrail fire on my command?** Start with the guardrail's `description`: it lists what the
|
|
431
|
+
guardrail misses. Then check [what these guardrails deliberately do not catch](#what-these-guardrails-deliberately-do-not-catch).
|
|
432
|
+
To test a command against every guardrail, paste it into the [online checker](https://www.agenttrail.sh/guardrails#check).
|
|
420
433
|
|
|
421
434
|
**Why is `secret-exposure` mostly `warn`?** Reading a secret is a normal part of a normal day.
|
|
422
435
|
Refusing every `.env` read would be turned off within a week. Moving a secret off the machine is
|
|
@@ -430,16 +443,19 @@ held for approval instead.
|
|
|
430
443
|
### Two ways to import it
|
|
431
444
|
|
|
432
445
|
```ts
|
|
433
|
-
import { RULES, getRule } from "@agenttrail/guardrails/guardrails"; // just the
|
|
434
|
-
import { parseRule } from "@agenttrail/guardrails"; //
|
|
446
|
+
import { RULES, getRule } from "@agenttrail/guardrails/guardrails"; // just the guardrails
|
|
447
|
+
import { parseRule } from "@agenttrail/guardrails"; // guardrails + the shape checker
|
|
435
448
|
```
|
|
436
449
|
|
|
437
|
-
|
|
450
|
+
The exports keep their original names (`RULES`, `getRule`, `parseRule`, `defineRule`, `Rule`); each
|
|
451
|
+
entry is a guardrail.
|
|
452
|
+
|
|
453
|
+
Use the first when you want to *apply* guardrails, and the second when you want to *validate* one you are
|
|
438
454
|
writing.
|
|
439
455
|
|
|
440
456
|
They are separate because the guard starts a fresh process on **every single command** an agent runs,
|
|
441
457
|
under a ten-second ceiling. It cannot afford to load a validator it never calls, or to re-check 74
|
|
442
|
-
|
|
458
|
+
guardrails that were already checked before release.
|
|
443
459
|
|
|
444
460
|
### Known limits of the carrier logic
|
|
445
461
|
|
|
@@ -457,38 +473,38 @@ In the same spirit as the rest of this file:
|
|
|
457
473
|
|
|
458
474
|
### MCP coverage
|
|
459
475
|
|
|
460
|
-
A command
|
|
461
|
-
MCP call's serialized `tool_input` as the same command text every command
|
|
476
|
+
A command guardrail fires on `Bash`, `PowerShell` *and* any `mcp__*` tool: the guard hands the checker an
|
|
477
|
+
MCP call's serialized `tool_input` as the same command text every command guardrail reads, so a command
|
|
462
478
|
shape run through an MCP server (`{"command":"rm -rf /"}`) is caught, not ignored. Two honest limits
|
|
463
479
|
follow from that:
|
|
464
480
|
|
|
465
|
-
1. **Start-anchored patterns may miss.** A
|
|
481
|
+
1. **Start-anchored patterns may miss.** A guardrail whose pattern is anchored to the start of the command
|
|
466
482
|
(`^…` or a command-position class) may not fire inside the JSON, where the shape sits after a `"`
|
|
467
|
-
rather than at a command boundary; the `\b`-anchored
|
|
483
|
+
rather than at a command boundary; the `\b`-anchored guardrails (most of the corpus) do fire.
|
|
468
484
|
2. **Mentions are not exempt.** The quoted-mention exemptions are shell-only, so an MCP payload that
|
|
469
485
|
merely *names* a command in a text field (`{"title":"fix the rm -rf / bug"}`) is matched the same
|
|
470
486
|
as one that runs it; a JSON blob cannot be told apart from a shell-running MCP server.
|
|
471
487
|
|
|
472
|
-
File
|
|
473
|
-
|
|
488
|
+
File guardrails match by path on whichever file tool a client uses. No guardrail is shell-only by design; a
|
|
489
|
+
guardrail that does not reach the MCP channel does so because its pattern, not its label, does not match
|
|
474
490
|
the serialized shape.
|
|
475
491
|
|
|
476
|
-
### Where these
|
|
492
|
+
### Where these guardrails came from
|
|
477
493
|
|
|
478
494
|
Independently authored. No block list, pattern or wording is copied from any other project.
|
|
479
495
|
|
|
480
|
-
Where a
|
|
496
|
+
Where a guardrail's shape follows an obvious convention (an `rm -rf` pattern looks like an `rm -rf`
|
|
481
497
|
pattern), that is two people meeting the same shell, not one copying the other.
|
|
482
498
|
|
|
483
499
|
## Part of AgentTrail
|
|
484
500
|
|
|
485
|
-
|
|
501
|
+
This library is part of [AgentTrail](https://www.agenttrail.sh), which builds guardrails and
|
|
486
502
|
observability for AI coding agents:
|
|
487
503
|
|
|
488
|
-
- **[AgentTrail Guard](https://www.agenttrail.sh/agenttrail-guard)** enforces
|
|
504
|
+
- **[AgentTrail Guard](https://www.agenttrail.sh/agenttrail-guard)** enforces these guardrails on every
|
|
489
505
|
tool call that reaches its hooks, on your machine. It is free, open source, and needs no
|
|
490
506
|
account. Its code is in [agenttrailhq/guard](https://github.com/agenttrailhq/guard).
|
|
491
|
-
- **[AgentTrail OS](https://www.agenttrail.sh/agenttrail-os)** uses the same open
|
|
507
|
+
- **[AgentTrail OS](https://www.agenttrail.sh/agenttrail-os)** uses the same open guardrail library,
|
|
492
508
|
and adds searchable session history and team workflows. See
|
|
493
509
|
[AgentTrail Guard vs AgentTrail OS](https://www.agenttrail.sh/guard-vs-os).
|
|
494
510
|
|
|
@@ -496,7 +512,7 @@ observability for AI coding agents:
|
|
|
496
512
|
|
|
497
513
|
Apache-2.0. See [LICENSE](./LICENSE).
|
|
498
514
|
|
|
499
|
-
|
|
500
|
-
If you rely on these
|
|
515
|
+
Guardrail ideas and reports go to [Guardrails issues](https://github.com/agenttrailhq/guardrails/issues).
|
|
516
|
+
If you rely on these guardrails, a star helps other developers find them.
|
|
501
517
|
|
|
502
518
|
Made with ❤️ by [agenttrail.sh](https://www.agenttrail.sh)
|