@agenttrail/guardrails 0.2.0 → 0.2.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.
- package/dist/{chunk-3NDBRXP3.js → chunk-SLWHBH7H.js} +103 -26
- package/dist/chunk-SLWHBH7H.js.map +1 -0
- package/dist/guardrails.cjs +102 -25
- package/dist/guardrails.cjs.map +1 -1
- package/dist/guardrails.d.cts +8 -5
- package/dist/guardrails.d.ts +8 -5
- package/dist/guardrails.js +1 -1
- package/dist/index.cjs +102 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +44 -29
- package/dist/chunk-3NDBRXP3.js.map +0 -1
package/dist/guardrails.d.cts
CHANGED
|
@@ -280,9 +280,12 @@ declare function pwsh(command: string): Fixture;
|
|
|
280
280
|
* A fixture on the file channel.
|
|
281
281
|
*
|
|
282
282
|
* `tool` defaults to `Edit`. The guard's mapper routes `Edit`, `Write`, `Read`,
|
|
283
|
-
* `MultiEdit` and `
|
|
284
|
-
*
|
|
285
|
-
*
|
|
283
|
+
* `MultiEdit`, `NotebookEdit`, `Grep` and `Glob` to this channel, and WHICH ONE a
|
|
284
|
+
* fixture names is load-bearing: a file rule that names an operation in its id or
|
|
285
|
+
* title excludes the read tools in its `none_of` (`{Read,Grep}`), so the same path
|
|
286
|
+
* matches under `Edit` and does not match under `Read`. Naming the tool is
|
|
287
|
+
* therefore part of the case a fixture proves, not documentation of it — an
|
|
288
|
+
* `allow` fixture that leaves the default in place proves nothing about reads.
|
|
286
289
|
*/
|
|
287
290
|
declare function file(filePath: string, tool?: string): Fixture;
|
|
288
291
|
/**
|
|
@@ -362,7 +365,7 @@ declare function mentions(text: string): Fixture[];
|
|
|
362
365
|
* Pinned to `package.json`'s `version` by `__tests__/stamp.test.ts` — this is a copy
|
|
363
366
|
* of that value, not an independent one, and the test is what makes the copy safe.
|
|
364
367
|
*/
|
|
365
|
-
declare const CATALOG_VERSION = "0.2.
|
|
368
|
+
declare const CATALOG_VERSION = "0.2.1";
|
|
366
369
|
/**
|
|
367
370
|
* When the rules in this catalog reached their current state, as an ISO-8601
|
|
368
371
|
* instant. The question it answers is not "when did this reach a registry" but "how
|
|
@@ -372,7 +375,7 @@ declare const CATALOG_VERSION = "0.2.0";
|
|
|
372
375
|
* the guard handles a missing date, and a wrong date that looks right is worse than
|
|
373
376
|
* a missing one.
|
|
374
377
|
*/
|
|
375
|
-
declare const CATALOG_PUBLISHED_AT = "2026-09-
|
|
378
|
+
declare const CATALOG_PUBLISHED_AT = "2026-09-29T03:49:22Z";
|
|
376
379
|
|
|
377
380
|
/**
|
|
378
381
|
* The corpus itself — rules only, and **deliberately free of zod**.
|
package/dist/guardrails.d.ts
CHANGED
|
@@ -280,9 +280,12 @@ declare function pwsh(command: string): Fixture;
|
|
|
280
280
|
* A fixture on the file channel.
|
|
281
281
|
*
|
|
282
282
|
* `tool` defaults to `Edit`. The guard's mapper routes `Edit`, `Write`, `Read`,
|
|
283
|
-
* `MultiEdit` and `
|
|
284
|
-
*
|
|
285
|
-
*
|
|
283
|
+
* `MultiEdit`, `NotebookEdit`, `Grep` and `Glob` to this channel, and WHICH ONE a
|
|
284
|
+
* fixture names is load-bearing: a file rule that names an operation in its id or
|
|
285
|
+
* title excludes the read tools in its `none_of` (`{Read,Grep}`), so the same path
|
|
286
|
+
* matches under `Edit` and does not match under `Read`. Naming the tool is
|
|
287
|
+
* therefore part of the case a fixture proves, not documentation of it — an
|
|
288
|
+
* `allow` fixture that leaves the default in place proves nothing about reads.
|
|
286
289
|
*/
|
|
287
290
|
declare function file(filePath: string, tool?: string): Fixture;
|
|
288
291
|
/**
|
|
@@ -362,7 +365,7 @@ declare function mentions(text: string): Fixture[];
|
|
|
362
365
|
* Pinned to `package.json`'s `version` by `__tests__/stamp.test.ts` — this is a copy
|
|
363
366
|
* of that value, not an independent one, and the test is what makes the copy safe.
|
|
364
367
|
*/
|
|
365
|
-
declare const CATALOG_VERSION = "0.2.
|
|
368
|
+
declare const CATALOG_VERSION = "0.2.1";
|
|
366
369
|
/**
|
|
367
370
|
* When the rules in this catalog reached their current state, as an ISO-8601
|
|
368
371
|
* instant. The question it answers is not "when did this reach a registry" but "how
|
|
@@ -372,7 +375,7 @@ declare const CATALOG_VERSION = "0.2.0";
|
|
|
372
375
|
* the guard handles a missing date, and a wrong date that looks right is worse than
|
|
373
376
|
* a missing one.
|
|
374
377
|
*/
|
|
375
|
-
declare const CATALOG_PUBLISHED_AT = "2026-09-
|
|
378
|
+
declare const CATALOG_PUBLISHED_AT = "2026-09-29T03:49:22Z";
|
|
376
379
|
|
|
377
380
|
/**
|
|
378
381
|
* The corpus itself — rules only, and **deliberately free of zod**.
|
package/dist/guardrails.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -220,7 +220,7 @@ var acInstructionFileEdit = {
|
|
|
220
220
|
severity: "high",
|
|
221
221
|
defaultAction: "require_approval",
|
|
222
222
|
title: "The agent editing its standing instructions",
|
|
223
|
-
description: "Holds a file tool opening the instructions a coding agent loads at the start of every session: `CLAUDE.md`, `CLAUDE.local.md` and `.claude/rules/`; `AGENTS.md` and `AGENTS.override.md`, which Codex, Cursor, Windsurf, Copilot and Cline all read; `GEMINI.md`; `.cursorrules`; Windsurf's `.windsurfrules`, `.windsurf/rules/` and `.devin/rules/`; Cline's `.clinerules` file or directory and its global `Cline/Rules/` folder; Copilot's `.github/copilot-instructions.md` and `.github/instructions/**/*.instructions.md`; and `.aider.conf.yml`, which sets the files Aider reads on every launch. A line written into one of these is followed in every later session. Matched in any directory and in any letter case. File tools carry a path and no content, so it cannot see what was written
|
|
223
|
+
description: "Holds a file tool opening the instructions a coding agent loads at the start of every session: `CLAUDE.md`, `CLAUDE.local.md` and `.claude/rules/`; `AGENTS.md` and `AGENTS.override.md`, which Codex, Cursor, Windsurf, Copilot and Cline all read; `GEMINI.md`; `.cursorrules`; Windsurf's `.windsurfrules`, `.windsurf/rules/` and `.devin/rules/`; Cline's `.clinerules` file or directory and its global `Cline/Rules/` folder; Copilot's `.github/copilot-instructions.md` and `.github/instructions/**/*.instructions.md`; and `.aider.conf.yml`, which sets the files Aider reads on every launch. A line written into one of these is followed in every later session. Matched in any directory and in any letter case. Reading is excluded \u2014 the `Read` and `Grep` tools never match \u2014 so an agent opening its instructions to read them is not held; every other file tool is, including one this corpus does not know. File tools carry a path and no content, so it still cannot see what was written. Does not cover `.cursor/rules/`, which `fs.agent-self-config` holds. Misses a context file renamed through Gemini's `context.fileName` or Codex's `project_doc_fallback_filenames`, Aider's `CONVENTIONS.md`, which Aider loads only when asked and which is too common a name to match, and any of these files written by a shell command instead of a file tool.",
|
|
224
224
|
match: {
|
|
225
225
|
any_of: [
|
|
226
226
|
{
|
|
@@ -236,6 +236,11 @@ var acInstructionFileEdit = {
|
|
|
236
236
|
{ kind: "execute_tool", file_glob: "**/.github/copilot-instructions.md" },
|
|
237
237
|
{ kind: "execute_tool", file_glob: "**/.github/instructions/**/*.instructions.md" },
|
|
238
238
|
{ kind: "execute_tool", file_glob: "**/.aider.conf.yml" }
|
|
239
|
+
],
|
|
240
|
+
none_of: [
|
|
241
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
242
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
243
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
239
244
|
]
|
|
240
245
|
},
|
|
241
246
|
fixtures: {
|
|
@@ -270,7 +275,9 @@ var acInstructionFileEdit = {
|
|
|
270
275
|
file(".github/workflows/ci.yml"),
|
|
271
276
|
file(".cursor/rules/style.mdc"),
|
|
272
277
|
file(".claude/settings.json"),
|
|
273
|
-
file(".aider.chat.history.md")
|
|
278
|
+
file(".aider.chat.history.md"),
|
|
279
|
+
file("CLAUDE.md", "Read"),
|
|
280
|
+
file(".github/copilot-instructions.md", "Grep")
|
|
274
281
|
]
|
|
275
282
|
}
|
|
276
283
|
};
|
|
@@ -284,7 +291,7 @@ var acMcpServerAdd = {
|
|
|
284
291
|
severity: "high",
|
|
285
292
|
defaultAction: "require_approval",
|
|
286
293
|
title: "Adding an MCP server to an agent",
|
|
287
|
-
description: "Holds the commands that register an MCP server with a coding agent \u2014 `claude mcp add`, `claude mcp add-json`, `claude mcp add-from-claude-desktop`, `codex mcp add` and `gemini mcp add` \u2014 and `claude --mcp-config`, which attaches servers to a single session. Each gives an agent a new set of tools, often a program fetched and started on the spot, with nobody reviewing the change. Deliberately NOT matched: listing or removing servers (`claude mcp list`, `claude mcp remove`), and the MCP Inspector (`npx @modelcontextprotocol/inspector`), which is a debugging tool rather than a registration. Cursor has no command for this, so its `.cursor/mcp.json`, in a project or the home directory, is matched as a file instead; a project's `.mcp.json` is held by `fs.agent-self-config`. Misses servers written into `~/.claude.json` or Gemini's `settings.json` with a file tool, and a global flag whose value is quoted when it sits before `mcp`. A quoted MENTION is not a use: a search, a `git commit -m` message, an `echo` or a `curl --data` body that only names this command is left alone, as long as every shell metacharacter stays inside the quotes.",
|
|
294
|
+
description: "Holds the commands that register an MCP server with a coding agent \u2014 `claude mcp add`, `claude mcp add-json`, `claude mcp add-from-claude-desktop`, `codex mcp add` and `gemini mcp add` \u2014 and `claude --mcp-config`, which attaches servers to a single session. Each gives an agent a new set of tools, often a program fetched and started on the spot, with nobody reviewing the change. Deliberately NOT matched: listing or removing servers (`claude mcp list`, `claude mcp remove`), and the MCP Inspector (`npx @modelcontextprotocol/inspector`), which is a debugging tool rather than a registration. Cursor has no command for this, so its `.cursor/mcp.json`, in a project or the home directory, is matched as a file instead \u2014 written, not read: the `Read` and `Grep` tools never match it, and every other file tool does, including one this corpus does not know; a project's `.mcp.json` is held by `fs.agent-self-config`. Misses servers written into `~/.claude.json` or Gemini's `settings.json` with a file tool, and a global flag whose value is quoted when it sits before `mcp`. A quoted MENTION is not a use: a search, a `git commit -m` message, an `echo` or a `curl --data` body that only names this command is left alone, as long as every shell metacharacter stays inside the quotes.",
|
|
288
295
|
match: {
|
|
289
296
|
any_of: [
|
|
290
297
|
{
|
|
@@ -299,7 +306,14 @@ var acMcpServerAdd = {
|
|
|
299
306
|
// Cursor has no `mcp add` command; its servers are added by editing this file.
|
|
300
307
|
{ kind: "execute_tool", file_glob: "**/.cursor/mcp.json" }
|
|
301
308
|
],
|
|
302
|
-
none_of: [
|
|
309
|
+
none_of: [
|
|
310
|
+
// A read is not an addition. This covers the `.cursor/mcp.json` arm — the
|
|
311
|
+
// shell arm already pins its own tool family. Named as a deny-list rather
|
|
312
|
+
// than an `any_of` over the write tools, so an unknown file tool on that
|
|
313
|
+
// path still holds.
|
|
314
|
+
{ kind: "execute_tool", label: "{Read,Grep}" },
|
|
315
|
+
...QUOTED_MENTION
|
|
316
|
+
]
|
|
303
317
|
},
|
|
304
318
|
fixtures: {
|
|
305
319
|
block: [
|
|
@@ -324,7 +338,9 @@ var acMcpServerAdd = {
|
|
|
324
338
|
bash("npx @modelcontextprotocol/inspector"),
|
|
325
339
|
file("config/mcp.json"),
|
|
326
340
|
file(".cursor/rules/style.mdc"),
|
|
327
|
-
bash("pnpm run test")
|
|
341
|
+
bash("pnpm run test"),
|
|
342
|
+
file(".cursor/mcp.json", "Read"),
|
|
343
|
+
file("/Users/dev/.cursor/mcp.json", "Grep")
|
|
328
344
|
]
|
|
329
345
|
}
|
|
330
346
|
};
|
|
@@ -336,7 +352,7 @@ var acMemoryStoreEdit = {
|
|
|
336
352
|
severity: "high",
|
|
337
353
|
defaultAction: "require_approval",
|
|
338
354
|
title: "The agent editing its own memory",
|
|
339
|
-
description: "Holds a file tool opening the memory a coding agent carries between sessions: Claude Code's auto memory under `.claude/projects/<project>/memory/`, and its sub-agent memory in `.claude/agent-memory/` and `.claude/agent-memory-local/`; Codex's `.codex/memories/`; Gemini's private `.gemini/tmp/<project>/memory/`; Windsurf's `.codeium/windsurf/memories/`, including `global_rules.md`; and `.cursor/memory/`. A false fact saved here is recalled as true in every later session. A `MEMORY.md` outside those directories deliberately does NOT match, and neither does a project's own `docs/memory/` folder: the name alone is not an agent's memory.
|
|
355
|
+
description: "Holds a file tool opening the memory a coding agent carries between sessions: Claude Code's auto memory under `.claude/projects/<project>/memory/`, and its sub-agent memory in `.claude/agent-memory/` and `.claude/agent-memory-local/`; Codex's `.codex/memories/`; Gemini's private `.gemini/tmp/<project>/memory/`; Windsurf's `.codeium/windsurf/memories/`, including `global_rules.md`; and `.cursor/memory/`. A false fact saved here is recalled as true in every later session. A `MEMORY.md` outside those directories deliberately does NOT match, and neither does a project's own `docs/memory/` folder: the name alone is not an agent's memory. Reading is excluded \u2014 the `Read` and `Grep` tools never match \u2014 so an agent recalling a memory by reading its file is not asked; every other file tool is, including one this corpus does not know. File tools carry a path and no content, so it still cannot see what was written. Misses a memory directory moved with Claude Code's `autoMemoryDirectory` setting, and Cursor memories kept anywhere other than `.cursor/memory/`, since Cursor does not document where it stores them. Gemini memories saved into `GEMINI.md` are held by `ac.instruction-file-edit` instead.",
|
|
340
356
|
match: {
|
|
341
357
|
any_of: [
|
|
342
358
|
{ kind: "execute_tool", file_glob: "**/.claude/projects/*/memory/**" },
|
|
@@ -345,6 +361,11 @@ var acMemoryStoreEdit = {
|
|
|
345
361
|
{ kind: "execute_tool", file_glob: "**/.gemini/tmp/*/memory/**" },
|
|
346
362
|
{ kind: "execute_tool", file_glob: "**/.codeium/windsurf/memories/**" },
|
|
347
363
|
{ kind: "execute_tool", file_glob: "**/.cursor/memory/**" }
|
|
364
|
+
],
|
|
365
|
+
none_of: [
|
|
366
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
367
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
368
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
348
369
|
]
|
|
349
370
|
},
|
|
350
371
|
fixtures: {
|
|
@@ -368,7 +389,9 @@ var acMemoryStoreEdit = {
|
|
|
368
389
|
file("packages/memory-store/src/index.ts"),
|
|
369
390
|
file("/Users/dev/.claude/projects/-Users-dev-shop/transcript.jsonl"),
|
|
370
391
|
file("/home/dev/.gemini/settings.json"),
|
|
371
|
-
file(".claude/settings.json")
|
|
392
|
+
file(".claude/settings.json"),
|
|
393
|
+
file(".claude/agent-memory/reviewer/MEMORY.md", "Read"),
|
|
394
|
+
file("/home/dev/.codex/memories/project.md", "Grep")
|
|
372
395
|
]
|
|
373
396
|
}
|
|
374
397
|
};
|
|
@@ -438,7 +461,7 @@ var acSkillInstall = {
|
|
|
438
461
|
severity: "high",
|
|
439
462
|
defaultAction: "require_approval",
|
|
440
463
|
title: "The agent installing a skill, command or sub-agent",
|
|
441
|
-
description: "Holds a file tool opening a skill, slash command, sub-agent or output style that a coding agent loads by name: Claude Code's `.claude/skills/`, `.claude/commands/`, `.claude/agents/` and `.claude/output-styles/`; the shared `.agents/skills/`; Codex's `.codex/skills/`, `.codex/prompts/` and `.codex/agents/`; Gemini's `.gemini/commands/`, `.gemini/skills/` and `.gemini/agents/`; Cursor's `.cursor/skills/`, `.cursor/agents/` and `.cursor/commands/`; Windsurf's `.windsurf/workflows/` and `.windsurf/skills/` and their global copies under `.codeium/windsurf/`; Cline's `.cline/skills/`; and a `SKILL.md` anywhere, which is how a plugin ships a skill. Each becomes a reusable instruction the agent may follow later, often with a script beside it that the agent runs. Matched at project or home level and in any letter case. File tools carry a path and no content, so it cannot see what was written
|
|
464
|
+
description: "Holds a file tool opening a skill, slash command, sub-agent or output style that a coding agent loads by name: Claude Code's `.claude/skills/`, `.claude/commands/`, `.claude/agents/` and `.claude/output-styles/`; the shared `.agents/skills/`; Codex's `.codex/skills/`, `.codex/prompts/` and `.codex/agents/`; Gemini's `.gemini/commands/`, `.gemini/skills/` and `.gemini/agents/`; Cursor's `.cursor/skills/`, `.cursor/agents/` and `.cursor/commands/`; Windsurf's `.windsurf/workflows/` and `.windsurf/skills/` and their global copies under `.codeium/windsurf/`; Cline's `.cline/skills/`; and a `SKILL.md` anywhere, which is how a plugin ships a skill. Each becomes a reusable instruction the agent may follow later, often with a script beside it that the agent runs. Matched at project or home level and in any letter case. Reading is excluded \u2014 the `Read` and `Grep` tools never match \u2014 so an agent opening an installed skill to read it is not held; every other file tool is, including one this corpus does not know. File tools carry a path and no content, so it still cannot see what was written. Deliberately NOT matched: an ordinary `skills/`, `commands/` or `agents/` folder in a project's source, and `.clinerules/skills/`, which `ac.instruction-file-edit` holds. Misses a skill copied into one of these folders by a shell command such as `cp` or `git clone`, and the managed system-wide workflow folders.",
|
|
442
465
|
match: {
|
|
443
466
|
any_of: [
|
|
444
467
|
{ kind: "execute_tool", file_glob: "**/.claude/{skills,commands,agents,output-styles}/**" },
|
|
@@ -450,6 +473,11 @@ var acSkillInstall = {
|
|
|
450
473
|
{ kind: "execute_tool", file_glob: "**/.codeium/windsurf/{skills,global_workflows}/**" },
|
|
451
474
|
{ kind: "execute_tool", file_glob: "**/.cline/skills/**" },
|
|
452
475
|
{ kind: "execute_tool", file_glob: "**/SKILL.md" }
|
|
476
|
+
],
|
|
477
|
+
none_of: [
|
|
478
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
479
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
480
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
453
481
|
]
|
|
454
482
|
},
|
|
455
483
|
fixtures: {
|
|
@@ -479,7 +507,9 @@ var acSkillInstall = {
|
|
|
479
507
|
file(".claude/settings.json"),
|
|
480
508
|
file(".github/workflows/release.yml"),
|
|
481
509
|
file("scripts/release.sh"),
|
|
482
|
-
file(".gemini/settings.json")
|
|
510
|
+
file(".gemini/settings.json"),
|
|
511
|
+
file(".claude/skills/deploy/SKILL.md", "Read"),
|
|
512
|
+
file(".claude/commands/deploy.md", "Grep")
|
|
483
513
|
]
|
|
484
514
|
}
|
|
485
515
|
};
|
|
@@ -1071,7 +1101,7 @@ var fsAgentSelfConfig = {
|
|
|
1071
1101
|
severity: "high",
|
|
1072
1102
|
defaultAction: "require_approval",
|
|
1073
1103
|
title: "The agent editing its own configuration",
|
|
1074
|
-
description: "Holds a file tool opening the files that define what the agent itself is allowed to do \u2014 Claude Code settings and hooks, an MCP server list, a plugin manifest, Cursor rules, a Codex config, and the guard's own config.json and guardrails.json. An agent that can edit these can widen its own reach with nobody reviewing the change. Does NOT match ordinary project files, or `.claude/commands/*.md`, which are prompts rather than permissions. Bounded to WELL-KNOWN paths: no working directory or project root reaches the guard, so it can only match names it already knows.",
|
|
1104
|
+
description: "Holds a file tool opening the files that define what the agent itself is allowed to do \u2014 Claude Code settings and hooks, an MCP server list, a plugin manifest, Cursor rules, a Codex config, and the guard's own config.json and guardrails.json. An agent that can edit these can widen its own reach with nobody reviewing the change. Reading is excluded \u2014 the `Read` and `Grep` tools never match \u2014 so opening one of these files to read it is not held; every other file tool is, including one this corpus does not know. Does NOT match ordinary project files, or `.claude/commands/*.md`, which are prompts rather than permissions. Bounded to WELL-KNOWN paths: no working directory or project root reaches the guard, so it can only match names it already knows.",
|
|
1075
1105
|
match: {
|
|
1076
1106
|
any_of: [
|
|
1077
1107
|
{ kind: "execute_tool", file_glob: "**/.claude/settings*.json" },
|
|
@@ -1082,6 +1112,11 @@ var fsAgentSelfConfig = {
|
|
|
1082
1112
|
{ kind: "execute_tool", file_glob: "**/.codex/config.toml" },
|
|
1083
1113
|
{ kind: "execute_tool", file_glob: "**/.agenttrail/guard/config.json" },
|
|
1084
1114
|
{ kind: "execute_tool", file_glob: "**/.agenttrail/guard/guardrails.json" }
|
|
1115
|
+
],
|
|
1116
|
+
none_of: [
|
|
1117
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
1118
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
1119
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
1085
1120
|
]
|
|
1086
1121
|
},
|
|
1087
1122
|
fixtures: {
|
|
@@ -1095,7 +1130,9 @@ var fsAgentSelfConfig = {
|
|
|
1095
1130
|
file(".claude/commands/deploy.md"),
|
|
1096
1131
|
file("src/index.ts"),
|
|
1097
1132
|
file("package.json"),
|
|
1098
|
-
file("README.md")
|
|
1133
|
+
file("README.md"),
|
|
1134
|
+
file(".claude/settings.json", "Read"),
|
|
1135
|
+
file(".mcp.json", "Grep")
|
|
1099
1136
|
]
|
|
1100
1137
|
}
|
|
1101
1138
|
};
|
|
@@ -1107,7 +1144,7 @@ var fsCiDefinition = {
|
|
|
1107
1144
|
severity: "medium",
|
|
1108
1145
|
defaultAction: "require_approval",
|
|
1109
1146
|
title: "Editing the CI pipeline definition",
|
|
1110
|
-
description: "Holds a file tool opening a CI definition \u2014 GitHub Actions workflows and composite actions, .gitlab-ci.yml, a Jenkinsfile, CircleCI, Azure Pipelines, Buildkite or Bitbucket pipelines. This is where the checks that gate every merge are written down, and where a new step would run with the repository's secrets; both edits look like an ordinary diff. Does NOT match other files under .github/ (CODEOWNERS, issue templates), which gate nothing. It MISSES a CI system whose definition lives outside the repository.",
|
|
1147
|
+
description: "Holds a file tool opening a CI definition \u2014 GitHub Actions workflows and composite actions, .gitlab-ci.yml, a Jenkinsfile, CircleCI, Azure Pipelines, Buildkite or Bitbucket pipelines. This is where the checks that gate every merge are written down, and where a new step would run with the repository's secrets; both edits look like an ordinary diff. Reading is excluded \u2014 the Read and Grep tools never match \u2014 so opening a CI definition to read it is not held; every other file tool is, including one this corpus does not know. Does NOT match other files under .github/ (CODEOWNERS, issue templates), which gate nothing. It MISSES a CI system whose definition lives outside the repository.",
|
|
1111
1148
|
match: {
|
|
1112
1149
|
any_of: [
|
|
1113
1150
|
{ kind: "execute_tool", file_glob: "**/.github/workflows/**" },
|
|
@@ -1118,6 +1155,11 @@ var fsCiDefinition = {
|
|
|
1118
1155
|
{ kind: "execute_tool", file_glob: "**/azure-pipelines.yml" },
|
|
1119
1156
|
{ kind: "execute_tool", file_glob: "**/.buildkite/**" },
|
|
1120
1157
|
{ kind: "execute_tool", file_glob: "**/bitbucket-pipelines.yml" }
|
|
1158
|
+
],
|
|
1159
|
+
none_of: [
|
|
1160
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
1161
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
1162
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
1121
1163
|
]
|
|
1122
1164
|
},
|
|
1123
1165
|
fixtures: {
|
|
@@ -1131,7 +1173,9 @@ var fsCiDefinition = {
|
|
|
1131
1173
|
file(".github/CODEOWNERS"),
|
|
1132
1174
|
file(".github/PULL_REQUEST_TEMPLATE.md"),
|
|
1133
1175
|
file("package.json"),
|
|
1134
|
-
file("README.md")
|
|
1176
|
+
file("README.md"),
|
|
1177
|
+
file(".github/workflows/ci.yml", "Read"),
|
|
1178
|
+
file(".gitlab-ci.yml", "Grep")
|
|
1135
1179
|
]
|
|
1136
1180
|
}
|
|
1137
1181
|
};
|
|
@@ -1143,7 +1187,7 @@ var fsSystemPaths = {
|
|
|
1143
1187
|
severity: "high",
|
|
1144
1188
|
defaultAction: "require_approval",
|
|
1145
1189
|
title: "Writing to a system directory",
|
|
1146
|
-
description: "Holds a file tool
|
|
1190
|
+
description: "Holds a file tool writing to a path under a system directory \u2014 /etc, /bin, /sbin, /usr/bin, /usr/local/bin, /boot, /System, /Library/LaunchDaemons, or Windows/System32. Reading is excluded \u2014 the Read and Grep tools never match \u2014 so reading /etc/hosts is not held; every other file tool is, including one this corpus does not know. HONEST CEILING: this is a list of well-known ABSOLUTE paths and it cannot be anything else. No working directory and no project root reaches the guard, so the rule you would actually want \u2014 'the agent wrote outside the project' \u2014 is inexpressible, and would match everything or nothing. It therefore MISSES a write anywhere else outside your repository, including another project on the same machine.",
|
|
1147
1191
|
match: {
|
|
1148
1192
|
any_of: [
|
|
1149
1193
|
{ kind: "execute_tool", file_glob: "/etc/**" },
|
|
@@ -1155,6 +1199,11 @@ var fsSystemPaths = {
|
|
|
1155
1199
|
{ kind: "execute_tool", file_glob: "/System/**" },
|
|
1156
1200
|
{ kind: "execute_tool", file_glob: "/Library/LaunchDaemons/**" },
|
|
1157
1201
|
{ kind: "execute_tool", file_glob: "**/Windows/System32/**" }
|
|
1202
|
+
],
|
|
1203
|
+
none_of: [
|
|
1204
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
1205
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
1206
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
1158
1207
|
]
|
|
1159
1208
|
},
|
|
1160
1209
|
fixtures: {
|
|
@@ -1168,7 +1217,9 @@ var fsSystemPaths = {
|
|
|
1168
1217
|
file("src/index.ts"),
|
|
1169
1218
|
file("/home/dev/project/src/main.rs"),
|
|
1170
1219
|
file("/tmp/scratch.txt", "Write"),
|
|
1171
|
-
file("docs/etc-notes.md")
|
|
1220
|
+
file("docs/etc-notes.md"),
|
|
1221
|
+
file("/etc/hosts", "Read"),
|
|
1222
|
+
file("/usr/local/bin/app", "Grep")
|
|
1172
1223
|
]
|
|
1173
1224
|
}
|
|
1174
1225
|
};
|
|
@@ -1180,7 +1231,7 @@ var fsVcsInternals = {
|
|
|
1180
1231
|
severity: "high",
|
|
1181
1232
|
defaultAction: "require_approval",
|
|
1182
1233
|
title: "Editing git's internals directly",
|
|
1183
|
-
description: "Holds a file tool opening git's own bookkeeping \u2014 .git/config, .git/hooks/, .git/refs/, .git/HEAD, .git/info/exclude \u2014 where a change alters what future git commands do rather than what the repository contains. Deliberately NARROW: all of .git/** would include COMMIT_EDITMSG and the index, which change during every ordinary commit, so the rule would fire constantly and be switched off. It does NOT match .gitignore, .gitattributes or anything under .github/, which are tracked project files.",
|
|
1234
|
+
description: "Holds a file tool opening git's own bookkeeping \u2014 .git/config, .git/hooks/, .git/refs/, .git/HEAD, .git/info/exclude \u2014 where a change alters what future git commands do rather than what the repository contains. Deliberately NARROW: all of .git/** would include COMMIT_EDITMSG and the index, which change during every ordinary commit, so the rule would fire constantly and be switched off. Reading is excluded \u2014 the Read and Grep tools never match \u2014 so opening .git/config to read it is not held; every other file tool is, including one this corpus does not know. It does NOT match .gitignore, .gitattributes or anything under .github/, which are tracked project files.",
|
|
1184
1235
|
match: {
|
|
1185
1236
|
any_of: [
|
|
1186
1237
|
{ kind: "execute_tool", file_glob: "**/.git/config" },
|
|
@@ -1188,6 +1239,11 @@ var fsVcsInternals = {
|
|
|
1188
1239
|
{ kind: "execute_tool", file_glob: "**/.git/refs/**" },
|
|
1189
1240
|
{ kind: "execute_tool", file_glob: "**/.git/HEAD" },
|
|
1190
1241
|
{ kind: "execute_tool", file_glob: "**/.git/info/exclude" }
|
|
1242
|
+
],
|
|
1243
|
+
none_of: [
|
|
1244
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
1245
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
1246
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
1191
1247
|
]
|
|
1192
1248
|
},
|
|
1193
1249
|
fixtures: {
|
|
@@ -1201,7 +1257,9 @@ var fsVcsInternals = {
|
|
|
1201
1257
|
file(".gitignore"),
|
|
1202
1258
|
file(".gitattributes"),
|
|
1203
1259
|
file(".github/CODEOWNERS"),
|
|
1204
|
-
file("src/index.ts")
|
|
1260
|
+
file("src/index.ts"),
|
|
1261
|
+
file(".git/config", "Read"),
|
|
1262
|
+
file(".git/hooks/pre-commit", "Grep")
|
|
1205
1263
|
]
|
|
1206
1264
|
}
|
|
1207
1265
|
};
|
|
@@ -1512,7 +1570,7 @@ var blockProdConfigEdit = {
|
|
|
1512
1570
|
severity: "high",
|
|
1513
1571
|
defaultAction: "require_approval",
|
|
1514
1572
|
title: "Approve production config edits",
|
|
1515
|
-
description: "Routes edits to production configuration files to human approval. Matched by path: `*.prod.*` and `*.production.*`, the bare `prod.*` / `production.*` spellings, and any file under a `prod/` or `production/` directory. Prose is excluded \u2014 `.md`, `.mdx` and `.txt` never match \u2014 so writing a runbook under `docs/production/` does not ask for approval to change production. It matches the PATH only: it cannot tell a real production config from a file that merely spells prod in its name, and it MISSES a production config named something else entirely, such as `values-live.yaml`.",
|
|
1573
|
+
description: "Routes edits to production configuration files to human approval. Matched by path: `*.prod.*` and `*.production.*`, the bare `prod.*` / `production.*` spellings, and any file under a `prod/` or `production/` directory. Reading is excluded \u2014 the `Read` and `Grep` tools never match \u2014 so opening a production config to look at it does not ask for approval; every other file tool does, including one this corpus does not know. Prose is excluded too \u2014 `.md`, `.mdx` and `.txt` never match \u2014 so writing a runbook under `docs/production/` does not ask for approval to change production. It matches the PATH only: it cannot tell a real production config from a file that merely spells prod in its name, and it MISSES a production config named something else entirely, such as `values-live.yaml`.",
|
|
1516
1574
|
match: {
|
|
1517
1575
|
any_of: [
|
|
1518
1576
|
{ kind: "execute_tool", file_glob: "**/*.prod.*" },
|
|
@@ -1523,6 +1581,9 @@ var blockProdConfigEdit = {
|
|
|
1523
1581
|
{ kind: "execute_tool", file_glob: "**/production/**" }
|
|
1524
1582
|
],
|
|
1525
1583
|
none_of: [
|
|
1584
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
1585
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
1586
|
+
{ kind: "execute_tool", label: "{Read,Grep}" },
|
|
1526
1587
|
{ kind: "execute_tool", file_glob: "**/*.md" },
|
|
1527
1588
|
{ kind: "execute_tool", file_glob: "**/*.mdx" },
|
|
1528
1589
|
{ kind: "execute_tool", file_glob: "**/*.txt" }
|
|
@@ -1540,7 +1601,9 @@ var blockProdConfigEdit = {
|
|
|
1540
1601
|
file("docs/production/README.md"),
|
|
1541
1602
|
file("config/database.dev.yml"),
|
|
1542
1603
|
file("src/index.ts"),
|
|
1543
|
-
file("package.json")
|
|
1604
|
+
file("package.json"),
|
|
1605
|
+
file("config/database.prod.yml", "Read"),
|
|
1606
|
+
file("k8s/production/deployment.yaml", "Grep")
|
|
1544
1607
|
]
|
|
1545
1608
|
}
|
|
1546
1609
|
};
|
|
@@ -2513,7 +2576,7 @@ var requireAuthOnPiiEndpoints = {
|
|
|
2513
2576
|
severity: "medium",
|
|
2514
2577
|
defaultAction: "require_approval",
|
|
2515
2578
|
title: "Review API endpoint changes for auth",
|
|
2516
|
-
description: "Routes edits to API route/handler SOURCE files to human approval so a reviewer can confirm authentication is present on new or changed endpoints. Matched by path: a source file under routes/, handlers/ or controllers/; an api/ directory nested inside a source tree (src/api/, app/api/, pages/api/); Next's route.ts convention; and the <name>.controller.* / <name>.routes.* spellings. HEURISTIC: a path signal only \u2014 it cannot inspect the edit for a missing auth check or exposed PII, so treat a match as confirm auth on this endpoint, not as a finding. It deliberately does NOT match every file in a package merely NAMED api, nor a client-side router table such as routes.tsx, which defines no endpoint; and it MISSES endpoints declared inline in a server file or by a framework convention not listed above.",
|
|
2579
|
+
description: "Routes edits to API route/handler SOURCE files to human approval so a reviewer can confirm authentication is present on new or changed endpoints. Matched by path: a source file under routes/, handlers/ or controllers/; an api/ directory nested inside a source tree (src/api/, app/api/, pages/api/); Next's route.ts convention; and the <name>.controller.* / <name>.routes.* spellings. Reading is excluded \u2014 the Read and Grep tools never match \u2014 so opening an endpoint's source to read it does not ask for approval; every other file tool does, including one this corpus does not know. HEURISTIC: a path signal only \u2014 it cannot inspect the edit for a missing auth check or exposed PII, so treat a match as confirm auth on this endpoint, not as a finding. It deliberately does NOT match every file in a package merely NAMED api, nor a client-side router table such as routes.tsx, which defines no endpoint; and it MISSES endpoints declared inline in a server file or by a framework convention not listed above.",
|
|
2517
2580
|
match: {
|
|
2518
2581
|
any_of: [
|
|
2519
2582
|
{ kind: "execute_tool", file_glob: `**/routes/**/*.${SOURCE}` },
|
|
@@ -2525,6 +2588,11 @@ var requireAuthOnPiiEndpoints = {
|
|
|
2525
2588
|
{ kind: "execute_tool", file_glob: `**/route.${SOURCE}` },
|
|
2526
2589
|
{ kind: "execute_tool", file_glob: `**/*.controller.${SOURCE}` },
|
|
2527
2590
|
{ kind: "execute_tool", file_glob: `**/*.routes.${SOURCE}` }
|
|
2591
|
+
],
|
|
2592
|
+
none_of: [
|
|
2593
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
2594
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
2595
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
2528
2596
|
]
|
|
2529
2597
|
},
|
|
2530
2598
|
fixtures: {
|
|
@@ -2538,7 +2606,9 @@ var requireAuthOnPiiEndpoints = {
|
|
|
2538
2606
|
file("apps/api/README.md"),
|
|
2539
2607
|
file("apps/api/package.json"),
|
|
2540
2608
|
file("apps/api/src/lib/logger.ts"),
|
|
2541
|
-
file("apps/web/src/app/routes.tsx")
|
|
2609
|
+
file("apps/web/src/app/routes.tsx"),
|
|
2610
|
+
file("src/api/users.ts", "Read"),
|
|
2611
|
+
file("src/controllers/payments.ts", "Grep")
|
|
2542
2612
|
]
|
|
2543
2613
|
}
|
|
2544
2614
|
};
|
|
@@ -3095,7 +3165,7 @@ var tiTestConfigEdit = {
|
|
|
3095
3165
|
severity: "medium",
|
|
3096
3166
|
defaultAction: "require_approval",
|
|
3097
3167
|
title: "Editing a test runner or coverage configuration",
|
|
3098
|
-
description: "Holds a file tool opening a test runner's or coverage tool's own configuration: Jest's `jest.config.*`, Vitest's `vitest.config.*` and legacy `vitest.workspace.*`, `pytest.ini`, `pytest.toml` and their dotted forms, `tox.ini`, Mocha's `.mocharc.*`, PHPUnit's `phpunit.xml`, `phpunit.xml.dist` and `phpunit.dist.xml`, `codecov.yml`, nyc's `.nycrc*` and `nyc.config.*`, `.c8rc`, coverage.py's `.coveragerc`, `karma.conf.*`, and the Playwright and Cypress configs. One line in any of these can exclude a failing file, lower a coverage threshold or retry a flaky test until it passes. File tools carry a path and no content, so it cannot tell a harmless edit from a weakening one
|
|
3168
|
+
description: "Holds a file tool opening a test runner's or coverage tool's own configuration: Jest's `jest.config.*`, Vitest's `vitest.config.*` and legacy `vitest.workspace.*`, `pytest.ini`, `pytest.toml` and their dotted forms, `tox.ini`, Mocha's `.mocharc.*`, PHPUnit's `phpunit.xml`, `phpunit.xml.dist` and `phpunit.dist.xml`, `codecov.yml`, nyc's `.nycrc*` and `nyc.config.*`, `.c8rc`, coverage.py's `.coveragerc`, `karma.conf.*`, and the Playwright and Cypress configs. One line in any of these can exclude a failing file, lower a coverage threshold or retry a flaky test until it passes. Reading is excluded \u2014 the `Read` and `Grep` tools never match \u2014 so opening one of these configs to read it is not held; every other file tool is, including one this corpus does not know. File tools carry a path and no content, so it still cannot tell a harmless edit from a weakening one. Deliberately NOT matched: general files that can also hold test settings \u2014 `pyproject.toml`, `setup.cfg`, `package.json`, `vite.config.*` \u2014 and test files themselves, since editing a test is how a test gets fixed. Misses test settings kept in those general files, and a config at a path passed with `--config`.",
|
|
3099
3169
|
match: {
|
|
3100
3170
|
any_of: [
|
|
3101
3171
|
{ kind: "execute_tool", file_glob: "**/jest.config.{js,ts,mjs,mts,cjs,cts,json}" },
|
|
@@ -3120,6 +3190,11 @@ var tiTestConfigEdit = {
|
|
|
3120
3190
|
kind: "execute_tool",
|
|
3121
3191
|
file_glob: "**/{playwright,cypress}.config.{js,ts,mjs,mts,cjs,cts}"
|
|
3122
3192
|
}
|
|
3193
|
+
],
|
|
3194
|
+
none_of: [
|
|
3195
|
+
// A read is not an edit. Named as a deny-list rather than an `any_of` over
|
|
3196
|
+
// the write tools, so an unknown file tool on a matched path still holds.
|
|
3197
|
+
{ kind: "execute_tool", label: "{Read,Grep}" }
|
|
3123
3198
|
]
|
|
3124
3199
|
},
|
|
3125
3200
|
fixtures: {
|
|
@@ -3152,7 +3227,9 @@ var tiTestConfigEdit = {
|
|
|
3152
3227
|
file("jest.setup.js"),
|
|
3153
3228
|
file(".env.test"),
|
|
3154
3229
|
file("docs/testing.md"),
|
|
3155
|
-
file("biome.json")
|
|
3230
|
+
file("biome.json"),
|
|
3231
|
+
file("vitest.config.ts", "Read"),
|
|
3232
|
+
file("jest.config.js", "Grep")
|
|
3156
3233
|
]
|
|
3157
3234
|
}
|
|
3158
3235
|
};
|
|
@@ -3638,8 +3715,8 @@ function isPack(value) {
|
|
|
3638
3715
|
}
|
|
3639
3716
|
|
|
3640
3717
|
// src/stamp.ts
|
|
3641
|
-
var CATALOG_VERSION = "0.2.
|
|
3642
|
-
var CATALOG_PUBLISHED_AT = "2026-09-
|
|
3718
|
+
var CATALOG_VERSION = "0.2.1";
|
|
3719
|
+
var CATALOG_PUBLISHED_AT = "2026-09-29T03:49:22Z";
|
|
3643
3720
|
|
|
3644
3721
|
// src/rules.ts
|
|
3645
3722
|
var RULES_BY_PACK = {
|