From df08857919f497357e1bd34f80de9e2413d53fb8 Mon Sep 17 00:00:00 2001 From: Loveneesh Dhir Date: Fri, 2 Oct 2026 23:26:12 +0530 Subject: [PATCH 1/3] docs: refresh README and add community health files Rewrite the README for first-time users: a quick start right after the intro, a recording of a real install, the 74 guardrails by pack, block/ask/warn behavior per agent, tuning and custom-rule examples with real CLI output, and links to the Guard and OS product pages. The detailed reference sections keep their technical content. Add CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md (Contributor Covenant 2.1) and a pull request template, so GitHub shows the Contributing, Security and Code of conduct tabs. Co-Authored-By: Claude Opus 5.5 --- .github/assets/agenttrail-guard-demo.svg | 1 + .github/pull_request_template.md | 17 + CODE_OF_CONDUCT.md | 83 ++ CONTRIBUTING.md | 114 +++ README.md | 1029 ++++++++++++++++------ SECURITY.md | 43 + 6 files changed, 1007 insertions(+), 280 deletions(-) create mode 100644 .github/assets/agenttrail-guard-demo.svg create mode 100644 .github/pull_request_template.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/assets/agenttrail-guard-demo.svg b/.github/assets/agenttrail-guard-demo.svg new file mode 100644 index 0000000..8961e4d --- /dev/null +++ b/.github/assets/agenttrail-guard-demo.svg @@ -0,0 +1 @@ +$$npm$npminstall$npminstall-g$npminstall-g@agenttrail/guardadded1packagein1s$a$ag$age$agen$agent$agentt$agenttr$agenttra$agenttrai$agenttrail$agenttrail-$agenttrail-g$agenttrail-gu$agenttrail-gua$agenttrail-guar$agenttrail-guard$agenttrail-guardinit$agenttrail-guardinit--agent$agenttrail-guardinit--agentclaudeInstalledagenttrail-guard@agenttrail-guard(scope:user).RestartClaudeCodefortheguardtostartenforcing.Wrote~/.agenttrail/guard/config.jsonWrote~/.agenttrail/guard/guardrails.jsonguardraillibraryv0.2.0,published13daysagoRun`agenttrail-guardstatus`anytimetoseewhatithasbeendoing.$clear$agenttrail-guardstatusClaudeCodeEnforcement:ON·74guardrailsacross11of11packsActivebundle:~/.claude/plugins/cache/agenttrail-guard/agenttrail-guard/0.4.0guardraillibraryv0.2.0,published13daysagoCursorEnforcement:NOTINSTALLED—run`agenttrail-guardinit--agentcursor`.CodexCLIEnforcement:NOTINSTALLED—run`agenttrail-guardinit--agentcodex`.Nodecisionsrecordedyet.$n$np$npmi$npmin$npmins$npminst$npminsta$npminstal$npminstall-$npminstall-g@$npminstall-g@a$npminstall-g@ag$npminstall-g@age$npminstall-g@agen$npminstall-g@agent$npminstall-g@agentt$npminstall-g@agenttr$npminstall-g@agenttra$npminstall-g@agenttrai$npminstall-g@agenttrail$npminstall-g@agenttrail/$npminstall-g@agenttrail/g$npminstall-g@agenttrail/gu$npminstall-g@agenttrail/gua$npminstall-g@agenttrail/guar$agenttrail-guardi$agenttrail-guardin$agenttrail-guardini$agenttrail-guardinit-$agenttrail-guardinit--$agenttrail-guardinit--a$agenttrail-guardinit--ag$agenttrail-guardinit--age$agenttrail-guardinit--agen$agenttrail-guardinit--agentc$agenttrail-guardinit--agentcl$agenttrail-guardinit--agentcla$agenttrail-guardinit--agentclau$agenttrail-guardinit--agentclaud$c$cl$cle$clea$agenttrail-guards$agenttrail-guardst$agenttrail-guardsta$agenttrail-guardstat$agenttrail-guardstatu \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..43d4465 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ + + +## What this changes + + + +## How I checked it + + + +## Checklist + +- [ ] `pnpm lint`, `pnpm spell`, `pnpm typecheck`, and `pnpm test` pass +- [ ] Behavior changes have regression tests +- [ ] The rebuilt hook bundle is included, if this change affects it +- [ ] Commit messages follow Conventional Commits +- [ ] No secrets, tokens, or private project details in code, tests, or screenshots diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..25e6f94 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [security@agenttrail.sh](mailto:security@agenttrail.sh). All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5d951eb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,114 @@ + + +# Contributing to AgentTrail Guard + +Thanks for helping make AI coding agents safer to run. Bug reports, rule ideas, +documentation fixes, and integration fixes are all welcome. You do not need to write code: +a small, reproducible example of an unexpected block or a missed action is one of the most +useful contributions. + +By taking part, you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md). To report a +security vulnerability, follow [SECURITY.md](SECURITY.md) instead of opening an issue. + +## Where does my contribution go? + +Code changes to Guard itself land here; the rules it enforces live in +[agenttrailhq/guardrails](https://github.com/agenttrailhq/guardrails). The README explains +[how the two fit together](README.md#guard-and-guardrails). + +| You want to… | Where it goes | +| --- | --- | +| Report a bundled rule firing on a legitimate command (a false positive) | [Guardrails issues](https://github.com/agenttrailhq/guardrails/issues) | +| Report a dangerous command no rule catches | [Guardrails issues](https://github.com/agenttrailhq/guardrails/issues) | +| Contribute a rule, with matching and non-matching examples | [Guardrails](https://github.com/agenttrailhq/guardrails#contributing-a-rule) | +| Fix a hook, install, `status`, `scan`, or CLI problem | [Guard issues](https://github.com/agenttrailhq/guard/issues) and pull requests | +| Improve documentation | The repository the document lives in | + +If you are not sure which repository fits, open a +[Guard issue](https://github.com/agenttrailhq/guard/issues) and describe what you saw. + +## Reporting a bug or a false positive + +Include: + +- your Guard version (`agenttrail-guard --version`); +- your agent and its version; +- your operating system; +- the command or tool call involved; +- the guardrail id from `status`; +- what you expected to happen. + +Share sanitized examples only: remove secrets, tokens, internal hostnames, and private +project details. `scan --review` shows you exactly what a report contains before you share +it. + +## Trying a rule locally + +To try a rule before proposing it, use `agenttrail-guard guardrails validate `, +then `agenttrail-guard guardrails add `. The rule format and what a contributed +rule needs are in the +[guardrails README](https://github.com/agenttrailhq/guardrails#contributing-a-rule). + +## Development setup + +Use the pnpm version pinned in `package.json`. CI runs on Node.js 22. + +```sh +git clone https://github.com/agenttrailhq/guard.git +cd guard +pnpm install --frozen-lockfile +pnpm build +pnpm test +``` + +## Contribute with your coding agent + +Using Claude Code, Cursor, or Codex CLI? From a fresh clone, paste this prompt into your agent +and fill in the last line of step 3: + +```text +You are helping me contribute to AgentTrail Guard, the repository in this directory. +1. Read README.md and CONTRIBUTING.md before changing anything. +2. Run `pnpm install --frozen-lockfile`, `pnpm build`, and `pnpm test`, and tell me about + any failure before we start. +3. Help me make this change: . +4. Add or update tests for any behavior change. +5. Run `pnpm lint`, `pnpm spell`, `pnpm typecheck`, and `pnpm test`. If the change affects + the hook bundle, run `pnpm build` and include plugin/scripts/guard-hook.mjs. +6. Suggest a Conventional Commit message. Do not push, publish, or add secrets. +``` + +With Guard installed, your agent's commands are checked while it works on Guard. + +## Before you open a pull request + +Run the checks used in CI: + +```sh +pnpm lint +pnpm spell +pnpm typecheck +``` + +- **Commit messages:** use Conventional Commits, such as `docs: clarify installation` or + `fix: handle missing hook files`. CI checks commit messages on pull requests. +- **Tests:** add regression coverage for behavior changes. +- **The hook bundle:** it is committed because the plugin runs it directly. CI rebuilds it + and checks that it matches the committed copy, so include the rebuilt bundle when a code + change affects it. + +## Find your way around + +| Path | Contents | +| --- | --- | +| [src/commands/](src/commands/) | CLI commands: setup, status, scans, and guardrail management | +| [src/engine/](src/engine/) | Rule matching and policy evaluation | +| [src/core/](src/core/) | Agent payload mapping, configuration, logs, and report generation | +| [src/__tests__/](src/__tests__/) | Unit, integration, privacy, and runtime checks | +| [plugin/](plugin/) | Claude Code plugin and bundled scripts | + +## License + +AgentTrail Guard is licensed under the [Apache License 2.0](LICENSE). Under section 5 of +that license, a contribution you submit for inclusion is provided under the same terms, +unless you state otherwise. diff --git a/README.md b/README.md index 97701f8..85d44c5 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,701 @@ - + -# `@agenttrail/guard` +
-Local guardrails for Claude Code, Cursor and Codex CLI. Before your AI agent runs a command or -touches a file, the guard checks it against a guardrail library and answers one of three things: -**go ahead**, **ask the human**, or **no**. + + + + AgentTrail + + -**No account, no sign-up.** Out of the box the guard makes no network calls and sends -us nothing — not telemetry, not the commands it sees, not an install ping. (The one -exception, crash reporting, is off unless you turn it on — see [Crash -reporting](#crash-reporting).) +# AgentTrail Guard: open-source guardrails for AI coding agents -## Install +**Check your AI coding agent's commands and file changes before they run.**
+Works with **Claude Code**, **Cursor**, and **Codex CLI**. Local, free, and no account needed. + +[![CI](https://github.com/agenttrailhq/guard/actions/workflows/ci.yml/badge.svg)](https://github.com/agenttrailhq/guard/actions/workflows/ci.yml) +[![npm version](https://img.shields.io/npm/v/@agenttrail/guard?color=0748FE&label=npm)](https://www.npmjs.com/package/@agenttrail/guard) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0748FE)](LICENSE) +[![Node.js 20+](https://img.shields.io/badge/node-%3E%3D20-0748FE)](https://nodejs.org/) + +[Quick start](#quick-start) · +[Guardrails](#what-guard-catches) · +[Reference](#reference) · +[Changelog](CHANGELOG.md) + +
+ +[AgentTrail Guard](https://www.agenttrail.sh/agenttrail-guard) is a free, open-source CLI +that connects to your coding agent's hooks and checks every tool call that reaches them +against [74 inspectable guardrails](#what-guard-catches) before it runs. Guard can block a +matching call, send it to you for approval, or record it as a warning, depending on the +agent. When nothing matches, your agent's normal permission flow continues. + +![Terminal recording: npm install -g @agenttrail/guard, then agenttrail-guard init --agent claude installs the Claude Code plugin, then agenttrail-guard status shows enforcement on with 74 guardrails across 11 of 11 packs](.github/assets/agenttrail-guard-demo.svg) + +## Quick start + +**You need:** Node.js 20 or newer, npm, and at least one of Claude Code, Cursor, or Codex CLI. + +### 1. Install Guard + +```sh +npm install -g @agenttrail/guard +``` + +### 2. Connect each agent you use + +```sh +agenttrail-guard init --agent claude # Claude Code +agenttrail-guard init --agent cursor # Cursor +agenttrail-guard init --agent codex # Codex CLI +``` + +Each setup is independent, and settings are shared under `~/.agenttrail/guard/`. To see +exactly what `init` would change first, add `--print`; it changes nothing. + +`init` demonstrates the engine by evaluating a synthetic `rm -rf /` and showing its +decision. **The command is never executed.** This checks the engine; you still need to +complete your agent's setup below. + +### 3. Finish agent setup and check status + +- **Claude Code:** restart Claude Code to load the plugin. +- **Cursor:** use the agent in an editor window. Check Cursor's Hooks tab, and restart + Cursor if Guard's entries do not appear. +- **Codex CLI:** start Codex, open `/hooks`, and approve **each** `agenttrail-guard` entry. + Guard does not run until you approve it. + +```sh +agenttrail-guard status +``` + +`status` shows what is enforcing in each agent, recent decisions, and settings that need +attention. For Codex, `ON` means the hook is installed and well-formed; confirm approval in +Codex's own `/hooks` screen, because Guard cannot read that state. + +### Upgrading and uninstalling + +To upgrade, install the latest release, re-run `init` for each agent you use, and restart +that agent. Your settings are kept, and Codex keeps the approval you already gave. Before +1.0, a minor release can include a breaking change, so skim the [changelog](CHANGELOG.md) +first. + +```sh +npm install -g @agenttrail/guard@latest +agenttrail-guard init --agent claude # and again for cursor or codex, if you use them +``` + +To remove Guard from an agent, run `uninstall` for it. It removes only what Guard added, and +keeps your settings in `~/.agenttrail/guard/`. + +```sh +agenttrail-guard uninstall --agent claude # or: --agent cursor, --agent codex +``` + +What each step changes is covered in [Updating](#updating) and [Uninstall](#uninstall). + +Prefer a guided walkthrough? See the +[AgentTrail Guard installation guide](https://www.agenttrail.sh/get-started/guard). + +## Why AgentTrail Guard? + +Your coding agent runs shell commands and edits files all day, faster than you can review +each one. Most of those calls are exactly what you want. The ones that aren't, like +`git reset --hard` over a day of uncommitted work, `rm -rf` on the wrong path, +`terraform apply -auto-approve`, or a read of your `.env`, tend to be the ones you can't take +back. Guard puts a rule-based check on every tool call that reaches its hooks, in every +session, so the risky ones stop and explain themselves before they run, and everything else +carries on as normal. + +- **Local, with no account.** Guard runs on your machine and needs no sign-up. Out of the + box it makes no network calls; the one exception is crash reporting, which is off unless + you turn it on. +- **Rules you can read.** Every guardrail is plain data with a title, severity, default + action, and a description of what it deliberately misses. +- **Tunable without turning it off.** Allow one command shape for one rule, change a rule's + action, disable a pack, or add your own rule. +- **One rule set across agents.** Claude Code, Cursor, and Codex CLI share the same settings + in `~/.agenttrail/guard/`. +- **See what it caught.** `agenttrail-guard status` shows recent decisions, and `scan` turns + past agent sessions into a self-contained HTML report. +- **Apache-2.0.** The matching engine and hooks live in this repository; the rule library + lives in [agenttrailhq/guardrails](https://github.com/agenttrailhq/guardrails). + +## How AgentTrail Guard works + +1. **Your agent proposes a tool call.** Its hook sends Guard the tool name and inputs. +2. **Guard evaluates it locally.** The engine matches supported command and path fields + against the bundled guardrails and your custom rules, applying your settings. +3. **Your agent receives a decision.** A matching rule can block, ask, or warn; + [each agent handles these differently](#block-ask-and-warn). +4. **You can inspect the result.** Matched calls are recorded in a local, scrubbed decision + log; `agenttrail-guard status` shows recent decisions. + +Guard uses rule matching, not an LLM reviewing your code. It can stop a matching call only +when your agent sends it through the installed hooks, it does not inspect file contents, and +it does not guarantee that every dangerous action will be caught. On an internal error it +**fails open**: your agent's normal permission flow continues. Read the +[coverage and runtime limits](#what-the-guard-does-not-do) before relying on it. + +## See AgentTrail Guard in action + +The table below is real output: each row is a Claude Code `PreToolUse` call passed to +Guard's hook, and the reason Guard returned before anything ran. + +| Your agent proposes | Guard's answer to Claude Code | +| --- | --- | +| `git reset --hard origin/main` | **Deny:** agenttrail-guard blocked this: git reset --hard discards uncommitted work (guardrail wt.reset-hard) | +| `terraform apply -auto-approve` | **Ask:** agenttrail-guard needs a person to approve this: Terraform apply/destroy without the confirmation prompt (guardrail pi.terraform-auto-approve) | +| Read `.env` | **Warn:** agenttrail-guard is warning about this: Flag reading a .env file (guardrail block-env-file-read) | +| `npm test` | No output. Claude Code's normal permission flow decides. | +| `git commit -m "docs: explain why rm -rf / is dangerous"` | No output. Mentioning a command is not running it. | + +In a real Claude Code session, an agent told it was only dev data reached for +`npx prisma db push --force-reset`, and `dd.accept-data-loss` blocked it before it ran. +[Read the case study](https://www.agenttrail.sh/blog/its-only-dev-data-force-reset). + +Every decision names its guardrail, and every guardrail explains itself (the description is +wrapped and trimmed here): + +```console +$ agenttrail-guard guardrails show wt.reset-hard + + wt.reset-hard working-tree · severity: high + git reset --hard discards uncommitted work + + Action block (shipped default) + Enforcing yes + + Discards every uncommitted change in the working tree, irrecoverably — there is no + reflog for work that was never committed. Does NOT match `git restore` (see + wt.restore-path), `git checkout -- .` (see wt.checkout-discard) [...] +``` + +## What Guard catches + +Guard ships with **74 guardrails across 11 packs**. Packs are named for the harm they +prevent, not the technique used to spot it. By default, 13 guardrails block, 50 ask, and +11 warn. + +| Pack | Rules | Representative examples | Defaults | +| --- | ---: | --- | --- | +| `working-tree` | 9 | `git reset --hard`, `git clean -fd`, force-push, `rm -rf` | 5 block, 4 ask | +| `destructive-data` | 8 | `rm -rf` on an absolute path, dropping a database, destructive SQL, deleting Docker volumes | 5 block, 3 ask | +| `prod-infra` | 8 | `terraform apply -auto-approve`, `kubectl delete`, Helm uninstall or rollback, production config edits | 8 ask | +| `secret-exposure` | 10 | Reading `.env` or credential files, printing tokens, hard-coded secrets in commands, public bucket ACLs | 1 block, 4 ask, 5 warn | +| `rce-supply-chain` | 6 | Piping `curl` into a shell, running code from a URL, TLS verification off, a redirected registry | 1 block, 5 ask | +| `safety-bypass` | 7 | `--no-verify`, admin merges past branch protection, disabling hooks, erasing shell history | 6 ask, 1 warn | +| `privilege-supply-chain` | 6 | `sudo` writes, `chmod 777`, IAM grants, publishing packages, new dependencies | 5 ask, 1 warn | +| `file-scope` | 4 | Writes to system directories, git internals, CI definitions, the agent's own config | 4 ask | +| `agent-context` | 6 | Editing agent instructions or memory, adding MCP servers, installing skills, turning off approvals | 6 ask | +| `test-integrity` | 6 | Deleting tests, blanket snapshot updates, bypassing coverage gates, skip-CI markers | 2 ask, 4 warn | +| `exfiltration` | 4 | Reverse shells, public tunnels, file uploads, paste services | 1 block, 3 ask | + +Run `agenttrail-guard guardrails list` for every rule with its current action, or +`guardrails show ` for one rule in full. The source for every rule is in +[agenttrailhq/guardrails](https://github.com/agenttrailhq/guardrails#the-eleven-packs). + +### Guard and Guardrails + +- **AgentTrail Guard** (this repository, `@agenttrail/guard`) is the tool: the agent hooks, + the matching engine, the CLI, the local decision log, and the scan report. +- **Guardrails** ([agenttrailhq/guardrails](https://github.com/agenttrailhq/guardrails), + `@agenttrail/guardrails`) is the rule library: plain data, with matching and non-matching + examples for every rule. + +Guard bundles the library, so you do not need to install it separately. Rules update when +you update Guard; nothing is fetched at runtime. + +### Block, ask, and warn + +A rule's action decides what happens when it matches. Each agent's hook system supports a +different subset, so the same action can land differently: + +| Action | Claude Code | Cursor | Codex CLI | +| --- | --- | --- | --- | +| `block` | Blocks matching hooked calls, with the guardrail's reason | Blocks matching hooked calls | Blocks matching hooked calls; Codex shows the reason | +| `ask` | Requests approval; broad permission allow rules can suppress the prompt | Requests approval for terminal commands; blocks other calls that need approval | Blocks calls that need approval, because Codex runs the action anyway when a hook asks | +| `warn` | Allowed, with a warning message | Allowed with no message; recorded locally | Allowed; shown in the terminal UI, not in `codex exec` | + +Every match is recorded in the local decision log, which `agenttrail-guard status` reads. + +## Tune a guardrail + +A guardrail that fires on something legitimate should get narrower, not switched off. +`status` identifies the rule that fires most often and suggests a command to allow that +specific pattern. Start by reading the rule: + +```sh +agenttrail-guard status +agenttrail-guard guardrails show +``` + +### Allow one command shape + +For a repeat false positive, prefer a narrow exception. `allow` silences only the named rule +for matching commands; the rule keeps enforcing everywhere else, and every other rule is +untouched. Review the pattern `status` suggests before applying it. + +```console +$ agenttrail-guard guardrails allow require-approval-rm-rf "rm -rf ./tmp" +Allowlisted. require-approval-rm-rf no longer matches "rm -rf ./tmp". +The guardrail still fires on everything else, and the other 73 guardrails are unchanged. +``` + +It refuses a pattern broad enough to disable the rule quietly: + +```console +$ agenttrail-guard guardrails allow require-approval-rm-rf "*" +agenttrail-guard guardrails allow: refusing that pattern — it also matches `ls -la`, `git status`, `pnpm install` and 3 more, which this guardrail has nothing to do with. That is a global mute for this guardrail, not a silence for one shape — the guardrail would keep showing as enabled while enforcing nothing. +``` + +### Change an action, or restore the default + +```sh +agenttrail-guard guardrails set-action warn # record, don't stop +agenttrail-guard guardrails reset # back to the shipped default +agenttrail-guard guardrails reset --all # undo every change you made +``` + +`warn` records the match without blocking the call. `reset --all` never touches rules you +added yourself. + +All bundled packs are enabled by default. Your changes take effect on the next tool call. A +new pack shipped in an update is enabled unless you disable it. + +### Add your own rule + +Write a rule with at least one command it must match (`block`) and one it must not +(`allow`): + +```json +{ + "id": "team.deploy-prod-script", + "category": "prod-infra", + "severity": "high", + "defaultAction": "require_approval", + "title": "Our production deploy script", + "description": "Holds `pnpm deploy:prod` for approval. Does not match the staging script.", + "match": { + "any_of": [ + { + "kind": "execute_tool", + "label": "Bash", + "detail_matches": ["\\bpnpm\\s+(run\\s+)?deploy:prod\\b"] + } + ] + }, + "fixtures": { + "block": ["pnpm deploy:prod", "pnpm run deploy:prod"], + "allow": ["pnpm deploy:staging"] + } +} +``` + +Validate it against Guard's own evaluator, then add it: + +```console +$ agenttrail-guard guardrails validate my-rule.json +Valid. team.deploy-prod-script (ask) + block fixture pnpm deploy:prod → matched PASS + block fixture pnpm run deploy:prod → matched PASS + allow fixture pnpm deploy:staging → not matched PASS + + Schema checked with the same `parseRule` CI runs. Fixtures were run through the + guard's own evaluator — the one that will run on your machine. -Install the CLI once, globally. The guard is a machine-level control: it keeps its config -in `~/.agenttrail/guard/`, and its commands (`status`, `scan`, `guardrails`) are run again -and again — so it belongs on your PATH, at a version you chose, rather than re-resolved on -every run. +$ agenttrail-guard guardrails add my-rule.json +``` + +In JSON, "ask" is written `require_approval`. The CLI accepts `ask`, but a hand-edited file +does not. See [Managing guardrails](#managing-guardrails) for every rule +command, and the +[guardrails rule format](https://github.com/agenttrailhq/guardrails#a-rule-start-to-finish) +for all fields. + +## Scan past sessions + +Your agents already keep session transcripts on disk. `scan` replays the recorded tool +calls through your current guardrails and writes a local report: + +```sh +agenttrail-guard scan --agent claude # reads ~/.claude/projects +agenttrail-guard scan --agent cursor # reads ~/.cursor/projects/*/agent-transcripts/ +agenttrail-guard scan --agent codex # reads ~/.codex/sessions +``` + +You get a summary in the terminal and a single self-contained +`agenttrail-guard-report.html`, opened in your browser (`--no-open` to skip, `--out ` +to choose where, `--json` for JSON only). The report loads nothing from outside itself. + +**A scan shows what would match now, not what was blocked then.** It applies today's +guardrails and settings to past calls, so its results can differ from what Guard enforced +during those sessions, or from sessions that ran before Guard was installed. + +Reports redact secrets, paths, and known identifying values, but redaction is not a +guarantee. Use `--review` and read the report before sharing it. The review output can +include raw MCP values that the report itself redacts. Details: +[Seeing what already happened](#seeing-what-already-happened). + +## The commands + +| Command | What it does | +| --- | --- | +| `agenttrail-guard init --agent ` | Install the integration, seed settings, and demonstrate the engine. Safe to re-run. | +| `agenttrail-guard status` | Inspect installation state, recent decisions, and settings that need attention. | +| `agenttrail-guard guardrails` | List, inspect, customize, and validate guardrails. | +| `agenttrail-guard scan --agent ` | Check past sessions and create a local HTML report. | +| `agenttrail-guard crash-report` | Manage opt-in crash reports. Off by default. | +| `agenttrail-guard uninstall --agent ` | Remove that integration and keep your settings. | + +Run `agenttrail-guard --help` for CLI help, or `agenttrail-guard guardrails --help` for rule +management. + +## Agent support + +Guard runs in all three agents through each agent's own hooks, so coverage differs by agent. +These are the behaviors documented and measured in this repository; see the detailed limits +before relying on an integration. How each agent handles `block`, `ask`, and `warn` is in +[Block, ask, and warn](#block-ask-and-warn). + +| Agent | Setup and coverage notes | +| --- | --- | +| Claude Code | Installed as a plugin. [Coverage limits](#what-the-guard-does-not-do). | +| Cursor | Use an editor window. Sandbox and allowlist settings can bypass checks. Cursor cloud agents are outside its coverage. [Cursor's limits](#cursors-limits). | +| Codex CLI | Approve every entry in `/hooks`. Measured on 0.154.0; Windows and Codex Desktop are unverified. Codex Cloud is outside its coverage. [Codex's limits](#codexs-limits). | + +## Privacy at a glance + +- **Reads:** the tool call your agent is about to make: its tool name, command, and file + path. `scan` also reads the transcripts already on your disk. +- **Keeps:** your settings and a scrubbed decision log in `~/.agenttrail/guard/`, capped + at 1 MiB, with older records dropped past 30 days. Clear it with + `agenttrail-guard status --clear-history`. +- **Sends:** nothing by default. Crash reporting is opt-in, sends stack traces only, and + ships with no endpoint configured. + +Scrubbing catches common secrets but is not complete: see the limits below and +[Files it keeps](#files-it-keeps). The AgentTrail website summarizes +[what Guard reads, keeps, and sends](https://www.agenttrail.sh/security#guard). + +## What the guard does not do + +Guard depends on the hooks and permission behavior of each agent. It cannot check actions +that bypass those hooks or detect dangerous content inside a written file. In short: + +- It checks only calls that reach its hooks. Actions outside hooked tools are invisible, + and Cursor and Codex have more of these gaps than Claude Code. +- It matches commands and file **paths**, never file contents. An agent can write a script + and run it as a separate command; only the command is checked. +- It does not see what the agent fetches from the web. +- It **fails open**. A crash, a timeout, or bad output lets the call through to your + agent's normal permission flow. +- An `ask` can be skipped by your agent's own allow settings, and becomes a block on Codex. +- Redaction in the log and report is good, not complete. + +
+General coverage, permission, and redaction limits + +Read this before you rely on it. A security tool that overstates its coverage is +worse than one that names the hole. + +- **The guard does not see what the agent fetches from the web.** `WebFetch` is not + intercepted, and there are no website guardrails in v1. The underlying engine can + match on a command string and on a file path — it has no matcher for a URL — so a + URL guardrail would have validated, installed, and then matched nothing, forever, + without telling anyone. Rather than ship a channel whose guardrails only appear to + work, v1 leaves web traffic out and says so here. `WebSearch` **is** covered, + because a search query is ordinary text on the command channel. +- **It does not stop an action it cannot see.** The guard runs as a Claude Code + `PreToolUse` hook, as Cursor `preToolUse` and `beforeShellExecution` hooks, and as + Codex `PreToolUse` and permission-request hooks. Anything the agent does outside a + hooked tool is invisible to it, and the other two apps have more of these gaps than + Claude Code: see [Cursor's limits](#cursors-limits) and + [Codex's limits](#codexs-limits). +- **A file guardrail matches the PATH, not the file's contents.** The engine reads a + command string and a file path; it has no matcher for what a write PUTS in a file. So a + guardrail on `Write` or `Edit` decides only by where the write lands, never by what it + contains — and **write-then-execute falls in the gap**: an agent can write a script (judged + by its path alone, its contents unseen) and then run it as a separate command. The run is + checked like any command, but if that command does not itself match a guardrail — + `./setup.sh`, say — the dangerous content that went into the file was never inspected. + Guard what a file's contents would DO by guarding the commands that act on it; a file-path + guardrail is a scope rule, not a content scanner. +- **A hold does not prompt when your settings already allow the tool.** A guardrail set + to `require_approval` (shown as `ask`) returns Claude Code's `ask` decision, but Claude + Code's own `permissions.allow` rules win over a hook: if your `settings.json` allows a + tool outright — `"Bash"`, `"Read"`, `"Edit"` — a call to that tool runs with no prompt, + and the hold never reaches you. The guard cannot change this, because Claude Code + decides, and the hook cannot see that it happened, so the call is still recorded as a + hold. `status` and `init` read your `settings.json` and tell you how many holds this + silences, naming the tools; a scoped allow like `"Bash(git:*)"` is narrower and is not + counted. +- **It does not redact what it lets through.** The guard decides about a command; it + never rewrites one. Secrets are redacted from what the guard itself *writes* — the + decision log, the `scan` report and a crash report all go through the scrubber — but + a command that contains a secret and is allowed still runs exactly as the agent + wrote it. +- **Its redaction is good, not complete.** Every command written to `events.jsonl` is + scrubbed first — AWS keys, GitHub and Slack tokens, JWTs, private keys, connection + strings, `.env`-style assignments, emails, and card numbers all become + `[REDACTED:…]`. What it does **not** catch is a secret passed as a bare command-line + argument, with no `=` or `:` between the name and the value: the patterns are + anchored to an assignment, so `--token abc123` reads as two ordinary words. Treat + the log as scrubbed, not sanitized. (The `scan` **report** goes further and does + redact a secret handed to a long flag such as `--token`, `--password` or `--key`; the + decision log does not.) +- **The `scan` report's name redaction is a deny-list for shell commands and an + allow-list for MCP.** Paths are redacted structurally, so that rule is total. Shell + names are not: the identifier redactor knows a fixed list of tools — container + runtimes, `kubectl` (resource names included), the database clients (host, database, + and the SQL passed to `-c` / `-e`), `ssh`, `git` commit messages and identity + settings, 1Password's `op` (item and vault names), and `gh` titles and bodies — and it + also redacts, in any command, a secret handed as a flag value (`--token`, + `--password`, `--key`), a UUID, a heredoc body (which becomes ``), and the text + of a `#` comment. A bare + operand naming a resource for a tool *not* on the list + survives, and a branch or tag given as a plain `git` operand (`git checkout my-branch`, + `git tag -a v1`) is kept readable on purpose. A **glued** short-flag password + (`mysql -pSECRET`) is not caught — only long secret flags and `.env`-style assignments + are. An `mcp__…` payload is handled the opposite way: every value is redacted to + `` by default and only the field names are kept, so no raw payload value reaches + the file. +- **A guardrail's id and title are printed as their author wrote them.** For the shipped + library that is our words. For a guardrail *you* added to `guardrails.json` it is yours, + and nothing redacts it: a title reading "AcmeCorp internal audit" appears in the report + verbatim. Paths and secrets pasted into a title *are* taken out — the title goes + through the same three passes as a command — but a name you chose is not something a + redactor can distinguish from a word. Run `scan --review` to read every line, commands + and guardrail names both, before the file is written. +- **The 30-day limit on the decision log is a ceiling, not a sweep.** Old records are + dropped when the log is compacted, and compaction happens when it crosses 1 MiB. On + a machine that stops using Claude Code and Cursor, the last half-megabyte of records + stays until either another decision is recorded or you run `status --clear-history`. + The bound that always holds is the size one. +- **It is one developer on one machine.** Nothing syncs, nothing is shared with a + team, and there is no approval queue. + +
+ +### Cursor's limits + +
+Cursor coverage and permission behavior + +Cursor's hooks see less than Claude Code's. In Cursor, the guard does not check these: + +- **Reads that Cursor shows as "Explored".** No hook runs for them, so a guardrail on a + file path does not see them. +- **Files Cursor has already attached to the conversation.** Cursor puts rule and + instruction files into the agent's context itself, with no tool call, so no hook fires. + The agent can then quote such a file even where a guardrail denies reading it: a + guardrail on a file path cannot stop content Cursor has already attached. +- **Everything a search can reach.** A `Grep` is checked on its folder, and on the folder + joined to its glob, so a search for `**/.env` under a project is checked as that path. + A brace glob such as `{.env,.env.local}` is not expanded, and searching the contents of + files under a folder is not covered: the search pattern is not read. +- **Tab edits.** Edits made with Cursor Tab are not covered. +- **The Agent Window.** It can skip hooks. Use Cursor's agent in an editor window; + `status` reminds you. +- **Approval, except for terminal commands.** Cursor shows its approval card for a + terminal command when the guard asks at `beforeShellExecution`. It does not enforce an + `ask` from `preToolUse`, so a guardrail that asks for approval of a file edit, a read, + a `Grep`, a `Delete` or an MCP call blocks that call instead, with the message + "agenttrail-guard needs a person to approve this, and Cursor cannot ask: " and the + guardrail's title and id. +- **A terminal command that Cursor's own run mode runs first.** This is the Cursor analogue + of Claude Code's "a hold does not prompt when your settings already allow the tool" (above). + In a **sandbox** run mode many commands run automatically with no card — and such a command + can bypass `beforeShellExecution` entirely, so the guard never evaluates it and there is no + `events.jsonl` record either. A command the sandbox cannot run — one that needs the network, + or writes outside it — is escalated to approval, where the guard does check it; so coverage + under a sandbox run mode is partial, and decided by Cursor, not by the guardrails. For the + guard to check **every** terminal command, use a non-sandbox run mode (Cursor's allowlist + mode without the sandbox), and keep the commands you want checked off Cursor's command + allowlist. Unlike Claude Code's `settings.json`, there is no documented, stable file for the + guard to read the run mode from, so `status` and `init` name the effect but cannot count it. + When the hook does run, the decision is recorded in `events.jsonl`. +- **Showing warnings.** A guardrail that warns is answered with no opinion. The match is + written to `events.jsonl` and counted by `status`, and Cursor shows nothing. +- **Being named in the agent's reply.** Cursor shows no reason of its own, so what you read + is the agent retelling what it was handed. Every message names the guard, the guardrail's + title and its id, and the agent's copy also asks for the line to be repeated as it stands + — but the agent writes its own reply and may drop the name. Measured on Cursor 3.20.21: + the verdict was always right and the wording was the agent's own. `status`, `events.jsonl` + and `scan` are where each decision is recorded exactly. +- **Cursor's cloud agents.** They do not run user-level hooks, and the guard's entries + are in your user-level `~/.cursor/hooks.json`. + +**The guard's Claude Code plugin inside Cursor.** With Cursor's "Include third-party +plugins" setting on, Cursor can run the guard's Claude Code plugin and hand it Cursor's +payload. The plugin reads that as a Cursor call, and it still blocks what a guardrail +blocks. + +- When both of the guard's Cursor entries are in `~/.cursor/hooks.json`, the plugin + leaves approvals and the decision log to them, so each decision is asked for and + logged once. +- Without them, the plugin is the only checkpoint. It blocks any call that needs + approval, terminal commands included, with "agenttrail-guard needs a person to approve + this, and Cursor cannot ask: " and the guardrail's title and id, and it logs every + decision itself. + +
+ +### Codex's limits + +
+Codex CLI coverage, hook approvals, and tested version + +A deny reaches Codex intact: the command never runs, and Codex prints the guard's own +reason on screen and the model repeats it. What differs is what the hook can see, what +it can ask for, and what happens before you approve it. Everything below is measured on +**codex-cli 0.154.0**. + +- **Nothing runs until you approve the guard in `/hooks`.** An installed-but-unapproved + entry never fires. The command runs normally, no decision is recorded, and neither + Codex nor the guard mentions it — the only symptom of an unguarded machine is + silence. `status` cannot close this gap for you, because approval is recorded in a + file the guard does not read. +- **Changing an installed entry silently un-approves it.** Codex trusts the entry's + content, so editing the command, the timeout or the matcher by as little as one + character stops the hook firing, with no warning and no log line, until you approve + it again. That is why the installed entry is frozen and pinned by a test, and why a + guard release ships a new *script* rather than a new entry. Putting the entry back + exactly as it was restores the approval by itself. +- **An approval becomes a block.** Codex parses a hook that asks for human approval, + marks the hook run as failed, and **runs the action anyway** — so asking would be the + same as saying nothing. The guard therefore refuses a call it would have held on + Claude Code, and says in the reason that a person has to approve it. 50 of the 74 + bundled guardrails ask rather than block, so this is the difference you will meet + most often. Downgrade any of them in `~/.agenttrail/guard/config.json`, or with + `guardrails set-action warn`, if a block is too strong for your work. +- **Reads fire no hook at all.** Codex has no read tool: it reads files by running + shell commands. A guardrail on a *file path* therefore never sees a read, and the + read is only checked as far as its command text goes. The paths a file guardrail does + see are the ones inside an edit. +- **An edit written through the shell carries no path.** Codex's editing tool sends the + patch, and the guard reads the paths out of it. An edit the agent writes as a shell + command instead — a heredoc into `cat`, a `sed -i`, a redirect — arrives as an + ordinary command with no path field anywhere, so only the command guardrails apply to + it. Every app here has this gap; it is wider on Codex because the shell is the route + for more of what the agent does. +- **Every failure lets the action through, and there is no switch for that.** A crash, + a non-zero exit other than 2, output that is not JSON, one field Codex does not + recognise, or a hook that runs past its timeout: each is recorded as a failed hook + run and the tool call proceeds. Codex offers no fail-closed setting, so a guard that + cannot answer is a guard that is not there. It is also why the hook answers with + exactly three fields and waits on nothing. +- **A warning is invisible in `codex exec`.** In Codex's terminal UI a warning shows as + a hook line above the command. In a scripted `codex exec` run the text appears + nowhere at all — only that the hook completed. The match is still written to + `events.jsonl` and counted by `status`, which is where a warning is reliably read. +- **MCP tool calls are unverified here.** The guard checks a Codex MCP call by the same + `mcp__server__tool` name Claude Code uses, taken from Codex's own source. No MCP + server was configured on the machine these measurements come from, so that path is + reasoned rather than observed. +- **Windows and Codex Desktop are unverified.** Both appear in open reports of denies + being ignored, and neither was exercised here. +- **A shell tool under another name is seen but not read.** The guard's entry matches + every tool, so the hook does run — but the guard understands three shapes on Codex: + the shell tool named `Bash`, an edit through `apply_patch`, and an MCP call named + `mcp__server__tool`. Anything else yields no verdict and **no line in the decision + log**, so it is neither checked nor recorded. This is not hypothetical: Codex Desktop + reports its shell tool as `shell_command`, which means the guard would not check + commands there at all. +- **Codex Cloud is not covered.** It runs on OpenAI's machines and not yours, so it + never reads `~/.codex/hooks.json` and the guard is not in the loop at all. +- **An enterprise-managed config can switch user hooks off entirely.** With + `allow_managed_hooks_only = true` set in a managed configuration, Codex runs only the + hooks that configuration provides and skips yours, the guard's included. There is no + message about it; the guard simply never fires. +- **0.154.0 is the tested floor.** Every behaviour above was measured on that version. + Codex's hook surface is young and moving, so treat a newer release as unverified + until a deny, an approval and a warning have each been seen on it. -```sh -npm i -g @agenttrail/guard -``` +
+ +## AgentTrail Guard and AgentTrail OS + +Guard is a complete, standalone tool. You can install it, use it, and contribute to it +without an AgentTrail account. Installing Guard does not connect your sessions to +AgentTrail OS. + +[AgentTrail](https://www.agenttrail.sh) also makes +**[AgentTrail OS](https://www.agenttrail.sh/agenttrail-os)**, for developers and +teams who also want a searchable record of every agent session. Both products use the same +open rule library. + +| | [AgentTrail Guard](https://www.agenttrail.sh/agenttrail-guard) | [AgentTrail OS](https://www.agenttrail.sh/agenttrail-os) | +| --- | --- | --- | +| Where decisions are made | On your machine, from the bundled library | On AgentTrail's servers, from your organization's policies | +| Account | None | Required | +| Agents | Claude Code, Cursor, Codex CLI | Claude Code, Cursor | +| Your own rules | A local JSON file | Authored in the app | +| History | A capped local log, plus on-demand scan reports | Searchable session history | +| For teams | One developer, one machine | Synced rules, policy backtesting, approvals, and an audit trail, depending on plan | +| When something breaks | Fails open: your agent's normal permission flow continues | Fails closed: the action does not proceed | + +Not sure which fits? See +[AgentTrail Guard vs AgentTrail OS](https://www.agenttrail.sh/guard-vs-os) for a detailed +comparison, or [get started](https://www.agenttrail.sh/get-started) with either. + +## Troubleshooting + +| What you see | What to check | +| --- | --- | +| `agenttrail-guard` is not found | Check that Node.js 20+ is installed and your npm global executable directory is on `PATH`. Reopen the terminal after installation. | +| `init`'s demo works, but the agent is not checked | Finish the agent-specific setup in [Quick start](#quick-start). The demo checks the engine, not the live hook connection. | +| `status` reports `BROKEN` | Read the named problem and re-run `init --agent` for that agent. This refreshes the hook copy and its Node path. | +| Codex reports `ON`, but calls are not checked | Open `/hooks` in Codex CLI and approve every Guard entry. `ON` reports installation state, not approval. | +| An `ask` rule never prompts in Claude Code | A broad `permissions.allow` entry in `settings.json` can skip the prompt. `status` names the tools affected. | +| A legitimate command keeps matching | Inspect the rule with `guardrails show`, then add a narrow exception or change its action. See [Tune a guardrail](#tune-a-guardrail). | +| A scan differs from a past decision | Scans apply your current guardrails to recorded calls; rules and settings may have changed since the session. | + +If the issue persists, [open a Guard issue](https://github.com/agenttrailhq/guard/issues) +with the details listed in +[CONTRIBUTING.md](CONTRIBUTING.md#reporting-a-bug-or-a-false-positive). + +## FAQ + +**Can I use Guard in a team?** +Teammates can each install Guard and share custom rule files through your own repository. +Guard's settings are per developer and per machine; nothing syncs. +[AgentTrail OS](https://www.agenttrail.sh/agenttrail-os) is built for shared policies. + +**What if I already use the `agenttrail` Claude Code plugin?** +`init --agent claude` detects it and stops, rather than installing a second hook that +decides on the same tool call. + +## Contributing + +Found a false positive, a missed command, or have an idea? Contributions of every size help, +code or not. [CONTRIBUTING.md](CONTRIBUTING.md) explains which repository each report +belongs in, how to build and test Guard, and how the code is laid out. Please follow the +[Code of Conduct](CODE_OF_CONDUCT.md), and report vulnerabilities privately as described in +[SECURITY.md](SECURITY.md). + +## Reference + +The full reference lives in this README, and most sections are collapsed so you can open +just the one you need: + +- **Setting up:** [Install](#install), [Updating](#updating), [Uninstall](#uninstall), and + [what `status` shows](#what-status-shows). +- **Day to day:** [managing guardrails](#managing-guardrails), + [seeing what already happened](#seeing-what-already-happened), and + [crash reporting](#crash-reporting). +- **Guarantees and limits:** [what the guard does not do](#what-the-guard-does-not-do), + [rules of the runtime](#rules-of-the-runtime), and [files it keeps](#files-it-keeps). -Then set it up for each app you use. Each install is separate, and you can have all three: +## Install -```sh -agenttrail-guard init --agent claude # Claude Code -agenttrail-guard init --agent cursor # Cursor -agenttrail-guard init --agent codex # Codex CLI -``` +
+What each integration installs and changes -`--agent` is required on `init`, `uninstall` and `scan`. Without it, or with any other -name, the command says so and changes nothing. +If you already have the `agenttrail` plugin installed, `init --agent claude` tells you +and stops to avoid installing two hooks that decide on the same tool call. **Claude Code.** `init --agent claude` does two things, and nothing else. It writes its own two files under `~/.agenttrail/guard/`, and it asks Claude Code to install the hook @@ -90,8 +754,13 @@ Want to look before you leap? `npx @agenttrail/guard@latest init --agent claude (or `--agent cursor --print`, or `--agent codex --print`) shows you exactly what it would do and changes nothing. +
+ ### Updating +
+Update the package and refresh each integration + A new release can add guardrails and whole packs. Every pack is on unless you turned it off, so a pack added in a release you install starts enforcing with no step of its own. The update itself is two steps, for each app you use: @@ -124,8 +793,13 @@ was made by a different version. If the app is running a newer guard than the co ran `status` with, it says that command is the one out of date, rather than suggesting an `init` that would downgrade the app. +
+ ### Uninstall +
+Remove an integration and keep your settings + ```sh npx @agenttrail/guard@latest uninstall --agent claude npx @agenttrail/guard@latest uninstall --agent cursor @@ -150,26 +824,13 @@ every entry after them up the file, and Codex identifies an approval by position other tool's hook that sat below the guard's therefore needs approving again in `/hooks`. `uninstall` says so when there is one. -## The commands - -| | | -|---|---| -| `agenttrail-guard init --agent ` | Install the hook for Claude Code, Cursor or Codex CLI, seed the config, demonstrate itself. Safe to re-run. | -| `agenttrail-guard status` | What is enforcing in each app, what it has been doing, and — if one guardrail keeps firing on something legitimate — the one line that silences just that guardrail. `--clear-history` empties the decision log. | -| `agenttrail-guard guardrails` | See and change what the guard enforces. | -| `agenttrail-guard scan --agent ` | Read the sessions already on your disk and say what your agent has been doing. | -| `agenttrail-guard crash-report` | See, enable, send or clear opt-in crash reports (off by default). | -| `agenttrail-guard uninstall --agent ` | Remove our plugin, or our Cursor or Codex entries, and only ours. Safe to re-run. Your settings stay in `~/.agenttrail/guard/` until you delete them. | - -If you already have the agenttrail plugin installed, `init --agent claude` will tell you -so and stop. Two hooks deciding on every tool call means two prompts and twice the -latency, for no benefit. - -> **Status.** Every command above is implemented, and so is the decision log. The -> bundled catalog is the real guardrail library: 74 guardrails across 11 packs. +
## What `status` shows +
+Installation checks, version checks, and decision history + `status` takes no `--agent`. It prints a section for Claude Code, one for Cursor, one for Codex CLI, and then what applies to all three. @@ -234,11 +895,15 @@ for Codex CLI, and then what applies to all three. `cursor` or `codex`), the guardrail id and the command; then the guardrail that fired most, with the one line that silences it for that command. -## When a guardrail is wrong +
-The most likely reason to remove this tool is one guardrail firing repeatedly on -something legitimate. `agenttrail-guard guardrails` is the alternative to hand-editing -JSON or uninstalling. +## Managing guardrails + +
+Rule commands, actions, allow patterns, and pack settings + +When one guardrail fires repeatedly on something legitimate, `agenttrail-guard guardrails` +fixes it without hand-editing JSON or uninstalling. ``` guardrails list [--pack ] [--enabled|--disabled] what is on, and what is not @@ -281,8 +946,13 @@ overrides, allowlist entries, guardrails you turned off, and packs you turned of `enabledPacks` key an older release left behind, and states the pack change on a line of its own. It never touches your own guardrails in `guardrails.json`. +
+ ## Seeing what already happened +
+Transcript readers, report contents, redaction, and sharing + ```sh agenttrail-guard scan --agent claude agenttrail-guard scan --agent cursor @@ -296,9 +966,12 @@ guardrails the hook enforces, and tells you what your agent has actually been do how many sessions, which guardrails would have fired, which mistakes repeat, and, for Claude Code, exact token counts with the cache split. -Both readers open each session's own file **and** the sub-agent transcripts beside it, so a -sub-agent's tool calls are counted in the session that started it; any other `.jsonl` file -elsewhere under the root is not read, and the report counts it. +`scan --agent codex` reads session files under `~/.codex/sessions`. +Use `--dir ` to select a different transcript root for any agent. + +The Claude Code and Cursor readers open each session's own file **and** the sub-agent +transcripts beside it, so a sub-agent's tool calls are counted in the session that started +it; any other `.jsonl` file elsewhere under the root is not read, and the report counts it. **Cursor's session files hold less than Claude Code's transcripts, and the scan says so.** @@ -324,10 +997,10 @@ by action, the most serious findings and the top repeats — and one self-contai `agenttrail-guard-report.html` in the directory you ran it from, or wherever `--out` points. The report opens in your browser when it is written; `--no-open` skips that, and it stays closed when `CI` is set. That file is a single document with its styles and the -agenttrail logo inline, and it follows your system's light or dark theme. It loads +AgentTrail logo inline, and it follows your system's light or dark theme. It loads nothing from outside itself, so it opens from a `file://` URL with the wifi off and cannot phone home when someone else opens it. The source repository's address in its -footer is text. A Claude Code report also carries one short note about agenttrail with a +footer is text. A Claude Code report also carries one short note about AgentTrail with a single link to www.agenttrail.sh, which does nothing until someone clicks it; a Cursor report carries none. @@ -344,12 +1017,8 @@ is per-machine and your projects are your business. is structural — a token either has a separator or it does not — so redacting one is a total rule. A name is not: `acme-prod-db` is a container to a human and an ordinary word to a regex. For **shell commands** the identifier redactor closes that by knowing a fixed -list of tools — container runtimes, `kubectl` (including resource names), the database -clients (host, database, and the SQL passed to `-c` / `-e`), `ssh`, `git` commit -messages and identity settings, 1Password's `op` (item and vault names), and `gh` titles -and bodies — plus, in any command, a secret handed as a flag value (`--token`, -`--password`, `--key`), a UUID, the body of a heredoc (which becomes ``), and -the text of a `#` comment. That is a deny-list, and it will +list of tools, plus a few shapes it redacts in any command; both are listed under +[What the guard does not do](#what-the-guard-does-not-do). That is a deny-list, and it will miss the tool nobody thought of. **An `mcp__…` tool call is redacted the other way round:** its payload is a structured value, so rather than deny-listing known-sensitive fields, every value is redacted to `` by default and only the field names and the @@ -395,8 +1064,13 @@ gives you the path to the local file instead. Cursor scans are not published thi `scan --agent cursor` writes the report and opens it in your browser. The skill ships with plugin 0.3.0; to get it, run `agenttrail-guard init --agent claude`. +
+ ## Crash reporting +
+Local crash records and opt-in reporting + Off by default, opt-in, and stack traces only. A crash is recorded to a file on your machine either way; nothing is transmitted until you turn reporting on and run `agenttrail-guard crash-report --send` yourself. Stack traces are scrubbed for secrets @@ -417,215 +1091,13 @@ environment variable, or `"crashEndpoint"` in `config.json`. With nothing config `--send` refuses by name and transmits nothing — so a default install has no endpoint to send to, by design. `crash-report` (no flag) shows whether one is configured. -## What the guard does not do - -Read this before you rely on it. A security tool that overstates its coverage is -worse than one that names the hole. - -- **The guard does not see what the agent fetches from the web.** `WebFetch` is not - intercepted, and there are no website guardrails in v1. The underlying engine can - match on a command string and on a file path — it has no matcher for a URL — so a - URL guardrail would have validated, installed, and then matched nothing, forever, - without telling anyone. Rather than ship a channel whose guardrails only appear to - work, v1 leaves web traffic out and says so here. `WebSearch` **is** covered, - because a search query is ordinary text on the command channel. -- **It does not stop an action it cannot see.** The guard runs as a Claude Code - `PreToolUse` hook, as Cursor `preToolUse` and `beforeShellExecution` hooks, and as - Codex `PreToolUse` and permission-request hooks. Anything the agent does outside a - hooked tool is invisible to it, and the other two apps have more of these gaps than - Claude Code: see [Cursor's limits](#cursors-limits) and - [Codex's limits](#codexs-limits). -- **A file guardrail matches the PATH, not the file's contents.** The engine reads a - command string and a file path; it has no matcher for what a write PUTS in a file. So a - guardrail on `Write` or `Edit` decides only by where the write lands, never by what it - contains — and **write-then-execute falls in the gap**: an agent can write a script (judged - by its path alone, its contents unseen) and then run it as a separate command. The run is - checked like any command, but if that command does not itself match a guardrail — - `./setup.sh`, say — the dangerous content that went into the file was never inspected. - Guard what a file's contents would DO by guarding the commands that act on it; a file-path - guardrail is a scope rule, not a content scanner. -- **A hold does not prompt when your settings already allow the tool.** A guardrail set - to `require_approval` (shown as `ask`) returns Claude Code's `ask` decision, but Claude - Code's own `permissions.allow` rules win over a hook: if your `settings.json` allows a - tool outright — `"Bash"`, `"Read"`, `"Edit"` — a call to that tool runs with no prompt, - and the hold never reaches you. The guard cannot change this, because Claude Code - decides, and the hook cannot see that it happened, so the call is still recorded as a - hold. `status` and `init` read your `settings.json` and tell you how many holds this - silences, naming the tools; a scoped allow like `"Bash(git:*)"` is narrower and is not - counted. -- **It does not redact what it lets through.** The guard decides about a command; it - never rewrites one. Secrets are redacted from what the guard itself *writes* — the - decision log, the `scan` report and a crash report all go through the scrubber — but - a command that contains a secret and is allowed still runs exactly as the agent - wrote it. -- **Its redaction is good, not complete.** Every command written to `events.jsonl` is - scrubbed first — AWS keys, GitHub and Slack tokens, JWTs, private keys, connection - strings, `.env`-style assignments, emails, and card numbers all become - `[REDACTED:…]`. What it does **not** catch is a secret passed as a bare command-line - argument, with no `=` or `:` between the name and the value: the patterns are - anchored to an assignment, so `--token abc123` reads as two ordinary words. Treat - the log as scrubbed, not sanitized. (The `scan` **report** goes further and does - redact a secret handed to a long flag such as `--token`, `--password` or `--key`; the - decision log does not.) -- **The `scan` report's name redaction is a deny-list for shell commands and an - allow-list for MCP.** Paths are redacted structurally, so that rule is total. Shell - names are not: the identifier redactor knows a fixed list of tools — container - runtimes, `kubectl` (resource names included), the database clients (host, database, - and the SQL passed to `-c` / `-e`), `ssh`, `git` commit messages and identity - settings, 1Password's `op` (item and vault names), and `gh` titles and bodies — and it - also redacts, in any command, a secret handed as a flag value (`--token`, - `--password`, `--key`), a UUID, a heredoc body, and the text of a `#` comment. A bare - operand naming a resource for a tool *not* on the list - survives, and a branch or tag given as a plain `git` operand (`git checkout my-branch`, - `git tag -a v1`) is kept readable on purpose. A **glued** short-flag password - (`mysql -pSECRET`) is not caught — only long secret flags and `.env`-style assignments - are. An `mcp__…` payload is handled the opposite way: every value is redacted to - `` by default and only the field names are kept, so no raw payload value reaches - the file. -- **A guardrail's id and title are printed as their author wrote them.** For the shipped - library that is our words. For a guardrail *you* added to `guardrails.json` it is yours, - and nothing redacts it: a title reading "AcmeCorp internal audit" appears in the report - verbatim. Paths and secrets pasted into a title *are* taken out — the title goes - through the same three passes as a command — but a name you chose is not something a - redactor can distinguish from a word. Run `scan --review` to read every line, commands - and guardrail names both, before the file is written. -- **The 30-day limit on the decision log is a ceiling, not a sweep.** Old records are - dropped when the log is compacted, and compaction happens when it crosses 1 MiB. On - a machine that stops using Claude Code and Cursor, the last half-megabyte of records - stays until either another decision is recorded or you run `status --clear-history`. - The bound that always holds is the size one. -- **It is one developer on one machine.** Nothing syncs, nothing is shared with a - team, and there is no approval queue. - -### Cursor's limits - -Cursor's hooks see less than Claude Code's. In Cursor, the guard does not check these: - -- **Reads that Cursor shows as "Explored".** No hook runs for them, so a guardrail on a - file path does not see them. -- **Files Cursor has already attached to the conversation.** Cursor puts rule and - instruction files into the agent's context itself, with no tool call, so no hook fires. - The agent can then quote such a file even where a guardrail denies reading it: a - guardrail on a file path cannot stop content Cursor has already attached. -- **Everything a search can reach.** A `Grep` is checked on its folder, and on the folder - joined to its glob, so a search for `**/.env` under a project is checked as that path. - A brace glob such as `{.env,.env.local}` is not expanded, and searching the contents of - files under a folder is not covered: the search pattern is not read. -- **Tab edits.** Edits made with Cursor Tab are not covered. -- **The Agent Window.** It can skip hooks. Use Cursor's agent in an editor window; - `status` reminds you. -- **Approval, except for terminal commands.** Cursor shows its approval card for a - terminal command when the guard asks at `beforeShellExecution`. It does not enforce an - `ask` from `preToolUse`, so a guardrail that asks for approval of a file edit, a read, - a `Grep`, a `Delete` or an MCP call blocks that call instead, with the message - "agenttrail-guard needs a person to approve this, and Cursor cannot ask: " and the - guardrail's title and id. -- **A terminal command that Cursor's own run mode runs first.** This is the Cursor analogue - of Claude Code's "a hold does not prompt when your settings already allow the tool" (above). - In a **sandbox** run mode many commands run automatically with no card — and such a command - can bypass `beforeShellExecution` entirely, so the guard never evaluates it and there is no - `events.jsonl` record either. A command the sandbox cannot run — one that needs the network, - or writes outside it — is escalated to approval, where the guard does check it; so coverage - under a sandbox run mode is partial, and decided by Cursor, not by the guardrails. For the - guard to check **every** terminal command, use a non-sandbox run mode (Cursor's allowlist - mode without the sandbox), and keep the commands you want checked off Cursor's command - allowlist. Unlike Claude Code's `settings.json`, there is no documented, stable file for the - guard to read the run mode from, so `status` and `init` name the effect but cannot count it. - When the hook does run, the decision is recorded in `events.jsonl`. -- **Showing warnings.** A guardrail that warns is answered with no opinion. The match is - written to `events.jsonl` and counted by `status`, and Cursor shows nothing. -- **Being named in the agent's reply.** Cursor shows no reason of its own, so what you read - is the agent retelling what it was handed. Every message names the guard, the guardrail's - title and its id, and the agent's copy also asks for the line to be repeated as it stands - — but the agent writes its own reply and may drop the name. Measured on Cursor 3.20.21: - the verdict was always right and the wording was the agent's own. `status`, `events.jsonl` - and `scan` are where each decision is recorded exactly. -- **Cursor's cloud agents.** They do not run user-level hooks, and the guard's entries - are in your user-level `~/.cursor/hooks.json`. - -**The guard's Claude Code plugin inside Cursor.** With Cursor's "Include third-party -plugins" setting on, Cursor can run the guard's Claude Code plugin and hand it Cursor's -payload. The plugin reads that as a Cursor call, and it still blocks what a guardrail -blocks. - -- When both of the guard's Cursor entries are in `~/.cursor/hooks.json`, the plugin - leaves approvals and the decision log to them, so each decision is asked for and - logged once. -- Without them, the plugin is the only checkpoint. It blocks any call that needs - approval, terminal commands included, with "agenttrail-guard needs a person to approve - this, and Cursor cannot ask: " and the guardrail's title and id, and it logs every - decision itself. - -### Codex's limits - -A deny reaches Codex intact: the command never runs, and Codex prints the guard's own -reason on screen and the model repeats it. What differs is what the hook can see, what -it can ask for, and what happens before you approve it. Everything below is measured on -**codex-cli 0.154.0**. - -- **Nothing runs until you approve the guard in `/hooks`.** An installed-but-unapproved - entry never fires. The command runs normally, no decision is recorded, and neither - Codex nor the guard mentions it — the only symptom of an unguarded machine is - silence. `status` cannot close this gap for you, because approval is recorded in a - file the guard does not read. -- **Changing an installed entry silently un-approves it.** Codex trusts the entry's - content, so editing the command, the timeout or the matcher by as little as one - character stops the hook firing, with no warning and no log line, until you approve - it again. That is why the installed entry is frozen and pinned by a test, and why a - guard release ships a new *script* rather than a new entry. Putting the entry back - exactly as it was restores the approval by itself. -- **An approval becomes a block.** Codex parses a hook that asks for human approval, - marks the hook run as failed, and **runs the action anyway** — so asking would be the - same as saying nothing. The guard therefore refuses a call it would have held on - Claude Code, and says in the reason that a person has to approve it. 50 of the 74 - bundled guardrails ask rather than block, so this is the difference you will meet - most often. Downgrade any of them in `~/.agenttrail/guard/config.json`, or with - `guardrails set-action warn`, if a block is too strong for your work. -- **Reads fire no hook at all.** Codex has no read tool: it reads files by running - shell commands. A guardrail on a *file path* therefore never sees a read, and the - read is only checked as far as its command text goes. The paths a file guardrail does - see are the ones inside an edit. -- **An edit written through the shell carries no path.** Codex's editing tool sends the - patch, and the guard reads the paths out of it. An edit the agent writes as a shell - command instead — a heredoc into `cat`, a `sed -i`, a redirect — arrives as an - ordinary command with no path field anywhere, so only the command guardrails apply to - it. Every app here has this gap; it is wider on Codex because the shell is the route - for more of what the agent does. -- **Every failure lets the action through, and there is no switch for that.** A crash, - a non-zero exit other than 2, output that is not JSON, one field Codex does not - recognise, or a hook that runs past its timeout: each is recorded as a failed hook - run and the tool call proceeds. Codex offers no fail-closed setting, so a guard that - cannot answer is a guard that is not there. It is also why the hook answers with - exactly three fields and waits on nothing. -- **A warning is invisible in `codex exec`.** In Codex's terminal UI a warning shows as - a hook line above the command. In a scripted `codex exec` run the text appears - nowhere at all — only that the hook completed. The match is still written to - `events.jsonl` and counted by `status`, which is where a warning is reliably read. -- **MCP tool calls are unverified here.** The guard checks a Codex MCP call by the same - `mcp__server__tool` name Claude Code uses, taken from Codex's own source. No MCP - server was configured on the machine these measurements come from, so that path is - reasoned rather than observed. -- **Windows and Codex Desktop are unverified.** Both appear in open reports of denies - being ignored, and neither was exercised here. -- **A shell tool under another name is seen but not read.** The guard's entry matches - every tool, so the hook does run — but the guard understands three shapes on Codex: - the shell tool named `Bash`, an edit through `apply_patch`, and an MCP call named - `mcp__server__tool`. Anything else yields no verdict and **no line in the decision - log**, so it is neither checked nor recorded. This is not hypothetical: Codex Desktop - reports its shell tool as `shell_command`, which means the guard would not check - commands there at all. -- **Codex Cloud is not covered.** It runs on OpenAI's machines and not yours, so it - never reads `~/.codex/hooks.json` and the guard is not in the loop at all. -- **An enterprise-managed config can switch user hooks off entirely.** With - `allow_managed_hooks_only = true` set in a managed configuration, Codex runs only the - hooks that configuration provides and skips yours, the guard's included. There is no - message about it; the guard simply never fires. -- **0.154.0 is the tested floor.** Every behaviour above was measured on that version. - Codex's hook surface is young and moving, so treat a newer release as unverified - until a deny, an approval and a warning have each been seen on it. +
## Rules of the runtime +
+Hook output, exit behavior, and fail-open behavior + Three properties hold on every single invocation, and each has a test that fails the build if it stops holding: @@ -663,11 +1135,17 @@ build if it stops holding: If our code has a bug, your command goes through the app's normal permission flow. +
+ ## Files it keeps -Four files, all under `~/.agenttrail/guard/`, all written at mode `0600`, plus a -`cursor/` folder and a `codex/` folder there, entries in Cursor's hooks file once you -install for Cursor, and entries in Codex's hooks file once you install for Codex. The +
+Configuration, local logs, retention, and integration files + +Local settings, logs, and crash records live under `~/.agenttrail/guard/`. +Guard writes its private files at mode `0600` where supported. It also keeps +`cursor/` and `codex/` integration folders there and adds entries to each app's hooks +file when you install that integration. The guard writes nothing else anywhere — not in your project, not in your Claude Code settings, not in Codex's `config.toml` — with exactly one exception: `scan` writes `agenttrail-guard-report.html` into the directory you run it from, or to the path you @@ -744,20 +1222,11 @@ finding them silently gone after a reinstall would be worse than finding them th deletes the files in `cursor/`; `uninstall --agent codex` does the same for `~/.codex/hooks.json` and `codex/`. -## Building this from source - -```sh -pnpm install -pnpm build -pnpm test -``` - -`dependencies` is empty: the published package bundles everything it runs, including the -rule-matching engine in `src/engine/` and the guardrail library. The library is its own package, -`@agenttrail/guardrails`. Guardrail contributions belong there, and -`agenttrail-guard guardrails validate ` runs the same schema check locally that its CI runs on -a pull request. +
## License -Apache-2.0. +AgentTrail Guard is licensed under the [Apache License 2.0](LICENSE), commercial use +included. If you rely on Guard, a star helps other developers find it. + +Made with ❤️ by [agenttrail.sh](https://www.agenttrail.sh) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c4b6d0e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,43 @@ +# Security Policy + +## How to report + +Email **[security@agenttrail.sh](mailto:security@agenttrail.sh)**. Keep vulnerability +details out of GitHub issues, pull requests, and discussions until a fix is released. + +A useful report includes: + +- what an attacker could do, and under which agent (Claude Code, Cursor, or Codex CLI); +- steps to reproduce, with your Guard version (`agenttrail-guard --version`), agent version, + and operating system; +- a minimal payload or command that shows the problem, with secrets and private project + details removed. + +Before sending, please confirm the problem still occurs on the +[latest npm release](https://www.npmjs.com/package/@agenttrail/guard). We welcome +coordinated disclosure and will work with you on timing. + +## In scope + +Guard documents a small set of guarantees, and a way to break any of them is a +vulnerability: + +- **It never answers `allow`.** A call Guard does not block or hold goes to the agent's own + permission flow. +- **It never exits with code 2**, and writes at most one JSON object to stdout. +- **It sends nothing by default.** Crash reporting is off unless you turn it on, and then + sends scrubbed stack traces only. +- **It writes only the files it documents** in [Files it keeps](README.md#files-it-keeps). + +The details are in [Rules of the runtime](README.md#rules-of-the-runtime). For a plain-language +summary of what Guard reads, keeps, and sends, see +[Security & Trust](https://www.agenttrail.sh/security#guard). + +## Out of scope + +These are documented limits of the design, so please raise them in public instead: + +- Behavior described in [What the guard does not do](README.md#what-the-guard-does-not-do), + such as failing open, missing calls that never reach a hook, or not reading file contents. +- A dangerous command that no guardrail matches. That is a rule gap: open a + [Guardrails issue](https://github.com/agenttrailhq/guardrails/issues). From b17dc5391b1f5570d6f07a82db6cf9b9fd427091 Mon Sep 17 00:00:00 2001 From: Loveneesh Dhir Date: Sat, 3 Oct 2026 00:28:58 +0530 Subject: [PATCH 2/3] docs: link the online guardrail library Point readers of "What Guard catches" to agenttrail.sh/guardrails, where every rule can be browsed and a command can be checked against the rules. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 85d44c5..dbe35f7 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,9 @@ prevent, not the technique used to spot it. By default, 13 guardrails block, 50 Run `agenttrail-guard guardrails list` for every rule with its current action, or `guardrails show ` for one rule in full. The source for every rule is in -[agenttrailhq/guardrails](https://github.com/agenttrailhq/guardrails#the-eleven-packs). +[agenttrailhq/guardrails](https://github.com/agenttrailhq/guardrails#the-eleven-packs). To browse +every rule online, or paste a command and see which rules match it, use the +[guardrail library](https://www.agenttrail.sh/guardrails). ### Guard and Guardrails From d8a97a4ed8d861f42f2f85cdd9658cf7a583f677 Mon Sep 17 00:00:00 2001 From: Loveneesh Dhir Date: Sat, 3 Oct 2026 00:46:12 +0530 Subject: [PATCH 3/3] docs: replace em dashes in the README Use colons, semicolons, commas and parentheses instead. Wording is unchanged, and the real CLI output shown in the README keeps the em dashes the CLI prints. Co-Authored-By: Claude Opus 5.5 --- README.md | 120 +++++++++++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index dbe35f7..8e1e730 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ worse than one that names the hole. - **The guard does not see what the agent fetches from the web.** `WebFetch` is not intercepted, and there are no website guardrails in v1. The underlying engine can - match on a command string and on a file path — it has no matcher for a URL — so a + match on a command string and on a file path (it has no matcher for a URL), so a URL guardrail would have validated, installed, and then matched nothing, forever, without telling anyone. Rather than ship a channel whose guardrails only appear to work, v1 leaves web traffic out and says so here. `WebSearch` **is** covered, @@ -419,28 +419,28 @@ worse than one that names the hole. - **A file guardrail matches the PATH, not the file's contents.** The engine reads a command string and a file path; it has no matcher for what a write PUTS in a file. So a guardrail on `Write` or `Edit` decides only by where the write lands, never by what it - contains — and **write-then-execute falls in the gap**: an agent can write a script (judged + contains, and **write-then-execute falls in the gap**: an agent can write a script (judged by its path alone, its contents unseen) and then run it as a separate command. The run is - checked like any command, but if that command does not itself match a guardrail — - `./setup.sh`, say — the dangerous content that went into the file was never inspected. + checked like any command, but if that command does not itself match a guardrail + (`./setup.sh`, say), the dangerous content that went into the file was never inspected. Guard what a file's contents would DO by guarding the commands that act on it; a file-path guardrail is a scope rule, not a content scanner. - **A hold does not prompt when your settings already allow the tool.** A guardrail set to `require_approval` (shown as `ask`) returns Claude Code's `ask` decision, but Claude Code's own `permissions.allow` rules win over a hook: if your `settings.json` allows a - tool outright — `"Bash"`, `"Read"`, `"Edit"` — a call to that tool runs with no prompt, + tool outright (`"Bash"`, `"Read"`, `"Edit"`), a call to that tool runs with no prompt, and the hold never reaches you. The guard cannot change this, because Claude Code decides, and the hook cannot see that it happened, so the call is still recorded as a hold. `status` and `init` read your `settings.json` and tell you how many holds this silences, naming the tools; a scoped allow like `"Bash(git:*)"` is narrower and is not counted. - **It does not redact what it lets through.** The guard decides about a command; it - never rewrites one. Secrets are redacted from what the guard itself *writes* — the - decision log, the `scan` report and a crash report all go through the scrubber — but + never rewrites one. Secrets are redacted from what the guard itself *writes* (the + decision log, the `scan` report and a crash report all go through the scrubber), but a command that contains a secret and is allowed still runs exactly as the agent wrote it. - **Its redaction is good, not complete.** Every command written to `events.jsonl` is - scrubbed first — AWS keys, GitHub and Slack tokens, JWTs, private keys, connection + scrubbed first: AWS keys, GitHub and Slack tokens, JWTs, private keys, connection strings, `.env`-style assignments, emails, and card numbers all become `[REDACTED:…]`. What it does **not** catch is a secret passed as a bare command-line argument, with no `=` or `:` between the name and the value: the patterns are @@ -450,25 +450,25 @@ worse than one that names the hole. decision log does not.) - **The `scan` report's name redaction is a deny-list for shell commands and an allow-list for MCP.** Paths are redacted structurally, so that rule is total. Shell - names are not: the identifier redactor knows a fixed list of tools — container + names are not; the identifier redactor knows a fixed list of tools: container runtimes, `kubectl` (resource names included), the database clients (host, database, and the SQL passed to `-c` / `-e`), `ssh`, `git` commit messages and identity - settings, 1Password's `op` (item and vault names), and `gh` titles and bodies — and it + settings, 1Password's `op` (item and vault names), and `gh` titles and bodies; and it also redacts, in any command, a secret handed as a flag value (`--token`, `--password`, `--key`), a UUID, a heredoc body (which becomes ``), and the text of a `#` comment. A bare operand naming a resource for a tool *not* on the list survives, and a branch or tag given as a plain `git` operand (`git checkout my-branch`, `git tag -a v1`) is kept readable on purpose. A **glued** short-flag password - (`mysql -pSECRET`) is not caught — only long secret flags and `.env`-style assignments + (`mysql -pSECRET`) is not caught: only long secret flags and `.env`-style assignments are. An `mcp__…` payload is handled the opposite way: every value is redacted to `` by default and only the field names are kept, so no raw payload value reaches the file. - **A guardrail's id and title are printed as their author wrote them.** For the shipped library that is our words. For a guardrail *you* added to `guardrails.json` it is yours, and nothing redacts it: a title reading "AcmeCorp internal audit" appears in the report - verbatim. Paths and secrets pasted into a title *are* taken out — the title goes - through the same three passes as a command — but a name you chose is not something a + verbatim. Paths and secrets pasted into a title *are* taken out (the title goes + through the same three passes as a command), but a name you chose is not something a redactor can distinguish from a word. Run `scan --review` to read every line, commands and guardrail names both, before the file is written. - **The 30-day limit on the decision log is a ceiling, not a sweep.** Old records are @@ -509,10 +509,10 @@ Cursor's hooks see less than Claude Code's. In Cursor, the guard does not check guardrail's title and id. - **A terminal command that Cursor's own run mode runs first.** This is the Cursor analogue of Claude Code's "a hold does not prompt when your settings already allow the tool" (above). - In a **sandbox** run mode many commands run automatically with no card — and such a command + In a **sandbox** run mode many commands run automatically with no card, and such a command can bypass `beforeShellExecution` entirely, so the guard never evaluates it and there is no - `events.jsonl` record either. A command the sandbox cannot run — one that needs the network, - or writes outside it — is escalated to approval, where the guard does check it; so coverage + `events.jsonl` record either. A command the sandbox cannot run (one that needs the network, + or writes outside it) is escalated to approval, where the guard does check it; so coverage under a sandbox run mode is partial, and decided by Cursor, not by the guardrails. For the guard to check **every** terminal command, use a non-sandbox run mode (Cursor's allowlist mode without the sandbox), and keep the commands you want checked off Cursor's command @@ -523,8 +523,8 @@ Cursor's hooks see less than Claude Code's. In Cursor, the guard does not check written to `events.jsonl` and counted by `status`, and Cursor shows nothing. - **Being named in the agent's reply.** Cursor shows no reason of its own, so what you read is the agent retelling what it was handed. Every message names the guard, the guardrail's - title and its id, and the agent's copy also asks for the line to be repeated as it stands - — but the agent writes its own reply and may drop the name. Measured on Cursor 3.20.21: + title and its id, and the agent's copy also asks for the line to be repeated as it stands, + but the agent writes its own reply and may drop the name. Measured on Cursor 3.20.21: the verdict was always right and the wording was the agent's own. `status`, `events.jsonl` and `scan` are where each decision is recorded exactly. - **Cursor's cloud agents.** They do not run user-level hooks, and the guard's entries @@ -557,7 +557,7 @@ it can ask for, and what happens before you approve it. Everything below is meas - **Nothing runs until you approve the guard in `/hooks`.** An installed-but-unapproved entry never fires. The command runs normally, no decision is recorded, and neither - Codex nor the guard mentions it — the only symptom of an unguarded machine is + Codex nor the guard mentions it: the only symptom of an unguarded machine is silence. `status` cannot close this gap for you, because approval is recorded in a file the guard does not read. - **Changing an installed entry silently un-approves it.** Codex trusts the entry's @@ -567,7 +567,7 @@ it can ask for, and what happens before you approve it. Everything below is meas guard release ships a new *script* rather than a new entry. Putting the entry back exactly as it was restores the approval by itself. - **An approval becomes a block.** Codex parses a hook that asks for human approval, - marks the hook run as failed, and **runs the action anyway** — so asking would be the + marks the hook run as failed, and **runs the action anyway**, so asking would be the same as saying nothing. The guard therefore refuses a call it would have held on Claude Code, and says in the reason that a person has to approve it. 50 of the 74 bundled guardrails ask rather than block, so this is the difference you will meet @@ -579,7 +579,7 @@ it can ask for, and what happens before you approve it. Everything below is meas see are the ones inside an edit. - **An edit written through the shell carries no path.** Codex's editing tool sends the patch, and the guard reads the paths out of it. An edit the agent writes as a shell - command instead — a heredoc into `cat`, a `sed -i`, a redirect — arrives as an + command instead (a heredoc into `cat`, a `sed -i`, a redirect) arrives as an ordinary command with no path field anywhere, so only the command guardrails apply to it. Every app here has this gap; it is wider on Codex because the shell is the route for more of what the agent does. @@ -591,7 +591,7 @@ it can ask for, and what happens before you approve it. Everything below is meas exactly three fields and waits on nothing. - **A warning is invisible in `codex exec`.** In Codex's terminal UI a warning shows as a hook line above the command. In a scripted `codex exec` run the text appears - nowhere at all — only that the hook completed. The match is still written to + nowhere at all: only that the hook completed. The match is still written to `events.jsonl` and counted by `status`, which is where a warning is reliably read. - **MCP tool calls are unverified here.** The guard checks a Codex MCP call by the same `mcp__server__tool` name Claude Code uses, taken from Codex's own source. No MCP @@ -600,7 +600,7 @@ it can ask for, and what happens before you approve it. Everything below is meas - **Windows and Codex Desktop are unverified.** Both appear in open reports of denies being ignored, and neither was exercised here. - **A shell tool under another name is seen but not read.** The guard's entry matches - every tool, so the hook does run — but the guard understands three shapes on Codex: + every tool, so the hook does run, but the guard understands three shapes on Codex: the shell tool named `Bash`, an edit through `apply_patch`, and an MCP call named `mcp__server__tool`. Anything else yields no verdict and **no line in the decision log**, so it is neither checked nor recorded. This is not hypothetical: Codex Desktop @@ -701,7 +701,7 @@ and stops to avoid installing two hooks that decide on the same tool call. **Claude Code.** `init --agent claude` does two things, and nothing else. It writes its own two files under `~/.agenttrail/guard/`, and it asks Claude Code to install the hook -as a plugin. **It never touches the `hooks` block in your `settings.json`** — Claude +as a plugin. **It never touches the `hooks` block in your `settings.json`**; Claude Code owns that file, and the guard's hook lives inside the plugin instead. **Cursor.** `init --agent cursor` writes the same two files, copies the hook to @@ -740,12 +740,12 @@ its absolute path. It does not run Codex. `init --agent codex` again. **Then approve the guard inside Codex, or none of this runs.** Start Codex, type -`/hooks`, and approve each `agenttrail-guard` entry — Codex reviews each one +`/hooks`, and approve each `agenttrail-guard` entry; Codex reviews each one separately. Until you do, the install is complete and **completely inert**: the hook never fires, every command goes through unchecked, and neither Codex nor the guard says a word about it. The guard cannot approve itself, because writing Codex's trust record for it would defeat the review that record exists for. Measured on codex-cli -0.154.0, and it applies again after any change to an entry — read +0.154.0, and it applies again after any change to an entry; read [Codex's limits](#codexs-limits) before you rely on it. `init` finishes by running a synthetic `rm -rf /` through the real guardrail engine and @@ -783,8 +783,8 @@ The update itself is two steps, for each app you use: the old one until it restarts; restart Cursor too if the guard's entries do not show in its Hooks tab. -**A new release does not cost you a second Codex approval.** Codex trusts the *entry* — -the command, its timeout and its matcher — and not the script that entry runs, so a +**A new release does not cost you a second Codex approval.** Codex trusts the *entry* +(the command, its timeout and its matcher) and not the script that entry runs, so a release that ships a new hook keeps the approval you already gave. That is also why the entry is frozen: changing it would silently revoke the approval and leave you unguarded until you noticed. See [Codex's limits](#codexs-limits). @@ -879,7 +879,7 @@ for Codex CLI, and then what applies to all three. reads perfectly and is no longer trusted. - **It cannot tell you whether Codex trusts the guard**, and says so rather than guessing. Approval is recorded in `~/.codex/config.toml`, which the guard does not - read — it ships no TOML parser and will not grow one to read a file it never writes. + read: it ships no TOML parser and will not grow one to read a file it never writes. `ON` here means installed and well-formed; whether it is *running* is answered only by Codex's own `/hooks` screen. - When the guard is installed, `ON` or `BROKEN`: a line when the install was made by a @@ -890,7 +890,7 @@ for Codex CLI, and then what applies to all three. - In each section, when there is at least one, the number of crash records from the guard's hook and the newest one's time. A record does not say which app ran the hook, so every section shows the same count, labelled as shared. -- Your own guardrails that are invalid, and settings in `config.json` that were ignored — +- Your own guardrails that are invalid, and settings in `config.json` that were ignored, including a pack name in `disabledPacks` that matches no pack, and an `enabledPacks` key left by an older release, which is no longer read. - The last five decisions, each with the decision, the app that sent the call (`claude`, @@ -922,19 +922,19 @@ guardrails validate [] the same check CI runs Shared flags: `--json`, `--quiet`, `--config `. **`allow` is the one that matters.** It suppresses one guardrail on one command shape -and nothing else — the same guardrail keeps blocking everything else, and the others are +and nothing else: the same guardrail keeps blocking everything else, and the others are untouched. `agenttrail-guard status` names the guardrail that has been firing most and prints the exact line to paste. It will **refuse** a pattern that would quietly do more than you asked. `*`, `**` and anything starting with `!` match every command, so allowlisting one would disable that guardrail entirely while `guardrails list` still showed it enabled. It also refuses a -pattern that is one of the guardrail's own block fixtures — a command it exists to stop — +pattern that is one of the guardrail's own block fixtures (a command it exists to stop) because silencing that shape would blind the guardrail to its own purpose. Patterns are matched in `/`-separated segments, so no `*` crosses a `/`; give `**` a segment of its own (`./generated/**`) to cover a subtree. -**One word to get right.** You type `ask`, and the tool prints `ask` — but the value +**One word to get right.** You type `ask`, and the tool prints `ask`, but the value stored in `config.json` and `guardrails.json` is `require_approval`. That is the vocabulary the guardrail engine uses. Writing `"ask"` into either file by hand does not work: `guardrails.json` reports it as invalid, and `config.json` **silently ignores it**, which is @@ -942,7 +942,7 @@ why `status` now lists settings it had to drop. **Turning packs off.** `guardrails disable ` adds the pack to `disabledPacks` in `config.json`, and `enable` takes it out; the change applies to the very next tool call. -You can turn every library pack off — the guard then checks only your own guardrails, if +You can turn every library pack off; the guard then checks only your own guardrails, if you have any, and says so. `guardrails reset --all` puts everything back: it clears action overrides, allowlist entries, guardrails you turned off, and packs you turned off, removes an `enabledPacks` key an older release left behind, and states the pack change on a line of its @@ -964,7 +964,7 @@ Claude Code and Cursor already keep a record of every agent session on your disk `scan --agent claude` reads Claude Code's transcripts under `~/.claude/projects`, and `scan --agent cursor` reads Cursor's session files under `~/.cursor/projects/*/agent-transcripts/`. It replays every tool call through the same -guardrails the hook enforces, and tells you what your agent has actually been doing — +guardrails the hook enforces, and tells you what your agent has actually been doing: how many sessions, which guardrails would have fired, which mistakes repeat, and, for Claude Code, exact token counts with the cache split. @@ -990,12 +990,12 @@ it; any other `.jsonl` file elsewhere under the root is not read, and the report and not in the live hook; any other guardrail gets the same verdict in both. It reads local files and nothing else. No account, no upload, and `scan` itself makes -no network call — the one exception anywhere in this tool is crash reporting, which is +no network call; the one exception anywhere in this tool is crash reporting, which is off unless you turn it on, and which `scan` never touches. To publish a report from Claude Code, see [Share a report from Claude Code](#share-a-report-from-claude-code). -You get two things: a summary in the terminal — the counts, the matches by severity and -by action, the most serious findings and the top repeats — and one self-contained +You get two things: a summary in the terminal (the counts, the matches by severity and +by action, the most serious findings and the top repeats) and one self-contained `agenttrail-guard-report.html` in the directory you ran it from, or wherever `--out` points. The report opens in your browser when it is written; `--no-open` skips that, and it stays closed when `CI` is set. That file is a single document with its styles and the @@ -1016,7 +1016,7 @@ the projects your sessions came from are a *count* and never names, because the is per-machine and your projects are your business. **Identifying names are a weaker claim than paths, and the difference matters.** A path -is structural — a token either has a separator or it does not — so redacting one is a +is structural (a token either has a separator or it does not), so redacting one is a total rule. A name is not: `acme-prod-db` is a container to a human and an ordinary word to a regex. For **shell commands** the identifier redactor closes that by knowing a fixed list of tools, plus a few shapes it redacts in any command; both are listed under @@ -1024,9 +1024,9 @@ list of tools, plus a few shapes it redacts in any command; both are listed unde miss the tool nobody thought of. **An `mcp__…` tool call is redacted the other way round:** its payload is a structured value, so rather than deny-listing known-sensitive fields, every value is redacted to `` by default and only the field names and the -shape are kept — no raw MCP payload value reaches the file. **Redaction is thorough but -not a guarantee.** Run `scan --review` to read every line the file will contain — and the -raw MCP payload values behind each `` — before it is written, and read it before +shape are kept; no raw MCP payload value reaches the file. **Redaction is thorough but +not a guarantee.** Run `scan --review` to read every line the file will contain (and the +raw MCP payload values behind each ``) before it is written, and read it before you post it anywhere public. **No dollar figures.** Counts and token totals are exact and come straight off the @@ -1076,7 +1076,7 @@ with plugin 0.3.0; to get it, run `agenttrail-guard init --agent claude`. Off by default, opt-in, and stack traces only. A crash is recorded to a file on your machine either way; nothing is transmitted until you turn reporting on and run `agenttrail-guard crash-report --send` yourself. Stack traces are scrubbed for secrets -*and* for file paths before they go anywhere — a raw stack carries your username, your +*and* for file paths before they go anywhere: a raw stack carries your username, your project's name and your client's name. No commands, no file contents, no environment variables, ever. @@ -1090,7 +1090,7 @@ agenttrail-guard crash-report --clear # delete everything spooled `--send` needs one more thing besides the setting: an endpoint, and **none ships by default**. Bring your own with `--endpoint `, the `AGENTTRAIL_GUARD_CRASH_ENDPOINT` environment variable, or `"crashEndpoint"` in `config.json`. With nothing configured, -`--send` refuses by name and transmits nothing — so a default install has no endpoint to +`--send` refuses by name and transmits nothing, so a default install has no endpoint to send to, by design. `crash-report` (no flag) shows whether one is configured. @@ -1115,7 +1115,7 @@ build if it stops holding: - For Cursor: exactly one JSON object on every call. `{"permission":"deny",…}` blocks, `{"permission":"ask",…}` asks (at `beforeShellExecution` only), and `{}` is no opinion, the answer to a warning and to a call nothing matches. - - For Codex: one JSON object when it blocks or warns, and no output otherwise — and + - For Codex: one JSON object when it blocks or warns, and no output otherwise; and that object carries exactly the fields Codex documents, never one more. Codex rejects a whole answer for a single field it does not recognise, and then runs the action, so one extra key would turn every block into a no-op. @@ -1130,8 +1130,8 @@ build if it stops holding: guard's entries do not set `failClosed`, so a hook that cannot run, or runs past its timeout, lets the call through. - For Codex: the guard never answers `allow`, which would skip Codex's own approval - card, and never asks, because Codex rejects an answer that asks and runs the action - — so a guardrail that would hold a call blocks it there instead. Where it has no + card, and never asks, because Codex rejects an answer that asks and runs the action; + so a guardrail that would hold a call blocks it there instead. Where it has no opinion it writes nothing. Codex itself fails open on a crash, a non-zero exit, bad output and a timeout, and has no setting that changes that. @@ -1148,22 +1148,22 @@ Local settings, logs, and crash records live under `~/.agenttrail/guard/`. Guard writes its private files at mode `0600` where supported. It also keeps `cursor/` and `codex/` integration folders there and adds entries to each app's hooks file when you install that integration. The -guard writes nothing else anywhere — not in your project, not in your Claude Code -settings, not in Codex's `config.toml` — with exactly one exception: `scan` writes +guard writes nothing else anywhere (not in your project, not in your Claude Code +settings, not in Codex's `config.toml`), with exactly one exception: `scan` writes `agenttrail-guard-report.html` into the directory you run it from, or to the path you give `--out`, because that file is for you to read and share. -- `config.json` — the packs you turned off (`disabledPacks`), the guardrails you turned off +- `config.json`: the packs you turned off (`disabledPacks`), the guardrails you turned off one at a time, per-guardrail action overrides, and the per-guardrail allowlist. It records only what you changed: every pack not named in `disabledPacks` is on, including packs added in later releases. Written by `init` if it is absent, and never overwritten after that. -- `guardrails.json` — your own guardrails, enforced alongside the bundled ones. Starts as +- `guardrails.json`: your own guardrails, enforced alongside the bundled ones. Starts as `[]`; `guardrails add` writes to it. One here that is invalid is skipped, not fatal, - and **`status` is where you find out** — a hook's stderr goes to a debug log you will + and **`status` is where you find out**: a hook's stderr goes to a debug log you will never read, so that is the only place it can tell you. A file that is corrupt entirely is ignored, and the bundled guardrails keep working. -- `events.jsonl` — the guard's own record of what it decided. One line per tool call +- `events.jsonl`: the guard's own record of what it decided. One line per tool call that matched a guardrail: the time, the tool, the decision, the guardrail id, the command, and last `agent`, the app that sent the call (`claude`, `cursor` or `codex`). A Cursor terminal command passes two checkpoints and is still written once, and so @@ -1172,17 +1172,17 @@ give `--out`, because that file is for you to read and share. We say we send you nothing, and separately we keep a file listing commands your assistant ran. Both are true, so here is exactly what that file is. **It never - leaves your machine** — nothing in the guard transmits it, and there is no code path + leaves your machine**: nothing in the guard transmits it, and there is no code path that could. **Every command in it is scrubbed before it is written**, not on the way out. **It does not grow forever**: it is capped at 1 MiB, and older records are dropped past 30 days. **You can empty it whenever you like** with `agenttrail-guard status --clear-history`, and deleting the file outright is always - safe — the guard simply starts a new one. -- `crashes/` — crash reports from the hook and `scan`, written whether or not crash + safe; the guard simply starts a new one. +- `crashes/`: crash reports from the hook and `scan`, written whether or not crash reporting is on. Scrubbed stack traces only, capped at 20 files and 30 days. Nothing here is transmitted unless you turn crash reporting on and run `crash-report --send` yourself, and deleting the directory is always safe. -- `cursor/` — only after `init --agent cursor`: +- `cursor/`: only after `init --agent cursor`: - `guard-hook.mjs`, the copy of the hook that Cursor runs; - `hooks.json.backup`, your `~/.cursor/hooks.json` as it was before the first install, or an empty `hooks.json.was-absent` when there was no such file; @@ -1190,13 +1190,13 @@ give `--out`, because that file is for you to read and share. runs and the guard version, so `status` can tell when a different version made it. Running `init --agent cursor` again when nothing would change writes nothing. -- `codex/` — only after `init --agent codex`, and deliberately the same four files as +- `codex/`: only after `init --agent codex`, and deliberately the same four files as `cursor/`, so uninstall and `status` have one shape to handle: - `guard-hook.mjs`, the copy of the hook that Codex runs; - `hooks.json.backup`, your `~/.codex/hooks.json` as it was before the first install, or an empty `hooks.json.was-absent` when there was no such file; - `install.json`, which records when the install was made, the hook copy, the Node it - runs and the guard version — and, because Codex identifies an approval by an entry's + runs and the guard version, and, because Codex identifies an approval by an entry's position, where in the file the guard's entries were written, so `status` can tell you when something has moved them. @@ -1213,7 +1213,7 @@ In `~/.codex/hooks.json`, which belongs to Codex, the guard appends one entry fo pre-tool event and one for the permission-request event, and keeps every other key and entry where it was. The file keeps its mode, and a file the guard creates gets `0600`. A project's `.codex/hooks.json`, an enterprise-managed hooks file, and Codex's -`~/.codex/config.toml` are never touched — the last of those matters, because Codex +`~/.codex/config.toml` are never touched; the last of those matters, because Codex adds the two hook sources together and an entry in both would run the guard twice on every call.