@agenttrail/guardrails 0.2.1 → 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 CHANGED
@@ -1,50 +1,122 @@
1
- <!-- cspell:words exfiltration kubeconfig -->
1
+ <!-- cspell:words exfiltration kubeconfig hostnames agenttrailhq OWASP slopsquatted -->
2
2
 
3
- # @agenttrail/guardrails
3
+ <div align="center">
4
4
 
5
- **A library of rules that spot dangerous commands before an AI coding agent runs them.**
5
+ <a href="https://www.agenttrail.sh">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="https://www.agenttrail.sh/brand/agenttrail_logo_white.svg" />
8
+ <img src="https://www.agenttrail.sh/brand/agenttrail_logo.svg" alt="AgentTrail" width="220" />
9
+ </picture>
10
+ </a>
6
11
 
7
- 74 rules, grouped into 11 packs. Apache-2.0.
12
+ # AgentTrail Guardrails: an open-source guardrail library for AI coding agents
8
13
 
9
- ---
14
+ **A library of guardrails for catching dangerous commands before an AI coding agent runs them.**<br />
15
+ Plain data you can read, test, and change. Enforced in **Claude Code**, **Cursor**, and **Codex CLI**.
16
+
17
+ [![CI](https://github.com/agenttrailhq/guardrails/actions/workflows/ci.yml/badge.svg)](https://github.com/agenttrailhq/guardrails/actions/workflows/ci.yml)
18
+ [![npm version](https://img.shields.io/npm/v/@agenttrail/guardrails?color=0748FE&label=npm)](https://www.npmjs.com/package/@agenttrail/guardrails)
19
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0748FE)](LICENSE)
20
+ [![Node.js 22.13+](https://img.shields.io/badge/node-%3E%3D22.13-0748FE)](https://nodejs.org/)
21
+
22
+ [Quick start](#quick-start) ·
23
+ [The packs](#the-eleven-packs) ·
24
+ [OWASP coverage](#owasp-coverage) ·
25
+ [Guardrail format](#a-guardrail-start-to-finish) ·
26
+ [Contribute a guardrail](#contributing-a-guardrail) ·
27
+ [Changelog](CHANGELOG.md)
28
+
29
+ </div>
30
+
31
+ [74 guardrails](https://www.agenttrail.sh/guardrails), grouped into 11 packs. Apache-2.0. Published
32
+ as `@agenttrail/guardrails` and enforced by [AgentTrail Guard](https://github.com/agenttrailhq/guard).
33
+
34
+ ![A Claude Code session: asked to split a name column and push the schema, the agent edits the Prisma schema and seed, then runs npx prisma db push --force-reset to wipe the database. AgentTrail Guard blocks it with the guardrail dd.accept-data-loss, and the agent stops](.github/assets/guard-blocks-force-reset.gif)
35
+
36
+ <sub>A real Claude Code session. The agent decides to reset the database to get a schema push
37
+ through; Guard blocks `prisma db push --force-reset` with `dd.accept-data-loss` before it runs.</sub>
10
38
 
11
39
  ## What this is, in plain terms
12
40
 
13
- When you let an AI coding agent work in your terminal, it eventually proposes something you would
14
- not have typed yourself — `git reset --hard` over a day's work, `rm -rf` on the wrong path, a
41
+ Your AI coding agent proposes commands in your terminal all day, and some are ones you would
42
+ not have typed yourself: `git reset --hard` over a day's work, `rm -rf` on the wrong path, a
15
43
  `terraform apply` against production.
16
44
 
17
45
  This package is the **list of things worth stopping**, written as data. Each entry says what to look
18
- for, how serious it is, and what should happen — allow it, ask a human first, or refuse.
46
+ for, how serious it is, and what should happen: allow it, ask a human first, or refuse.
19
47
 
20
48
  **That is all this package does.** It contains no code that watches your machine and nothing that
21
- talks to the network. It is a list. Something else has to read it and act on it — normally
49
+ talks to the network. Something else has to read the list and act on it: normally
22
50
  [`agenttrail-guard`](https://github.com/agenttrailhq/guard), which runs on your laptop and checks each
23
- command an agent proposes against these rules.
51
+ command an agent proposes against these guardrails.
52
+
53
+ ## Why these guardrails?
24
54
 
25
- Separating the two is deliberate. You can read every rule here, disagree with one, and change it,
26
- without trusting anything about the tool that enforces them.
55
+ - **Readable.** Every guardrail is plain data with a title, a severity, a default action, and a
56
+ description that says what it catches **and what it misses**.
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
+ either side does not build.
60
+ - **Quiet on purpose.** A commit message or a `grep` that only *mentions* `rm -rf /` is not
61
+ treated as running it. See [Talking about a command is not running it](#talking-about-a-command-is-not-running-it).
62
+ - **Mapped to known risks.** The packs line up with the
63
+ [OWASP Top 10s for agentic and LLM applications](#owasp-coverage), and the gaps are stated, not
64
+ hidden.
65
+ - **Separate from the enforcer.** You can read, disagree with, and change any guardrail without
66
+ trusting anything about the tool that enforces it.
27
67
 
28
- ## Who this is for
68
+ ## Quick start
29
69
 
30
- - **You use an AI coding agent** and want a sensible default set of guardrails rather than writing
31
- your own from scratch.
32
- - **You want to see exactly what is being blocked and why.** Every rule is plain data with a written
33
- explanation, including what it *misses*.
34
- - **You want to contribute a rule** you wish had existed. See [Contributing](#contributing-a-rule).
70
+ **You need:** Node.js 22.13 or newer.
35
71
 
36
- ## Install
72
+ ### Enforce the guardrails in your agent
73
+
74
+ Most people never install this package directly. [AgentTrail Guard](https://github.com/agenttrailhq/guard)
75
+ bundles it, so installing Guard is all it takes:
76
+
77
+ ```bash
78
+ npm install -g @agenttrail/guard
79
+ agenttrail-guard init --agent claude # or: --agent cursor, --agent codex
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
+ ```
85
+
86
+ See the [AgentTrail Guard README](https://github.com/agenttrailhq/guard#quick-start) for setup
87
+ and everything else Guard can do.
88
+
89
+ ### Use the guardrails in your own code
90
+
91
+ Install the package if you are writing guardrails, or building your own tool on top of the list:
37
92
 
38
93
  ```bash
39
94
  npm install @agenttrail/guardrails
40
95
  ```
41
96
 
42
- Most people never install this directly — `agenttrail-guard` bundles it. Install it yourself if you
43
- are writing rules, or building your own tool on top of the list.
97
+ ```ts
98
+ import { RULES, getRule, rulesForPack } from "@agenttrail/guardrails/guardrails";
99
+
100
+ // Everything that refuses outright, rather than asking or warning.
101
+ const blocking = RULES.filter((rule) => rule.defaultAction === "block");
102
+ console.log(`${RULES.length} guardrails, ${blocking.length} block by default`);
103
+
104
+ // One pack, with each guardrail's severity and default action.
105
+ for (const rule of rulesForPack("secret-exposure")) {
106
+ console.log(rule.id, rule.severity, rule.defaultAction);
107
+ }
108
+
109
+ // One guardrail, including the limits its description admits to.
110
+ console.log(getRule("wt.reset-hard")?.description);
111
+ ```
112
+
113
+ This package has no evaluator: it tells you what each guardrail looks for, not whether a given
114
+ command matches. Guard does the matching. [Two ways to import it](#two-ways-to-import-it)
115
+ explains the two entry points.
44
116
 
45
- ## A rule, start to finish
117
+ ## A guardrail, start to finish
46
118
 
47
- Here is a complete rule. Nothing is hidden; this is the actual shape.
119
+ Here is a complete guardrail. Nothing is hidden; this is the actual shape.
48
120
 
49
121
  ```jsonc
50
122
  {
@@ -53,7 +125,8 @@ Here is a complete rule. Nothing is hidden; this is the actual shape.
53
125
  "severity": "high",
54
126
  "defaultAction": "block",
55
127
  "title": "git reset --hard discards uncommitted work",
56
- "description": "Discards all uncommitted changes. Does not match `git restore` — see wt.restore-path.",
128
+ "description": "Discards all uncommitted changes. Does not match `git restore` (see wt.restore-path).",
129
+ "owasp": ["ASI02", "LLM03"],
57
130
 
58
131
  // What to look for. This one matches a Bash command against a regular expression.
59
132
  "match": {
@@ -62,7 +135,7 @@ Here is a complete rule. Nothing is hidden; this is the actual shape.
62
135
  ]
63
136
  },
64
137
 
65
- // Proof it works, in both directions — see "Every rule proves both directions" below.
138
+ // Proof it works, in both directions (see "Every guardrail proves both directions" below).
66
139
  "fixtures": {
67
140
  "block": ["git reset --hard"],
68
141
  "allow": ["git reset src/api.ts"]
@@ -70,48 +143,103 @@ Here is a complete rule. Nothing is hidden; this is the actual shape.
70
143
  }
71
144
  ```
72
145
 
146
+ The real guardrail is in [`wt-reset-hard.ts`](src/packs/working-tree/wt-reset-hard.ts); this is a
147
+ trimmed copy. With it enforced by Guard, an agent that proposes `git reset --hard` is refused
148
+ and told which guardrail stopped it, while `git reset src/api.ts`, which unstages one file, goes
149
+ ahead as normal.
150
+
73
151
  Reading the fields:
74
152
 
75
153
  | Field | What it means |
76
154
  |---|---|
77
- | `id` | A stable name. Users type it to disable or change a rule, so it never changes. |
78
- | `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. |
155
+ | `id` | A stable name. Users type it to disable or change a guardrail, so it never changes. |
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. |
79
157
  | `defaultAction` | What should happen: `block` (refuse), `require_approval` (ask a human), or `warn` (allow, but say so). A user can override it. |
80
- | `description` | What the rule catches **and what it misses**. The honest limits are part of the rule, not a footnote. |
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. |
81
160
  | `match` | The condition. `any_of` means "any one of these is enough". |
82
161
  | `fixtures` | Examples that must match, and examples that must not. |
83
162
 
84
163
  ## The eleven packs
85
164
 
86
- A rule is filed by **the harm it prevents**, never by the technique it uses to spot it.
165
+ A guardrail is filed by **the harm it prevents**, never by the technique it uses to spot it.
87
166
 
88
- That sounds like a detail and is not. The three rules about production config, `.env` files and API
89
- endpoints all work by matching file paths — but they are *not* in `file-scope`. Someone who turned
167
+ That sounds like a detail and is not. The three guardrails about production config, `.env` files and API
168
+ endpoints all work by matching file paths, but they are *not* in `file-scope`. Someone who turned
90
169
  that pack off to stop path noise would otherwise silently lose their production and secret
91
170
  protection, which they never asked to turn off and would not know they had.
92
171
 
93
- | Pack | Rules | What it is about |
172
+ | Pack | Guardrails | What it is about |
94
173
  |---|---:|---|
95
- | `working-tree` | 9 | Destroying uncommitted work or published history — `git reset --hard`, `git clean -fd`, force-push, `rm -rf`. |
96
- | `destructive-data` | 8 | Data git cannot bring back — a dropped volume, a dropped database, destructive DDL, a deleted shadow copy. |
97
- | `prod-infra` | 8 | Changing running infrastructure — Terraform, Kubernetes, Helm, cloud deletes, a deploy that names production. |
98
- | `secret-exposure` | 10 | Credentials and sensitive data leaving where they live. Mostly `warn`: reading a secret is a normal part of a normal day. |
99
- | `rce-supply-chain` | 6 | Running code nobody reviewed — pipe-to-shell, a remote runner, a redirected registry, TLS verification off. |
100
- | `safety-bypass` | 7 | Turning off a check somebody installed on purpose, or erasing the record of it — `--no-verify`, `--admin` merge, hooks disabled, host-key checking off, history and log purges, forged terminal output. |
101
- | `privilege-supply-chain` | 6 | Gaining reach or handing it out — `sudo` writes, `chmod 777`, IAM grants, persistence, publishing, new dependencies. |
102
- | `file-scope` | 4 | The agent wrote somewhere it had no business writing — its own config, the machine, git's internals, the CI definition. |
103
- | `agent-context` | 6 | The agent changing what it is or what it knows — its standing instructions, its memory, its skills and commands, its MCP servers — or starting more agents, or switching another agent's approvals off. |
104
- | `test-integrity` | 6 | The agent making its work look successful — deleting a test, weakening a runner's configuration, accepting every snapshot, switching a coverage gate off, silencing failures in bulk, or telling CI not to run. |
105
- | `exfiltration` | 4 | Moving data off the machine or opening a way in — a reverse shell, a public tunnel, a file upload, a paste service. Command channel only. |
174
+ | [`working-tree`](src/packs/working-tree/) | 9 | Destroying uncommitted work or published history: `git reset --hard`, `git clean -fd`, force-push, `rm -rf`. |
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. |
176
+ | [`prod-infra`](src/packs/prod-infra/) | 8 | Changing running infrastructure: Terraform, Kubernetes, Helm, cloud deletes, a deploy that names production. |
177
+ | [`secret-exposure`](src/packs/secret-exposure/) | 10 | Credentials and sensitive data leaving where they live. Mostly `warn`: reading a secret is a normal part of a normal day. |
178
+ | [`rce-supply-chain`](src/packs/rce-supply-chain/) | 6 | Running code nobody reviewed: pipe-to-shell, a remote runner, a redirected registry, TLS verification off. |
179
+ | [`safety-bypass`](src/packs/safety-bypass/) | 7 | Turning off a check somebody installed on purpose, or erasing the record of it: `--no-verify`, `--admin` merge, hooks disabled, host-key checking off, history and log purges, forged terminal output. |
180
+ | [`privilege-supply-chain`](src/packs/privilege-supply-chain/) | 6 | Gaining reach or handing it out: `sudo` writes, `chmod 777`, IAM grants, persistence, publishing, new dependencies. |
181
+ | [`file-scope`](src/packs/file-scope/) | 4 | The agent wrote somewhere it had no business writing: its own config, the machine, git's internals, the CI definition. |
182
+ | [`agent-context`](src/packs/agent-context/) | 6 | The agent changing what it is or what it knows (its standing instructions, its memory, its skills and commands, its MCP servers), or starting more agents, or switching another agent's approvals off. |
183
+ | [`test-integrity`](src/packs/test-integrity/) | 6 | The agent making its work look successful: deleting a test, weakening a runner's configuration, accepting every snapshot, switching a coverage gate off, silencing failures in bulk, or telling CI not to run. |
184
+ | [`exfiltration`](src/packs/exfiltration/) | 4 | Moving data off the machine or opening a way in: a reverse shell, a public tunnel, a file upload, a paste service. Command channel only. |
106
185
 
107
186
  Pack names appear in user config files, so renaming one is a breaking change, not a tidy-up.
108
187
 
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
190
+ [guardrail library](https://www.agenttrail.sh/guardrails) on the AgentTrail website, where you
191
+ can paste a command to see which guardrails match it.
192
+
193
+ ## OWASP coverage
194
+
195
+ Several packs exist because of risks named in OWASP's two lists for AI systems: the
196
+ [Top 10 for Agentic Applications](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/)
197
+ (ASI01 to ASI10, 2026 edition) and the
198
+ [Top 10 for LLM Applications](https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/)
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
201
+ command or a file change.
202
+
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
227
+ redirected the agent. They can only stop the action it led to.
228
+ - **Anything that needs memory across commands (LLM06, ASI08).** A guardrail sees one command at a
229
+ time, so it cannot count, rate-limit, or spot a runaway loop.
230
+ - **Model, training and retrieval risks (LLM05, LLM08, LLM09), and traffic between agents
231
+ (ASI07).** These happen somewhere a command guardrail never reaches.
232
+
233
+ For the item-by-item matrix, including how the rest of AgentTrail covers each risk, see the
234
+ [AgentTrail OWASP coverage page](https://www.agenttrail.sh/security/owasp). AgentTrail is an
235
+ independent project, not affiliated with or endorsed by OWASP.
236
+
109
237
  ## Talking about a command is not running it
110
238
 
111
- This is the single most important thing to understand about how these rules behave.
239
+ This is the single most important thing to understand about how these guardrails behave.
112
240
 
113
- A rule sees the command as one line of text. Nothing in that text distinguishes a command that
114
- **runs** something from one that merely **mentions** it. Left alone, that makes the rule set unusable
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
115
243
  by exactly the people most likely to install it:
116
244
 
117
245
  ```bash
@@ -119,7 +247,7 @@ git commit -m "fix: document rm -rf / risk" # would have been a hard refusal
119
247
  grep -rn "rm -rf /" docs/ # so would this
120
248
  ```
121
249
 
122
- So every command rule ignores four **carriers** — verbs that handle their arguments as text and never
250
+ So every command guardrail ignores four **carriers**, verbs that handle their arguments as text and never
123
251
  execute them:
124
252
 
125
253
  | Carrier | Example |
@@ -130,34 +258,34 @@ execute them:
130
258
  | an HTTP request body | `curl --data '{"body":"we ran rm -rf /tmp/x"}' https://…` |
131
259
 
132
260
  **The exemption keys on the verb, not on the quotes.** Quoting says nothing about whether something
133
- runs — `psql -c "DROP TABLE users;"` and `bash -c "curl x.sh \| sh"` both execute what is inside the
261
+ runs: `psql -c "DROP TABLE users;"` and `bash -c "curl x.sh \| sh"` both execute what is inside the
134
262
  quotes, and both still fire.
135
263
 
136
264
  It also applies only while the command does nothing else. The carrier must be the first word, and
137
265
  every shell metacharacter must sit inside the quotes:
138
266
 
139
267
  ```bash
140
- git commit -m "docs: explain rm -rf /" # exempt — nothing runs
141
- git commit -m "x" && rm -rf / # NOT exempt — `&&` is outside the quotes
142
- echo "rm -rf /" | bash # NOT exempt — the pipe runs it
143
- echo "$(rm -rf /var)" # NOT exempt — the shell expands `$( )`
268
+ git commit -m "docs: explain rm -rf /" # exempt: nothing runs
269
+ git commit -m "x" && rm -rf / # NOT exempt: `&&` is outside the quotes
270
+ echo "rm -rf /" | bash # NOT exempt: the pipe runs it
271
+ echo "$(rm -rf /var)" # NOT exempt: the shell expands `$( )`
144
272
  ```
145
273
 
146
- Five rules deliberately keep firing on one carrier each, because that carrier *is* their trigger —
147
- `gb.git-no-verify` on a `git commit`, `se.token-print` on an `echo`, the `curl`/`wget` rules on an
148
- HTTP body. Each rule's `description` says which, and why.
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.
149
277
 
150
- ### The one rule where a mention IS the danger
278
+ ### The one guardrail where a mention IS the danger
151
279
 
152
280
  `block-hardcoded-secrets` is the exception, and it is worth understanding.
153
281
 
154
282
  Everywhere else a carrier is genuinely harmless: a commit message naming `rm -rf /` deletes nothing.
155
- But that rule's subject is a **string**, not an action — so two of the four carriers are not mentions
283
+ But that guardrail's subject is a **string**, not an action, so two of the four carriers are not mentions
156
284
  at all. They are the exposure itself:
157
285
 
158
286
  | Carrier | What happens to the key | Exempt? |
159
287
  |---|---|---|
160
- | `grep -rn AKIA .` | searched for, goes nowhere — and this is how you find a key to rotate | **yes** |
288
+ | `grep -rn AKIA .` | searched for, goes nowhere, and this is how you find a key to rotate | **yes** |
161
289
  | `echo "AKIA…"` | transient terminal output | **yes** |
162
290
  | `git commit -m "…AKIA…"` | written into history, then pushed | **no** |
163
291
  | `curl --data "…AKIA…"` | sent to a remote host | **no** |
@@ -165,61 +293,43 @@ at all. They are the exposure itself:
165
293
  The cost, stated in the other direction: documenting a real-looking key in a commit message is still
166
294
  blocked. Redact the body of the key, or use a placeholder short enough to fail the length check.
167
295
 
168
- **Known limits of the carrier logic**, in the same spirit as the rest of this file. The carrier must
169
- be the first word — a single leading `sudo` is tolerated, because it changes privilege rather than
170
- meaning, but a runner prefix is not: `pnpm exec rg …`, `npx …` and `xargs -0 grep …` still fire,
171
- since "some program eventually runs a search" is a much weaker claim than "this command is a search".
172
- At most four quoted arguments are recognised. A carrier that can be made to execute through a flag —
173
- `ack --pager='…'`, `rg --pre <cmd>` — is still treated as a mention; closing that needs information
174
- the checker does not have. And an MCP tool whose input carries the same text is not exempt either,
175
- because exempting a JSON blob would exempt a shell-running MCP server along with it.
176
-
177
- **MCP coverage.** A command rule fires on `Bash`, `PowerShell` *and* any `mcp__*` tool: the guard hands
178
- the checker an MCP call's serialized `tool_input` as the same command text every command rule reads, so
179
- a command shape run through an MCP server — `{"command":"rm -rf /"}` — is caught, not ignored. Two
180
- honest limits follow from that. First, a rule whose pattern is anchored to the start of the command
181
- (`^…` or a command-position class) may not fire inside the JSON, where the shape sits after a `"`
182
- rather than at a command boundary; the `\b`-anchored rules — most of the corpus — do fire. Second, the
183
- quoted-mention exemptions are shell-only, so an MCP payload that merely *names* a command in a text
184
- field (`{"title":"fix the rm -rf / bug"}`) is matched the same as one that runs it — a JSON blob cannot
185
- be told apart from a shell-running MCP server. File rules match by path on whichever file tool a client
186
- uses. No rule is shell-only by design; a rule that does not reach the MCP channel does so because its
187
- pattern, not its label, does not match the serialized shape.
188
-
189
- ## What these rules deliberately do not catch
296
+ The finer limits of the carrier logic, and how command guardrails apply to MCP tools, are in the
297
+ [Reference](#reference).
298
+
299
+ ## What these guardrails deliberately do not catch
190
300
 
191
301
  Stated here rather than discovered later. Every one is a real limit of the format, not something
192
302
  somebody forgot.
193
303
 
194
- - **Nothing about the web.** Pages an agent fetches are not checked, and there are no URL rules.
304
+ - **Nothing about the web.** Pages an agent fetches are not checked, and there are no URL guardrails.
195
305
  - **Nothing inside a file.** The checker sees a file's *path*, never its contents. A secret typed into
196
- a source file, SQL built by string concatenation, a missing auth check — none of it is visible.
197
- Rules that would need it are absent rather than approximated.
306
+ a source file, SQL built by string concatenation, a missing auth check: none of it is visible.
307
+ Guardrails that would need it are absent rather than approximated.
198
308
  - **Nothing about where you are.** No working directory, no project root, no git branch, no cloud
199
- profile reaches the checker — it gets one command and nothing else. So "the agent wrote outside the
200
- project" **cannot be written as a rule**, and `file-scope` is limited to well-known absolute paths
201
- for good. For the same reason, a rule cannot tell a scratch database from a production one.
202
- - **Nothing hidden inside a quoted payload.** Where the danger is inside a quoted argument —
203
- `psql -c "<sql>"`, `python -c "<code>"` — a text rule can only guess. In a long script, a match says
204
- very little about what the script actually does.
309
+ profile reaches the checker; it gets one command and nothing else. So "the agent wrote outside the
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.
312
+ - **Nothing hidden inside a quoted payload.** Where the danger is inside a quoted argument
313
+ (`psql -c "<sql>"`, `python -c "<code>"`), a text guardrail can only guess. In a long script, a match
314
+ says very little about what the script actually does.
205
315
  - **Nothing a wrapper hides.** `./deploy.sh` that runs `terraform apply -auto-approve` inside it is
206
316
  just a shell script from the outside.
207
317
  - **Nothing recurring.** There is no counting. "The same mistake three times this week" needs memory
208
318
  across commands, and a single command has none.
209
319
 
210
- Each rule's own `description` names its specific misses. Read those before trusting a rule to cover a
211
- case — they are written to be believed, not to sell.
320
+ Each guardrail's own `description` names its specific misses. Read those before trusting a guardrail to cover a
321
+ case; they are written to be believed, not to sell.
212
322
 
213
- ## Every rule proves both directions
323
+ ## Every guardrail proves both directions
214
324
 
215
- Every rule ships at least one **`block`** example and at least one **`allow`** example. A rule missing
325
+ Every guardrail ships at least one **`block`** example and at least one **`allow`** example. A guardrail missing
216
326
  either does not build.
217
327
 
218
- - **`block` means "this rule must match."** It does *not* mean the agent is refused — most packs
328
+ - **`block` means "this guardrail must match."** It does *not* mean the agent is refused; most packs
219
329
  default to asking or warning.
220
- - **`allow` means "this rule must NOT match."** *This is the half that matters.* Anyone can write a
221
- rule that catches `rm -rf /`. The hard part is not firing on `rm -rf ./node_modules` forty times a
222
- day, and a rule with no negative example has not shown it can tell them apart.
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.
223
333
 
224
334
  Examples come in two kinds, and each must use the right one:
225
335
 
@@ -228,13 +338,13 @@ Examples come in two kinds, and each must use the right one:
228
338
  | a command | `Bash`, `PowerShell`, a search query, an MCP tool's input |
229
339
  | a file path | `Edit`, `Write`, `Read`, `MultiEdit`, `NotebookEdit` |
230
340
 
231
- Giving a path-matching rule a command example makes it pass **without testing anything** — it matches
232
- nothing, which reads as proof of quietness and proves only that the path never reached the rule. CI
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
233
343
  rejects that.
234
344
 
235
- ## Contributing a rule
345
+ ## Contributing a guardrail
236
346
 
237
- Rules are meant to be contributed. The bar is not "clever regex" — it is **does it fire on the real
347
+ Guardrails are meant to be contributed. The bar is not "clever regex"; it is **does it fire on the real
238
348
  thing, and stay quiet on the near-miss**.
239
349
 
240
350
  **1. Write it**, following the shape above. Give it a `description` that says what it misses.
@@ -251,65 +361,158 @@ if (!result.success) console.error(result.error.issues);
251
361
  Or, if you have the guard installed:
252
362
 
253
363
  ```bash
254
- agenttrail-guard guardrails validate ./my-rule.json
364
+ agenttrail-guard guardrails validate ./my-guardrail.json
255
365
  ```
256
366
 
257
- **3. Understand what that does and does not tell you.** It answers *"is this a well-formed rule?"* It
367
+ **3. Understand what that does and does not tell you.** It answers *"is this a well-formed guardrail?"* It
258
368
  does **not** answer *"does it actually fire on the command I think it does?"* That needs the real
259
369
  checker, which is not part of this package.
260
370
 
261
- **So the real test runs in CI, on your pull request** — the same check, on the same machine, for
371
+ **So the real test runs in CI, on your pull request**: the same check, on the same machine, for
262
372
  everyone. You get the shape check instantly here and the real answer there, which is where it has to
263
373
  run to be trusted anyway.
264
374
 
265
- Your rule is also run against a **quiet corpus**: 328 everyday commands and paths that no rule may
266
- match at all. Your own negative example only proves your rule is quiet on the near-miss *you* thought
267
- of. The quiet corpus is what catches a Terraform rule firing on `pnpm test`.
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`.
268
378
 
269
379
  ### Shapes that will not validate
270
380
 
271
- Three are rejected outright, each because it produces a rule that *looks* enforced and is not — the
381
+ Three are rejected outright, each because it produces a guardrail that *looks* enforced and is not: the
272
382
  worst failure a security tool can have.
273
383
 
274
384
  | Rejected | Why |
275
385
  |---|---|
276
- | `scope` | It compares against ids that are always UUIDs, never a vendor name — so a scoped rule matches nothing, forever, silently. |
386
+ | `scope` | It compares against ids that are always UUIDs, never a vendor name, so a scoped guardrail matches nothing, forever, silently. |
277
387
  | Numeric conditions | Token counts and durations are all zero *before* a command runs. "Greater than" can never fire; "less than" fires on everything. |
278
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`. |
279
389
 
280
390
  A regular expression is also rejected if it nests unbounded repetition (`(a+)+`). The guard fails
281
- **open** under a time limit, so a pattern that backtracks does not merely run slowly — it lets the
391
+ **open** under a time limit, so a pattern that backtracks does not merely run slowly; it lets the
282
392
  command through.
283
393
 
284
394
  One more is caught in CI rather than by the shape check, because it cannot be caught earlier: **a
285
- single-item brace list in a tool name.** `"{Bash}"` is a glob pattern, and it does not match `Bash` —
286
- so the rule matches nothing, forever, with no error anywhere. Write a single tool plainly as
395
+ single-item brace list in a tool name.** `"{Bash}"` is a glob pattern, and it does not match `Bash`,
396
+ so the guardrail matches nothing, forever, with no error anywhere. Write a single tool plainly as
287
397
  `"Bash"`; braces are for real alternatives, `"{Bash,PowerShell}"`.
288
398
 
289
- ## Two ways to import it
399
+ ### Ready to open a pull request?
400
+
401
+ [CONTRIBUTING.md](CONTRIBUTING.md) has a pre-submission checklist, local build and test
402
+ commands, and where to report what. Please follow the [Code of Conduct](CODE_OF_CONDUCT.md),
403
+ and report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
404
+
405
+ ## Stability and versioning
406
+
407
+ Guardrail ids and pack names end up in people's config files, so they are treated as public API:
408
+
409
+ - **A guardrail id never changes.** Renaming or removing a guardrail would silently undo an override
410
+ someone set on it, so either one is a breaking change.
411
+ - **A pack name never changes** either, for the same reason: a renamed pack silently
412
+ re-enables a pack someone had turned off.
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.
415
+
416
+ Releases are cut automatically from [Conventional Commits](https://www.conventionalcommits.org/).
417
+ Before 1.0, a breaking change ships as a minor release and a new guardrail as a patch release, so
418
+ skim the [changelog](CHANGELOG.md) before you upgrade.
419
+
420
+ ## FAQ
421
+
422
+ **Can I use these guardrails without Guard?** Yes. They are plain data under Apache-2.0. You need your
423
+ own matcher to apply them, because this package does not contain one.
424
+
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
427
+ off. Then please [open an issue](https://github.com/agenttrailhq/guardrails/issues) with a
428
+ sanitized example, so the guardrail can learn the near-miss.
429
+
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).
433
+
434
+ **Why is `secret-exposure` mostly `warn`?** Reading a secret is a normal part of a normal day.
435
+ Refusing every `.env` read would be turned off within a week. Moving a secret off the machine is
436
+ held for approval instead.
437
+
438
+ **How do I turn off a whole pack?** `agenttrail-guard guardrails disable <pack>`. See
439
+ [Tune a guardrail](https://github.com/agenttrailhq/guard#tune-a-guardrail) in the Guard README.
440
+
441
+ ## Reference
442
+
443
+ ### Two ways to import it
290
444
 
291
445
  ```ts
292
- import { RULES, getRule } from "@agenttrail/guardrails/guardrails"; // just the rules
293
- import { parseRule } from "@agenttrail/guardrails"; // rules + the shape checker
446
+ import { RULES, getRule } from "@agenttrail/guardrails/guardrails"; // just the guardrails
447
+ import { parseRule } from "@agenttrail/guardrails"; // guardrails + the shape checker
294
448
  ```
295
449
 
296
- Use the first when you want to *apply* rules, and the second when you want to *validate* one you are
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
297
454
  writing.
298
455
 
299
456
  They are separate because the guard starts a fresh process on **every single command** an agent runs,
300
457
  under a ten-second ceiling. It cannot afford to load a validator it never calls, or to re-check 74
301
- rules that were already checked before release.
458
+ guardrails that were already checked before release.
459
+
460
+ ### Known limits of the carrier logic
461
+
462
+ In the same spirit as the rest of this file:
463
+
464
+ - **The carrier must be the first word.** A single leading `sudo` is tolerated, because it changes
465
+ privilege rather than meaning, but a runner prefix is not: `pnpm exec rg …`, `npx …` and
466
+ `xargs -0 grep …` still fire, since "some program eventually runs a search" is a much weaker claim
467
+ than "this command is a search".
468
+ - **At most four quoted arguments are recognised.**
469
+ - **A carrier that can be made to execute through a flag** (`ack --pager='…'`, `rg --pre <cmd>`) is
470
+ still treated as a mention; closing that needs information the checker does not have.
471
+ - **An MCP tool whose input carries the same text is not exempt either,** because exempting a JSON
472
+ blob would exempt a shell-running MCP server along with it.
473
+
474
+ ### MCP coverage
475
+
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
478
+ shape run through an MCP server (`{"command":"rm -rf /"}`) is caught, not ignored. Two honest limits
479
+ follow from that:
302
480
 
303
- ## Where these rules came from
481
+ 1. **Start-anchored patterns may miss.** A guardrail whose pattern is anchored to the start of the command
482
+ (`^…` or a command-position class) may not fire inside the JSON, where the shape sits after a `"`
483
+ rather than at a command boundary; the `\b`-anchored guardrails (most of the corpus) do fire.
484
+ 2. **Mentions are not exempt.** The quoted-mention exemptions are shell-only, so an MCP payload that
485
+ merely *names* a command in a text field (`{"title":"fix the rm -rf / bug"}`) is matched the same
486
+ as one that runs it; a JSON blob cannot be told apart from a shell-running MCP server.
487
+
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
490
+ the serialized shape.
491
+
492
+ ### Where these guardrails came from
304
493
 
305
494
  Independently authored. No block list, pattern or wording is copied from any other project.
306
495
 
307
- Where a rule's shape follows an obvious convention — an `rm -rf` pattern looks like an `rm -rf`
308
- pattern — that is two people meeting the same shell, not one copying the other.
496
+ Where a guardrail's shape follows an obvious convention (an `rm -rf` pattern looks like an `rm -rf`
497
+ pattern), that is two people meeting the same shell, not one copying the other.
498
+
499
+ ## Part of AgentTrail
309
500
 
310
- Four vendors' own tools inspired specific rules through their *documented failure modes*, not their
311
- code.
501
+ This library is part of [AgentTrail](https://www.agenttrail.sh), which builds guardrails and
502
+ observability for AI coding agents:
503
+
504
+ - **[AgentTrail Guard](https://www.agenttrail.sh/agenttrail-guard)** enforces these guardrails on every
505
+ tool call that reaches its hooks, on your machine. It is free, open source, and needs no
506
+ account. Its code is in [agenttrailhq/guard](https://github.com/agenttrailhq/guard).
507
+ - **[AgentTrail OS](https://www.agenttrail.sh/agenttrail-os)** uses the same open guardrail library,
508
+ and adds searchable session history and team workflows. See
509
+ [AgentTrail Guard vs AgentTrail OS](https://www.agenttrail.sh/guard-vs-os).
312
510
 
313
511
  ## License
314
512
 
315
513
  Apache-2.0. See [LICENSE](./LICENSE).
514
+
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.
517
+
518
+ Made with ❤️ by [agenttrail.sh](https://www.agenttrail.sh)