@agenttrail/guardrails 0.0.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,944 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * The guardrails rule format — self-contained, and deliberately so.
5
+ *
6
+ * ── Why this file imports nothing from the monorepo ──────────────────────────
7
+ *
8
+ * `@agenttrail/shared` and `@agenttrail/policy-engine` are both `private: true`.
9
+ * A public package whose source reads `import { … } from "@agenttrail/shared"`
10
+ * does not build for anyone outside this repo, and open source whose source does
11
+ * not compile is the first thing this audience checks
12
+ * (AGENTTRAIL-GUARD-SPEC.md 2.1).
13
+ *
14
+ * So this file mirrors the engine's `MatchCondition` / `Match` rather than
15
+ * importing them, and `@agenttrail/guardrails` declares NO dependency on either
16
+ * package — not even a dev one. That is not a convention anyone has to remember:
17
+ * pnpm does not hoist workspace packages, so a stray import here fails to
18
+ * resolve at typecheck and build time. A `MODULE_NOT_FOUND` cannot be argued
19
+ * with, and a lint rule over source text can — this repo has already had comment
20
+ * prose parsed as an import.
21
+ *
22
+ * The price of owning a second copy is drift, so a parity test that lives with
23
+ * the policy engine pins this schema to the engine's own:
24
+ * `guardrails-schema-parity.test.ts`.
25
+ * It is not optional and not redundant. Read it before changing anything here.
26
+ *
27
+ * ── What this is NOT ─────────────────────────────────────────────────────────
28
+ *
29
+ * This is not the engine's `PolicyPredicate`. That schema is `.strict()` with a
30
+ * required `version` and `action`, so it rejects 100% of this rule format — it
31
+ * is the schema of the `match` SUB-OBJECT, not of a rule. A rule carries
32
+ * `defaultAction` (what the guard does when it matches) and no `version`; the
33
+ * package version is the corpus version.
34
+ */
35
+
36
+ /**
37
+ * All five product severities, in order, most→least severe.
38
+ *
39
+ * This tuple mirrors `shared/src/types/common.ts` `Severity` and `risk.ts`
40
+ * `RISK_SEVERITIES` exactly, and the parity test pins them together so they
41
+ * cannot drift. Matching the product means the hosted catalog layers its own
42
+ * metadata by rule id with no translation table in between (spec 8.3).
43
+ *
44
+ * `info` earns its place: five of the eight packs default to `ask`/`warn`, and a
45
+ * warn-only rule labelled `low` overstates itself.
46
+ *
47
+ * ── Sharing a vocabulary with a pricing table is not permission to price ─────
48
+ *
49
+ * These same five values key `DEFAULT_PER_INCIDENT_CENTS` in the private
50
+ * `risk.ts` — `critical` is $160,000. Because the tuples now line up perfectly,
51
+ * joining severity to money is one line. **Do not.** The guard prints no
52
+ * currency anywhere (Appendix 0 #12, spec 9.1), and this package therefore ships
53
+ * NO mapping from severity to money. The reason is on the record: a slightly
54
+ * loose rule once produced the headline "rm -rf seen 17 times, $5,070,000",
55
+ * built entirely out of a test suite deleting its own scratch folders.
56
+ */
57
+ declare const SEVERITIES: readonly ["critical", "high", "medium", "low", "info"];
58
+ /** How dangerous the thing a rule catches is. An authoring signal, not a price. */
59
+ declare const SeveritySchema: z.ZodEnum<["critical", "high", "medium", "low", "info"]>;
60
+ /**
61
+ * What the guard does when a rule matches.
62
+ *
63
+ * The guard maps these onto Claude Code's `PreToolUse` verdicts:
64
+ * `block` → deny, `require_approval` → ask, `warn` → allow-but-recorded.
65
+ */
66
+ declare const ACTIONS: readonly ["block", "require_approval", "warn"];
67
+ /** The action a rule takes by default, before any user override. */
68
+ declare const ActionSchema: z.ZodEnum<["block", "require_approval", "warn"]>;
69
+ /** Longest accepted `detail_matches` pattern. Long enough for any real rule. */
70
+ declare const DETAIL_MATCHES_MAX_PATTERN_LENGTH = 200;
71
+ /** Most `detail_matches` patterns allowed on one condition (they are ORed). */
72
+ declare const DETAIL_MATCHES_MAX_PATTERNS = 10;
73
+ /**
74
+ * Reject a regex that can backtrack catastrophically, at PARSE time.
75
+ *
76
+ * Mirrored from the engine's `hasNestedUnboundedQuantifier`, and pinned to it by
77
+ * the parity test — including behavior over a shared pattern corpus, not just
78
+ * the fact that a function exists.
79
+ *
80
+ * Node has no regex timeout, so the only real bounds are a non-backtracking
81
+ * engine (RE2 — a runtime dependency deliberately not taken) or a syntactic
82
+ * restriction. This is the syntactic one: it rejects **star height >= 2**, an
83
+ * unbounded quantifier applied to a group that itself contains an unbounded
84
+ * quantifier — `(a+)+`, `(a*)*`, `(\s+x?)+` — the shape behind essentially every
85
+ * practical ReDoS.
86
+ *
87
+ * This matters more here than it does on the server. The guard's hook has a 10s
88
+ * Claude Code timeout, no internal watchdog, and it **fails open**: a pattern
89
+ * that backtracks does not merely make enforcement slow, it lets the action
90
+ * through. A slow rule on the hosted PDP is a slow rule; a slow rule in the
91
+ * guard is a disabled rule.
92
+ *
93
+ * HONEST LIMIT: this is a bound, not a proof. Star height 1 can still be
94
+ * quadratic (`(a|a)*`), which is slow but not exponential, and is acceptable
95
+ * against the length cap above. A hard guarantee needs RE2.
96
+ *
97
+ * @param source - The regex body (no delimiters, no flags).
98
+ * @returns `true` when the pattern nests unbounded quantifiers.
99
+ */
100
+ declare function hasNestedUnboundedQuantifier(source: string): boolean;
101
+ /**
102
+ * One condition matched against a tool call.
103
+ *
104
+ * - `kind`: required. The span kind, e.g. `execute_tool`. Case-insensitive.
105
+ * - `label`: optional. The tool name — a case-insensitive GLOB, so
106
+ * `mcp__chrome__*` and `{Bash,PowerShell}` are one condition rather than a
107
+ * list that rots. A plain label has no glob character and matches exactly.
108
+ * - `detail_contains`: optional. Substrings that must ALL appear (AND).
109
+ * **CASE-SENSITIVE**, and deliberately: `AKIA`, `ghp_`, `sk_live_` and the
110
+ * upper-case `TRUNCATE` arm are rules where case IS the signal. Author
111
+ * case-insensitive text matches with `detail_matches` instead.
112
+ * - `detail_matches`: optional. Regex patterns against the same text; at least
113
+ * one must match (OR, unlike `detail_contains`). Case-insensitive.
114
+ * - `file_glob`: optional. Case-insensitive glob against the file path.
115
+ *
116
+ * ── Two engine fields are deliberately absent ────────────────────────────────
117
+ *
118
+ * `numeric` is BANNED (spec 7.4). At guard decision time `tokens`,
119
+ * `cachedTokens` and `durationMs` are all 0, because the action has not run yet.
120
+ * `gt` is therefore always false — the rule can never fire — and, worse, **`lt`
121
+ * is always true, so the rule fires on every single command.** Omitting the key
122
+ * from a `.strict()` object makes that a parse error rather than a lint finding.
123
+ *
124
+ * `scope` is BANNED for the same class of reason and is absent from the rule
125
+ * envelope below: `agent_in` compares literally against an agent id that is
126
+ * always a UUID, never the string `"claude-code"`, so a scoped rule matches
127
+ * nothing, forever, silently.
128
+ *
129
+ * ── The third ban needs an actual check ──────────────────────────────────────
130
+ *
131
+ * A command matcher (`detail_contains` / `detail_matches`) and a file matcher
132
+ * (`file_glob`) in ONE condition can never both be satisfied: the guard's mapper
133
+ * is an if/else chain, so no real tool call ever carries both a command and a
134
+ * file path. The engine's schema permits the combination; this one refuses it,
135
+ * which is a deliberate divergence the parity test knows about.
136
+ */
137
+ declare const MatchConditionSchema: z.ZodEffects<z.ZodObject<{
138
+ kind: z.ZodString;
139
+ label: z.ZodOptional<z.ZodString>;
140
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
141
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
142
+ file_glob: z.ZodOptional<z.ZodString>;
143
+ }, "strict", z.ZodTypeAny, {
144
+ kind: string;
145
+ label?: string | undefined;
146
+ detail_contains?: string[] | undefined;
147
+ detail_matches?: string[] | undefined;
148
+ file_glob?: string | undefined;
149
+ }, {
150
+ kind: string;
151
+ label?: string | undefined;
152
+ detail_contains?: string[] | undefined;
153
+ detail_matches?: string[] | undefined;
154
+ file_glob?: string | undefined;
155
+ }>, {
156
+ kind: string;
157
+ label?: string | undefined;
158
+ detail_contains?: string[] | undefined;
159
+ detail_matches?: string[] | undefined;
160
+ file_glob?: string | undefined;
161
+ }, {
162
+ kind: string;
163
+ label?: string | undefined;
164
+ detail_contains?: string[] | undefined;
165
+ detail_matches?: string[] | undefined;
166
+ file_glob?: string | undefined;
167
+ }>;
168
+ /**
169
+ * Match composition — conditions combined with `any_of` (OR), `all_of` (AND)
170
+ * and/or `none_of` (negation).
171
+ *
172
+ * At least one of `any_of` or `all_of` must be present. `none_of` is an optional
173
+ * refinement that CANNOT stand alone: a match must positively select calls
174
+ * before excluding some. A pure-negation match is satisfied by every
175
+ * non-matching call and, at `block`, would deny everything the agent does.
176
+ */
177
+ declare const MatchSchema: z.ZodEffects<z.ZodObject<{
178
+ any_of: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
179
+ kind: z.ZodString;
180
+ label: z.ZodOptional<z.ZodString>;
181
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
182
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
183
+ file_glob: z.ZodOptional<z.ZodString>;
184
+ }, "strict", z.ZodTypeAny, {
185
+ kind: string;
186
+ label?: string | undefined;
187
+ detail_contains?: string[] | undefined;
188
+ detail_matches?: string[] | undefined;
189
+ file_glob?: string | undefined;
190
+ }, {
191
+ kind: string;
192
+ label?: string | undefined;
193
+ detail_contains?: string[] | undefined;
194
+ detail_matches?: string[] | undefined;
195
+ file_glob?: string | undefined;
196
+ }>, {
197
+ kind: string;
198
+ label?: string | undefined;
199
+ detail_contains?: string[] | undefined;
200
+ detail_matches?: string[] | undefined;
201
+ file_glob?: string | undefined;
202
+ }, {
203
+ kind: string;
204
+ label?: string | undefined;
205
+ detail_contains?: string[] | undefined;
206
+ detail_matches?: string[] | undefined;
207
+ file_glob?: string | undefined;
208
+ }>, "many">>;
209
+ all_of: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
210
+ kind: z.ZodString;
211
+ label: z.ZodOptional<z.ZodString>;
212
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
213
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
214
+ file_glob: z.ZodOptional<z.ZodString>;
215
+ }, "strict", z.ZodTypeAny, {
216
+ kind: string;
217
+ label?: string | undefined;
218
+ detail_contains?: string[] | undefined;
219
+ detail_matches?: string[] | undefined;
220
+ file_glob?: string | undefined;
221
+ }, {
222
+ kind: string;
223
+ label?: string | undefined;
224
+ detail_contains?: string[] | undefined;
225
+ detail_matches?: string[] | undefined;
226
+ file_glob?: string | undefined;
227
+ }>, {
228
+ kind: string;
229
+ label?: string | undefined;
230
+ detail_contains?: string[] | undefined;
231
+ detail_matches?: string[] | undefined;
232
+ file_glob?: string | undefined;
233
+ }, {
234
+ kind: string;
235
+ label?: string | undefined;
236
+ detail_contains?: string[] | undefined;
237
+ detail_matches?: string[] | undefined;
238
+ file_glob?: string | undefined;
239
+ }>, "many">>;
240
+ none_of: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
241
+ kind: z.ZodString;
242
+ label: z.ZodOptional<z.ZodString>;
243
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
244
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
245
+ file_glob: z.ZodOptional<z.ZodString>;
246
+ }, "strict", z.ZodTypeAny, {
247
+ kind: string;
248
+ label?: string | undefined;
249
+ detail_contains?: string[] | undefined;
250
+ detail_matches?: string[] | undefined;
251
+ file_glob?: string | undefined;
252
+ }, {
253
+ kind: string;
254
+ label?: string | undefined;
255
+ detail_contains?: string[] | undefined;
256
+ detail_matches?: string[] | undefined;
257
+ file_glob?: string | undefined;
258
+ }>, {
259
+ kind: string;
260
+ label?: string | undefined;
261
+ detail_contains?: string[] | undefined;
262
+ detail_matches?: string[] | undefined;
263
+ file_glob?: string | undefined;
264
+ }, {
265
+ kind: string;
266
+ label?: string | undefined;
267
+ detail_contains?: string[] | undefined;
268
+ detail_matches?: string[] | undefined;
269
+ file_glob?: string | undefined;
270
+ }>, "many">>;
271
+ }, "strict", z.ZodTypeAny, {
272
+ any_of?: {
273
+ kind: string;
274
+ label?: string | undefined;
275
+ detail_contains?: string[] | undefined;
276
+ detail_matches?: string[] | undefined;
277
+ file_glob?: string | undefined;
278
+ }[] | undefined;
279
+ all_of?: {
280
+ kind: string;
281
+ label?: string | undefined;
282
+ detail_contains?: string[] | undefined;
283
+ detail_matches?: string[] | undefined;
284
+ file_glob?: string | undefined;
285
+ }[] | undefined;
286
+ none_of?: {
287
+ kind: string;
288
+ label?: string | undefined;
289
+ detail_contains?: string[] | undefined;
290
+ detail_matches?: string[] | undefined;
291
+ file_glob?: string | undefined;
292
+ }[] | undefined;
293
+ }, {
294
+ any_of?: {
295
+ kind: string;
296
+ label?: string | undefined;
297
+ detail_contains?: string[] | undefined;
298
+ detail_matches?: string[] | undefined;
299
+ file_glob?: string | undefined;
300
+ }[] | undefined;
301
+ all_of?: {
302
+ kind: string;
303
+ label?: string | undefined;
304
+ detail_contains?: string[] | undefined;
305
+ detail_matches?: string[] | undefined;
306
+ file_glob?: string | undefined;
307
+ }[] | undefined;
308
+ none_of?: {
309
+ kind: string;
310
+ label?: string | undefined;
311
+ detail_contains?: string[] | undefined;
312
+ detail_matches?: string[] | undefined;
313
+ file_glob?: string | undefined;
314
+ }[] | undefined;
315
+ }>, {
316
+ any_of?: {
317
+ kind: string;
318
+ label?: string | undefined;
319
+ detail_contains?: string[] | undefined;
320
+ detail_matches?: string[] | undefined;
321
+ file_glob?: string | undefined;
322
+ }[] | undefined;
323
+ all_of?: {
324
+ kind: string;
325
+ label?: string | undefined;
326
+ detail_contains?: string[] | undefined;
327
+ detail_matches?: string[] | undefined;
328
+ file_glob?: string | undefined;
329
+ }[] | undefined;
330
+ none_of?: {
331
+ kind: string;
332
+ label?: string | undefined;
333
+ detail_contains?: string[] | undefined;
334
+ detail_matches?: string[] | undefined;
335
+ file_glob?: string | undefined;
336
+ }[] | undefined;
337
+ }, {
338
+ any_of?: {
339
+ kind: string;
340
+ label?: string | undefined;
341
+ detail_contains?: string[] | undefined;
342
+ detail_matches?: string[] | undefined;
343
+ file_glob?: string | undefined;
344
+ }[] | undefined;
345
+ all_of?: {
346
+ kind: string;
347
+ label?: string | undefined;
348
+ detail_contains?: string[] | undefined;
349
+ detail_matches?: string[] | undefined;
350
+ file_glob?: string | undefined;
351
+ }[] | undefined;
352
+ none_of?: {
353
+ kind: string;
354
+ label?: string | undefined;
355
+ detail_contains?: string[] | undefined;
356
+ detail_matches?: string[] | undefined;
357
+ file_glob?: string | undefined;
358
+ }[] | undefined;
359
+ }>;
360
+ /**
361
+ * One fixture: a tool call this rule must match, or must not.
362
+ *
363
+ * A bare string is shorthand for `{ tool: "Bash", command: "<string>" }`, which
364
+ * covers most rules and keeps them terse. Anything else is an object with a
365
+ * `tool` plus **exactly one** of `command` or `file_path` — the two channels in
366
+ * spec 4.1. `.strict()` on each variant is what makes "exactly one" true: a
367
+ * `command` object carrying a `file_path` matches neither variant.
368
+ *
369
+ * The parse output is always normalized to the object form, so the harness has
370
+ * exactly one shape to feed the evaluator.
371
+ *
372
+ * ── There is no `url` channel, and that is load-bearing ──────────────────────
373
+ *
374
+ * Spec 7.2 permitted `{ tool: "WebFetch", url: … }` until 2026-09-07. It was
375
+ * removed (PR #532) because **the engine cannot see a URL**: the only two
376
+ * attribute keys any matcher reads are `detail` and `file_path`, and there is no
377
+ * `url` field in the condition schema. A value written to `url` is read by
378
+ * nothing, so a `WebFetch` rule would have matched nothing, forever, silently —
379
+ * exactly the failure `scope` is banned for.
380
+ *
381
+ * Aliasing the URL onto the command channel (which the paid PEP does) was
382
+ * rejected: it ships a channel whose rules read as commands, and the guard's
383
+ * honesty about its own coverage is the product. **v1 ships no website rules**,
384
+ * `WebFetch` is not intercepted, and the README says so rather than papering
385
+ * over it. `WebSearch` stays, because its query really is text on the command
386
+ * channel.
387
+ *
388
+ * A `url` key therefore fails to parse — it does not silently become a fixture
389
+ * that tests nothing. `__tests__/schema.test.ts` pins that, because someone
390
+ * working from a cached reading of the old spec will write exactly it.
391
+ */
392
+ declare const FixtureSchema: z.ZodUnion<[z.ZodEffects<z.ZodString, {
393
+ tool: "Bash";
394
+ command: string;
395
+ }, string>, z.ZodObject<{
396
+ tool: z.ZodString;
397
+ command: z.ZodString;
398
+ }, "strict", z.ZodTypeAny, {
399
+ tool: string;
400
+ command: string;
401
+ }, {
402
+ tool: string;
403
+ command: string;
404
+ }>, z.ZodObject<{
405
+ tool: z.ZodString;
406
+ file_path: z.ZodString;
407
+ }, "strict", z.ZodTypeAny, {
408
+ tool: string;
409
+ file_path: string;
410
+ }, {
411
+ tool: string;
412
+ file_path: string;
413
+ }>]>;
414
+ /**
415
+ * The two directions every rule must prove, through the real evaluator.
416
+ *
417
+ * **`block` means "this rule MUST match"; `allow` means "this rule MUST NOT
418
+ * match".** They are not verdicts. Five of the eight packs default to `ask` or
419
+ * `warn`, and `warn` folds into an allow verdict, so reading `block` as "the
420
+ * guard denies" would fail every `warn` rule and make the negative fixture
421
+ * vacuous for them.
422
+ *
423
+ * Both are `.min(1)`: the spec only requires CI to fail on a rule missing a
424
+ * negative fixture, but requiring it here moves that failure to authoring time,
425
+ * where it costs a keystroke instead of a pull-request round trip.
426
+ */
427
+ declare const FixturesSchema: z.ZodObject<{
428
+ block: z.ZodArray<z.ZodUnion<[z.ZodEffects<z.ZodString, {
429
+ tool: "Bash";
430
+ command: string;
431
+ }, string>, z.ZodObject<{
432
+ tool: z.ZodString;
433
+ command: z.ZodString;
434
+ }, "strict", z.ZodTypeAny, {
435
+ tool: string;
436
+ command: string;
437
+ }, {
438
+ tool: string;
439
+ command: string;
440
+ }>, z.ZodObject<{
441
+ tool: z.ZodString;
442
+ file_path: z.ZodString;
443
+ }, "strict", z.ZodTypeAny, {
444
+ tool: string;
445
+ file_path: string;
446
+ }, {
447
+ tool: string;
448
+ file_path: string;
449
+ }>]>, "many">;
450
+ allow: z.ZodArray<z.ZodUnion<[z.ZodEffects<z.ZodString, {
451
+ tool: "Bash";
452
+ command: string;
453
+ }, string>, z.ZodObject<{
454
+ tool: z.ZodString;
455
+ command: z.ZodString;
456
+ }, "strict", z.ZodTypeAny, {
457
+ tool: string;
458
+ command: string;
459
+ }, {
460
+ tool: string;
461
+ command: string;
462
+ }>, z.ZodObject<{
463
+ tool: z.ZodString;
464
+ file_path: z.ZodString;
465
+ }, "strict", z.ZodTypeAny, {
466
+ tool: string;
467
+ file_path: string;
468
+ }, {
469
+ tool: string;
470
+ file_path: string;
471
+ }>]>, "many">;
472
+ }, "strict", z.ZodTypeAny, {
473
+ block: ({
474
+ tool: string;
475
+ command: string;
476
+ } | {
477
+ tool: string;
478
+ file_path: string;
479
+ } | {
480
+ tool: "Bash";
481
+ command: string;
482
+ })[];
483
+ allow: ({
484
+ tool: string;
485
+ command: string;
486
+ } | {
487
+ tool: string;
488
+ file_path: string;
489
+ } | {
490
+ tool: "Bash";
491
+ command: string;
492
+ })[];
493
+ }, {
494
+ block: (string | {
495
+ tool: string;
496
+ command: string;
497
+ } | {
498
+ tool: string;
499
+ file_path: string;
500
+ })[];
501
+ allow: (string | {
502
+ tool: string;
503
+ command: string;
504
+ } | {
505
+ tool: string;
506
+ file_path: string;
507
+ })[];
508
+ }>;
509
+ /**
510
+ * One published rule.
511
+ *
512
+ * `minTier` and `installCount` are absent on purpose. Both are hosted-only
513
+ * metadata, layered on by rule id at serve time, and `installCount` is authored
514
+ * fiction by its own docstring — shipping invented adoption numbers in an
515
+ * open-source security tool becomes the story. `.strict()` turns either one into
516
+ * a parse error rather than a field that quietly rides along into the public
517
+ * package.
518
+ *
519
+ * `description` carries the rule's HONEST COVERAGE LIMITS, verbatim — e.g. that
520
+ * an `rm -rf` rule misses `rm -fr`. That is the difference between a credible
521
+ * rule set and a marketing one, and it is the first thing a technical evaluator
522
+ * checks. It is a convention rather than a schema constraint, because no
523
+ * validator can tell an honest limit from a sentence shaped like one.
524
+ */
525
+ declare const RuleSchema: z.ZodObject<{
526
+ /** Stable forever. Never renamed — it is the join key for hosted metadata. */
527
+ id: z.ZodString;
528
+ /** The pack this rule belongs to. Validated against `PACKS` by the registry. */
529
+ category: z.ZodString;
530
+ severity: z.ZodEnum<["critical", "high", "medium", "low", "info"]>;
531
+ defaultAction: z.ZodEnum<["block", "require_approval", "warn"]>;
532
+ title: z.ZodString;
533
+ description: z.ZodString;
534
+ match: z.ZodEffects<z.ZodObject<{
535
+ any_of: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
536
+ kind: z.ZodString;
537
+ label: z.ZodOptional<z.ZodString>;
538
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
539
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
540
+ file_glob: z.ZodOptional<z.ZodString>;
541
+ }, "strict", z.ZodTypeAny, {
542
+ kind: string;
543
+ label?: string | undefined;
544
+ detail_contains?: string[] | undefined;
545
+ detail_matches?: string[] | undefined;
546
+ file_glob?: string | undefined;
547
+ }, {
548
+ kind: string;
549
+ label?: string | undefined;
550
+ detail_contains?: string[] | undefined;
551
+ detail_matches?: string[] | undefined;
552
+ file_glob?: string | undefined;
553
+ }>, {
554
+ kind: string;
555
+ label?: string | undefined;
556
+ detail_contains?: string[] | undefined;
557
+ detail_matches?: string[] | undefined;
558
+ file_glob?: string | undefined;
559
+ }, {
560
+ kind: string;
561
+ label?: string | undefined;
562
+ detail_contains?: string[] | undefined;
563
+ detail_matches?: string[] | undefined;
564
+ file_glob?: string | undefined;
565
+ }>, "many">>;
566
+ all_of: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
567
+ kind: z.ZodString;
568
+ label: z.ZodOptional<z.ZodString>;
569
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
570
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
571
+ file_glob: z.ZodOptional<z.ZodString>;
572
+ }, "strict", z.ZodTypeAny, {
573
+ kind: string;
574
+ label?: string | undefined;
575
+ detail_contains?: string[] | undefined;
576
+ detail_matches?: string[] | undefined;
577
+ file_glob?: string | undefined;
578
+ }, {
579
+ kind: string;
580
+ label?: string | undefined;
581
+ detail_contains?: string[] | undefined;
582
+ detail_matches?: string[] | undefined;
583
+ file_glob?: string | undefined;
584
+ }>, {
585
+ kind: string;
586
+ label?: string | undefined;
587
+ detail_contains?: string[] | undefined;
588
+ detail_matches?: string[] | undefined;
589
+ file_glob?: string | undefined;
590
+ }, {
591
+ kind: string;
592
+ label?: string | undefined;
593
+ detail_contains?: string[] | undefined;
594
+ detail_matches?: string[] | undefined;
595
+ file_glob?: string | undefined;
596
+ }>, "many">>;
597
+ none_of: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
598
+ kind: z.ZodString;
599
+ label: z.ZodOptional<z.ZodString>;
600
+ detail_contains: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
601
+ detail_matches: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
602
+ file_glob: z.ZodOptional<z.ZodString>;
603
+ }, "strict", z.ZodTypeAny, {
604
+ kind: string;
605
+ label?: string | undefined;
606
+ detail_contains?: string[] | undefined;
607
+ detail_matches?: string[] | undefined;
608
+ file_glob?: string | undefined;
609
+ }, {
610
+ kind: string;
611
+ label?: string | undefined;
612
+ detail_contains?: string[] | undefined;
613
+ detail_matches?: string[] | undefined;
614
+ file_glob?: string | undefined;
615
+ }>, {
616
+ kind: string;
617
+ label?: string | undefined;
618
+ detail_contains?: string[] | undefined;
619
+ detail_matches?: string[] | undefined;
620
+ file_glob?: string | undefined;
621
+ }, {
622
+ kind: string;
623
+ label?: string | undefined;
624
+ detail_contains?: string[] | undefined;
625
+ detail_matches?: string[] | undefined;
626
+ file_glob?: string | undefined;
627
+ }>, "many">>;
628
+ }, "strict", z.ZodTypeAny, {
629
+ any_of?: {
630
+ kind: string;
631
+ label?: string | undefined;
632
+ detail_contains?: string[] | undefined;
633
+ detail_matches?: string[] | undefined;
634
+ file_glob?: string | undefined;
635
+ }[] | undefined;
636
+ all_of?: {
637
+ kind: string;
638
+ label?: string | undefined;
639
+ detail_contains?: string[] | undefined;
640
+ detail_matches?: string[] | undefined;
641
+ file_glob?: string | undefined;
642
+ }[] | undefined;
643
+ none_of?: {
644
+ kind: string;
645
+ label?: string | undefined;
646
+ detail_contains?: string[] | undefined;
647
+ detail_matches?: string[] | undefined;
648
+ file_glob?: string | undefined;
649
+ }[] | undefined;
650
+ }, {
651
+ any_of?: {
652
+ kind: string;
653
+ label?: string | undefined;
654
+ detail_contains?: string[] | undefined;
655
+ detail_matches?: string[] | undefined;
656
+ file_glob?: string | undefined;
657
+ }[] | undefined;
658
+ all_of?: {
659
+ kind: string;
660
+ label?: string | undefined;
661
+ detail_contains?: string[] | undefined;
662
+ detail_matches?: string[] | undefined;
663
+ file_glob?: string | undefined;
664
+ }[] | undefined;
665
+ none_of?: {
666
+ kind: string;
667
+ label?: string | undefined;
668
+ detail_contains?: string[] | undefined;
669
+ detail_matches?: string[] | undefined;
670
+ file_glob?: string | undefined;
671
+ }[] | undefined;
672
+ }>, {
673
+ any_of?: {
674
+ kind: string;
675
+ label?: string | undefined;
676
+ detail_contains?: string[] | undefined;
677
+ detail_matches?: string[] | undefined;
678
+ file_glob?: string | undefined;
679
+ }[] | undefined;
680
+ all_of?: {
681
+ kind: string;
682
+ label?: string | undefined;
683
+ detail_contains?: string[] | undefined;
684
+ detail_matches?: string[] | undefined;
685
+ file_glob?: string | undefined;
686
+ }[] | undefined;
687
+ none_of?: {
688
+ kind: string;
689
+ label?: string | undefined;
690
+ detail_contains?: string[] | undefined;
691
+ detail_matches?: string[] | undefined;
692
+ file_glob?: string | undefined;
693
+ }[] | undefined;
694
+ }, {
695
+ any_of?: {
696
+ kind: string;
697
+ label?: string | undefined;
698
+ detail_contains?: string[] | undefined;
699
+ detail_matches?: string[] | undefined;
700
+ file_glob?: string | undefined;
701
+ }[] | undefined;
702
+ all_of?: {
703
+ kind: string;
704
+ label?: string | undefined;
705
+ detail_contains?: string[] | undefined;
706
+ detail_matches?: string[] | undefined;
707
+ file_glob?: string | undefined;
708
+ }[] | undefined;
709
+ none_of?: {
710
+ kind: string;
711
+ label?: string | undefined;
712
+ detail_contains?: string[] | undefined;
713
+ detail_matches?: string[] | undefined;
714
+ file_glob?: string | undefined;
715
+ }[] | undefined;
716
+ }>;
717
+ fixtures: z.ZodObject<{
718
+ block: z.ZodArray<z.ZodUnion<[z.ZodEffects<z.ZodString, {
719
+ tool: "Bash";
720
+ command: string;
721
+ }, string>, z.ZodObject<{
722
+ tool: z.ZodString;
723
+ command: z.ZodString;
724
+ }, "strict", z.ZodTypeAny, {
725
+ tool: string;
726
+ command: string;
727
+ }, {
728
+ tool: string;
729
+ command: string;
730
+ }>, z.ZodObject<{
731
+ tool: z.ZodString;
732
+ file_path: z.ZodString;
733
+ }, "strict", z.ZodTypeAny, {
734
+ tool: string;
735
+ file_path: string;
736
+ }, {
737
+ tool: string;
738
+ file_path: string;
739
+ }>]>, "many">;
740
+ allow: z.ZodArray<z.ZodUnion<[z.ZodEffects<z.ZodString, {
741
+ tool: "Bash";
742
+ command: string;
743
+ }, string>, z.ZodObject<{
744
+ tool: z.ZodString;
745
+ command: z.ZodString;
746
+ }, "strict", z.ZodTypeAny, {
747
+ tool: string;
748
+ command: string;
749
+ }, {
750
+ tool: string;
751
+ command: string;
752
+ }>, z.ZodObject<{
753
+ tool: z.ZodString;
754
+ file_path: z.ZodString;
755
+ }, "strict", z.ZodTypeAny, {
756
+ tool: string;
757
+ file_path: string;
758
+ }, {
759
+ tool: string;
760
+ file_path: string;
761
+ }>]>, "many">;
762
+ }, "strict", z.ZodTypeAny, {
763
+ block: ({
764
+ tool: string;
765
+ command: string;
766
+ } | {
767
+ tool: string;
768
+ file_path: string;
769
+ } | {
770
+ tool: "Bash";
771
+ command: string;
772
+ })[];
773
+ allow: ({
774
+ tool: string;
775
+ command: string;
776
+ } | {
777
+ tool: string;
778
+ file_path: string;
779
+ } | {
780
+ tool: "Bash";
781
+ command: string;
782
+ })[];
783
+ }, {
784
+ block: (string | {
785
+ tool: string;
786
+ command: string;
787
+ } | {
788
+ tool: string;
789
+ file_path: string;
790
+ })[];
791
+ allow: (string | {
792
+ tool: string;
793
+ command: string;
794
+ } | {
795
+ tool: string;
796
+ file_path: string;
797
+ })[];
798
+ }>;
799
+ }, "strict", z.ZodTypeAny, {
800
+ id: string;
801
+ category: string;
802
+ severity: "critical" | "high" | "medium" | "low" | "info";
803
+ defaultAction: "block" | "require_approval" | "warn";
804
+ title: string;
805
+ description: string;
806
+ match: {
807
+ any_of?: {
808
+ kind: string;
809
+ label?: string | undefined;
810
+ detail_contains?: string[] | undefined;
811
+ detail_matches?: string[] | undefined;
812
+ file_glob?: string | undefined;
813
+ }[] | undefined;
814
+ all_of?: {
815
+ kind: string;
816
+ label?: string | undefined;
817
+ detail_contains?: string[] | undefined;
818
+ detail_matches?: string[] | undefined;
819
+ file_glob?: string | undefined;
820
+ }[] | undefined;
821
+ none_of?: {
822
+ kind: string;
823
+ label?: string | undefined;
824
+ detail_contains?: string[] | undefined;
825
+ detail_matches?: string[] | undefined;
826
+ file_glob?: string | undefined;
827
+ }[] | undefined;
828
+ };
829
+ fixtures: {
830
+ block: ({
831
+ tool: string;
832
+ command: string;
833
+ } | {
834
+ tool: string;
835
+ file_path: string;
836
+ } | {
837
+ tool: "Bash";
838
+ command: string;
839
+ })[];
840
+ allow: ({
841
+ tool: string;
842
+ command: string;
843
+ } | {
844
+ tool: string;
845
+ file_path: string;
846
+ } | {
847
+ tool: "Bash";
848
+ command: string;
849
+ })[];
850
+ };
851
+ }, {
852
+ id: string;
853
+ category: string;
854
+ severity: "critical" | "high" | "medium" | "low" | "info";
855
+ defaultAction: "block" | "require_approval" | "warn";
856
+ title: string;
857
+ description: string;
858
+ match: {
859
+ any_of?: {
860
+ kind: string;
861
+ label?: string | undefined;
862
+ detail_contains?: string[] | undefined;
863
+ detail_matches?: string[] | undefined;
864
+ file_glob?: string | undefined;
865
+ }[] | undefined;
866
+ all_of?: {
867
+ kind: string;
868
+ label?: string | undefined;
869
+ detail_contains?: string[] | undefined;
870
+ detail_matches?: string[] | undefined;
871
+ file_glob?: string | undefined;
872
+ }[] | undefined;
873
+ none_of?: {
874
+ kind: string;
875
+ label?: string | undefined;
876
+ detail_contains?: string[] | undefined;
877
+ detail_matches?: string[] | undefined;
878
+ file_glob?: string | undefined;
879
+ }[] | undefined;
880
+ };
881
+ fixtures: {
882
+ block: (string | {
883
+ tool: string;
884
+ command: string;
885
+ } | {
886
+ tool: string;
887
+ file_path: string;
888
+ })[];
889
+ allow: (string | {
890
+ tool: string;
891
+ command: string;
892
+ } | {
893
+ tool: string;
894
+ file_path: string;
895
+ })[];
896
+ };
897
+ }>;
898
+ /** How dangerous the caught action is. Never a price. */
899
+ type Severity = (typeof SEVERITIES)[number];
900
+ /** What the guard does on a match. */
901
+ type Action = (typeof ACTIONS)[number];
902
+ /** One condition matched against a tool call. */
903
+ type MatchCondition = z.infer<typeof MatchConditionSchema>;
904
+ /** A composed match — `any_of` / `all_of` / `none_of`. */
905
+ type Match = z.infer<typeof MatchSchema>;
906
+ /** A fixture as authored: a bare command string, or a tagged single-channel object. */
907
+ type FixtureInput = z.input<typeof FixtureSchema>;
908
+ /** A fixture after parsing — always the tagged object form. */
909
+ type Fixture = z.output<typeof FixtureSchema>;
910
+ /** The must-match / must-not-match pair every rule ships. */
911
+ type Fixtures = z.infer<typeof FixturesSchema>;
912
+ /** One published rule, as authored. */
913
+ type RuleInput = z.input<typeof RuleSchema>;
914
+ /** One published rule, parsed and normalized. */
915
+ type Rule = z.output<typeof RuleSchema>;
916
+ /**
917
+ * Validate a rule from unknown input.
918
+ *
919
+ * This is the entry point a contributor gets locally. It answers "is this a
920
+ * well-formed rule?" — it does NOT answer "does this rule actually fire on the
921
+ * command you think it does." That needs the real evaluator, which lives in a
922
+ * private package, so it runs in our CI on the pull request. A contributor gets
923
+ * the schema here and the real answer there, which is where it has to run to be
924
+ * trusted anyway.
925
+ *
926
+ * @param input - Unknown input, e.g. parsed JSON or a rule module's default export.
927
+ * @returns A Zod SafeParseReturnType — check `.success` before reading `.data`.
928
+ */
929
+ declare function parseRule(input: unknown): z.SafeParseReturnType<unknown, Rule>;
930
+ /**
931
+ * Parse a rule, or throw with the reason.
932
+ *
933
+ * Used by rule modules themselves via {@link defineRule}, so an authoring
934
+ * mistake fails at import — which is to say, at test time — rather than
935
+ * surviving into a corpus that only validates what it remembers to check.
936
+ */
937
+ declare function parseRuleOrThrow(input: unknown): Rule;
938
+ /**
939
+ * Declare a rule. Validates at module load, so a malformed rule cannot reach the
940
+ * registry — the corpus is only as trustworthy as its weakest unvalidated entry.
941
+ */
942
+ declare function defineRule(rule: RuleInput): Rule;
943
+
944
+ export { ACTIONS, type Action, ActionSchema, DETAIL_MATCHES_MAX_PATTERNS, DETAIL_MATCHES_MAX_PATTERN_LENGTH, type Fixture, type FixtureInput, FixtureSchema, type Fixtures, FixturesSchema, type Match, type MatchCondition, MatchConditionSchema, MatchSchema, type Rule, type RuleInput, RuleSchema, SEVERITIES, type Severity, SeveritySchema, defineRule, hasNestedUnboundedQuantifier, parseRule, parseRuleOrThrow };