Merge pull request #495 from tomjwxf/feat/review-agent-governance-plugin

feat: add review-agent-governance plugin (second inhabitant of governance category)
This commit is contained in:
Seth Hobson
2026-04-27 20:29:49 -04:00
committed by GitHub
9 changed files with 970 additions and 0 deletions
+15
View File
@@ -1041,6 +1041,21 @@
"license": "MIT",
"category": "governance",
"keywords": ["tutorial", "skill", "recipe", "audit", "governance", "cedar", "receipts", "ed25519"]
},
{
"name": "review-agent-governance",
"source": "./plugins/review-agent-governance",
"description": "Require a human approval signal before an AI agent can post PR reviews, comments, merges, or writes to CI configuration. Joins protect-mcp and signed-audit-trails in the governance category; composes with protect-mcp for runtime enforcement.",
"version": "0.1.0",
"author": {
"name": "Tom Farley",
"email": "tommy@scopeblind.com",
"url": "https://github.com/tomjwxf"
},
"homepage": "https://veritasacta.com",
"license": "MIT",
"category": "governance",
"keywords": ["review", "governance", "cedar", "receipts", "human-approval", "pr-review", "ci-guard"]
}
]
}
@@ -0,0 +1,10 @@
{
"name": "review-agent-governance",
"version": "0.1.0",
"description": "Require a human approval signal before an AI agent can post PR reviews, comments, merges, or writes to CI config. Cedar-gated, receipt-signed, designed for the Hermes-style failure mode where a review bot posts without oversight.",
"author": {
"name": "Tom Farley",
"email": "tommy@scopeblind.com"
},
"license": "MIT"
}
+208
View File
@@ -0,0 +1,208 @@
# review-agent-governance
Require a human approval signal before an AI agent can post PR reviews,
comments, merges, or writes to CI configuration. Built on
[`protect-mcp`](https://www.npmjs.com/package/protect-mcp) + Cedar, with
every decision producing an Ed25519-signed receipt that verifies offline.
## The failure mode this addresses
AI agents that post to review surfaces (PR comments, approvals, merges,
CI workflow edits) can take actions that affect other contributors,
regulated systems, and the integrity of the codebase itself. When the
agent hallucinates, mis-reads context, or is tricked into acting
incorrectly, the damage is immediate and visible: bogus reviews show up
under a real account, merges happen that should not, workflow files get
rewritten.
This is not a hypothetical. Review bots have posted mass hallucinated
review comments, approved PRs they should not have approved, and edited
workflow files in ways that compromised other security controls. The
pattern is common enough to name: an automated agent is given scope to
act on review surfaces, and the lack of a human gate at the moment of
action is what turns a localized bug into a public incident.
## What the plugin does
Two hooks run around every Claude Code tool call:
1. **`PreToolUse`** checks for a human approval flag. If absent, evaluates
a Cedar policy (`./review-governance.cedar`) that forbids review-surface
actions unconditionally. Cedar deny means the tool call exits with code
2 and Claude Code blocks it.
2. **`PostToolUse`** signs an Ed25519 receipt of the attempt, whether it
was approved, denied, or skipped. The receipt chain records exactly
which actions were authorized and when.
Approved windows are opened by creating a `./.review-approved` flag file,
or by running the `/approve-review` slash command shipped with this plugin.
The window stays open until the flag is removed.
## What gets gated
The default policy forbids (unless approved):
- **`gh pr review`, `gh pr comment`, `gh pr merge`, `gh pr close`, `gh pr edit`**
- **`gh issue comment`, `gh issue close`, `gh issue edit`**
- **`gh release create`, `gh release edit`**
- **`gh api repos`** (catches arbitrary GitHub REST calls)
- **GitLab / Bitbucket equivalents** (`glab mr comment` etc.)
- **`git push` to `main`, `master`, `release`, `production`**
- **Writes to `.github/workflows/`, `.gitlab-ci.yml`, `.circleci/config.yml`**
- **`WebFetch` POSTs to `api.github.com`, `hooks.slack.com`, Discord**
Everything else passes through. This plugin is focused on the review
surface; use it alongside [protect-mcp](../protect-mcp/) if you want
general tool-call policy enforcement.
## Installation
```bash
claude plugin install wshobson/agents/review-agent-governance
```
Copy the default policy into your project:
```bash
cp .claude/plugins/review-agent-governance/policies/review-agent-governance.cedar \
./review-governance.cedar
```
Then either:
- **(Recommended)** keep hooks active for every session and open approval
windows explicitly before review actions, or
- Set `REVIEW_APPROVAL_FLAG=./never-approve` to effectively disable the
approval bypass (forces every review action through Cedar).
## Opening an approval window
### Flag file
```bash
touch ./.review-approved
# Let the agent perform the approved action
rm ./.review-approved
```
### Slash command (from inside Claude Code)
```
/approve-review "Posting the code review for #123"
```
The command creates `./.review-approved` with a note describing the
approval reason and appends a JSON entry under
`./review-receipts/approvals/`.
**Important note on the approval log:** entries under
`./review-receipts/approvals/*.json` are **plain JSON records, not signed
receipts**. They do not flow through `protect-mcp sign`, so
`@veritasacta/verify` does not cover them. The approval log is
operator-trust; it records what the human intended to approve but can be
edited after the fact without detection.
What IS signed and tamper-evident: the `PostToolUse` tool-call receipts
that every action (allowed or denied) produces under
`./review-receipts/*.json`. Those are the authoritative audit trail. Use
`npx @veritasacta/verify ./review-receipts/*.json` to verify them.
If you need signed approval records as well (for regulated environments),
run them through protect-mcp directly, or emit them as separate receipts
via `npx protect-mcp@latest sign --tool approve-review --input ...`.
### Listing pending or denied actions
```
/list-pending
```
Walks the receipt chain at `./review-receipts/` and prints any recent
`decision: deny` entries, so you can see what the agent tried to do that
was blocked.
### A note on what the signed chain covers
When the approval flag is present, the `PreToolUse` hook short-circuits
to `exit 0` without calling `protect-mcp evaluate`. The downstream
`PostToolUse` receipt for that approved action will therefore have
`decision: allow` but no `policy_digest` field, because no Cedar policy
was evaluated. Auditors walking the chain should expect this: an approved
tool call shows up as a signed receipt with `reason: human_approved` and
no policy reference. Denied tool calls and non-review actions (which do
go through Cedar) carry the `policy_digest` as usual.
## Example session
An agent working on a PR wants to post a review comment. Without approval:
```
$ agent: gh pr review 42 --comment --body "LGTM"
→ PreToolUse hook runs
→ No ./.review-approved file, policy evaluates
→ Cedar: forbid on context.command_pattern == "gh pr review"
→ Exit 2: Claude Code blocks the tool call
→ PostToolUse runs, signs a receipt with decision=deny
```
With approval:
```
$ touch ./.review-approved
$ agent: gh pr review 42 --comment --body "LGTM"
→ PreToolUse hook runs
→ ./.review-approved present, exit 0
→ Tool call proceeds
→ PostToolUse signs a receipt (decision=allow, reason=human_approved)
$ rm ./.review-approved
```
The receipt chain at `./review-receipts/` records both attempts: the
initial deny and the subsequent allow after approval. An auditor reading
the chain later can see exactly which actions were human-gated and when.
## Composing with protect-mcp
This plugin focuses on review-surface actions specifically. For general
policy enforcement across all Claude Code tool calls, install
[protect-mcp](../protect-mcp/) alongside it. They compose naturally:
- `protect-mcp` evaluates a general policy (e.g., deny `rm -rf`, restrict
`Write` to project root) for every tool call
- `review-agent-governance` adds the review-surface gate on top
Both hooks run, both produce receipts. Configure different receipt
directories (`./receipts/` and `./review-receipts/`) to keep the chains
separate if that helps your audit workflow.
## Why Cedar, why receipts
**Cedar** (AWS's open authorization engine) expresses policy declaratively
and formally. Reviewers read the policy to understand exactly what is
gated without reading code. Policies type-check with `cedar validate`.
Changes to the policy are diffable.
**Ed25519 receipts** (RFC 8032, JCS canonicalization per RFC 8785,
hash-chained) provide tamper-evident evidence that does not depend on the
operator. Any party with the public key can run
`npx @veritasacta/verify ./review-receipts/*.json` and get an exit code
that proves every receipt is authentic and the chain is intact. If any
receipt was altered after signing, verification fails with exit 1.
## Standards
- **Ed25519** (RFC 8032) for receipt signatures
- **JCS** (RFC 8785) for deterministic canonicalization before signing
- **Cedar** (AWS) for declarative, formally verifiable policy evaluation
- **IETF draft** [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) for receipt format
## Related
- [`protect-mcp`](../protect-mcp/) — general Cedar + receipt enforcement
for all Claude Code tool calls
- [`protect-mcp` on npm](https://www.npmjs.com/package/protect-mcp) — the
runtime this plugin depends on
- [`@veritasacta/verify`](https://www.npmjs.com/package/@veritasacta/verify)
— offline receipt verification CLI
- [Cedar for AI agents](https://github.com/cedar-policy/cedar-for-agents)
@@ -0,0 +1,164 @@
---
name: review-policy-author
description: Cedar policy author specialized in gating AI agent review actions (PR comments, reviews, merges, CI edits) behind human approval. Use when writing, auditing, or extending a review-governance.cedar policy for review-bot governance.
model: sonnet
---
# Review Policy Author
You are a Cedar policy expert specializing in review-surface gating: the set
of rules that decide whether an AI agent is allowed to post reviews, comment
on issues, merge pull requests, or edit CI configuration without human
approval.
## What you know
You understand the failure mode this policy class prevents. An AI agent with
unrestricted access to GitHub CLI or the GitHub API can post hallucinated
reviews, approve PRs with fabricated reasoning, close issues incorrectly, or
edit workflow files in ways that quietly bypass other security controls. The
damage is immediate, visible, and often attributed to the account running
the agent. Review-surface gating is the pattern that prevents this class of
incident.
You know the specific command patterns and paths that make up the review
surface on each major platform:
**GitHub (via `gh` CLI):**
`gh pr review`, `gh pr comment`, `gh pr merge`, `gh pr close`, `gh pr edit`,
`gh pr ready`, `gh issue comment`, `gh issue close`, `gh issue edit`,
`gh release create`, `gh release edit`, `gh api repos/.../comments`,
`gh api repos/.../reviews`, `gh api repos/.../pulls/.../merge`
**GitLab (via `glab`):**
`glab mr comment`, `glab mr approve`, `glab mr merge`, `glab mr close`,
`glab issue comment`, `glab issue close`, `glab release create`
**Bitbucket:** via `bb` CLI or direct API calls.
**CI / CD paths that must be human-gated:**
`.github/workflows/`, `.github/CODEOWNERS`, `.gitlab-ci.yml`,
`.circleci/config.yml`, `buildkite/pipeline.yml`, `Jenkinsfile`, `azure-pipelines.yml`
**Protected branches that must be gated:** `main`, `master`, `release`,
`production`, `prod`, `stable`.
**Notification surfaces:** Slack webhooks (`hooks.slack.com`), Discord
webhooks, Teams webhooks, PagerDuty events, any email API.
## How to help
When writing a review-governance policy:
1. **Start with the plugin's default.** Copy
`./plugins/review-agent-governance/policies/review-agent-governance.cedar`
to `./review-governance.cedar` and edit from there. The defaults cover
GitHub / GitLab / protected branches / CI paths and are a sound baseline.
2. **Extend for the project's specific surfaces.** If the team uses Linear,
Jira, Notion, or a custom review tool, add `forbid` rules for the CLI
patterns or WebFetch hosts those tools use.
3. **Do NOT gate read-only operations.** `gh pr view`, `gh issue list`, API
GETs — all fine for agents to do unattended. The gate is on write /
post / merge / close actions only.
4. **Gate branches by name, not by path.** Use `context.target_branch in
["main", ...]` not `context.resource_path starts with "refs/heads/main"`.
Branch names are what humans reason about.
5. **Include the notification surfaces.** Slack and Discord webhooks are
where review-bot hallucinations amplify. Gate POSTs to those hosts.
6. **Leave non-review actions alone.** This policy is focused. A permissive
`permit (principal, action, resource);` at the end lets everything else
through. Combine with `protect-mcp` for broader policy enforcement.
## Example extensions
### Teams that use Linear for issue triage
```cedar
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern starts with "linear"
};
forbid (
principal,
action == Action::"WebFetch",
resource
) when {
context.method == "POST" &&
context.url_host == "api.linear.app"
};
```
### Teams with their own internal review bot
```cedar
forbid (
principal,
action == Action::"WebFetch",
resource
) when {
context.method in ["POST", "PUT", "PATCH", "DELETE"] &&
context.url_host in [
"review-bot.internal.company.com",
"code-review.internal.company.com"
]
};
```
### Teams that want to allow a specific bot account
If the team wants to allow an agent running under a dedicated "automation"
identity but not a developer's personal account:
```cedar
permit (
principal == Principal::"gh-bot-reviewer",
action == Action::"Bash",
resource
) when {
context.command_pattern in ["gh pr comment"]
};
forbid (
principal,
action == Action::"Bash",
resource
) unless {
principal == Principal::"gh-bot-reviewer" ||
context.human_approved == true
};
```
## Auditing an existing policy
When reviewing a `review-governance.cedar`:
1. Confirm every review-surface CLI command the team uses has a matching
`forbid` rule.
2. Check for gaps in API coverage. `gh api repos` catches arbitrary GitHub
REST calls; without it, an agent can `gh api repos/X/Y/pulls/42/reviews`
and bypass command-pattern-based rules.
3. Verify protected-branch `git push` rules cover every branch that is
actually protected in the repo settings.
4. Confirm CI / CD path rules match the files that actually gate behavior
in this project (for example, some teams use `deployment/` instead of
`.github/workflows/`).
5. Check that the default-allow rule at the end does not override an
earlier `forbid`. Cedar `forbid` is authoritative; a later `permit`
does not lift it.
## References
- [protect-mcp docs](https://www.npmjs.com/package/protect-mcp) — runtime
this plugin depends on
- [Cedar language reference](https://docs.cedarpolicy.com/)
- [Cedar for AI agents](https://github.com/cedar-policy/cedar-for-agents)
- The plugin's default policy at `../policies/review-agent-governance.cedar`
@@ -0,0 +1,107 @@
---
description: "Open a review-action approval window by creating the ./.review-approved flag file. Takes an optional reason string that is embedded in the receipt chain."
argument-hint: "[reason for approval]"
---
# Approve Review
Open a human-approval window for review-surface actions (PR reviews,
comments, merges, CI edits). The window stays open until you remove the
flag file with `rm ./.review-approved` or restart the session.
## Usage
```
/approve-review "Approving LGTM on PR #42 after visual inspection"
/approve-review # no reason, still opens the window
```
## What this does
1. Creates a `./.review-approved` flag file in the project root.
2. If the user provided a reason, writes it into the file and into a
timestamped entry under `./review-receipts/approvals/`.
3. Prints a confirmation with the timestamp and, if provided, the reason.
4. Reminds the user to close the window with `rm ./.review-approved` as
soon as the approved action completes.
## Implementation
Run this in the Bash tool. Capture the full user argument as `$ARGUMENTS`
(the marketplace slash-command convention) so a reason with spaces is
preserved verbatim.
```bash
REASON="$ARGUMENTS"
TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
FLAG="./.review-approved"
# JSON-escape the reason so quotes, backslashes, newlines do not break
# the approval-record JSON below.
REASON_ESCAPED="$(printf '%s' "$REASON" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
# Write the flag file (human-readable key=value, not JSON).
{
echo "approved_at=$TS"
if [ -n "$REASON" ]; then
echo "reason=$REASON"
fi
} > "$FLAG"
# Record the approval. This is a plain JSON log file, NOT a signed
# receipt. The README explicitly notes that approval records are not
# signed by protect-mcp; only the PostToolUse tool-call receipts flow
# through the signer.
mkdir -p ./review-receipts/approvals
cat > "./review-receipts/approvals/$TS.json" <<JSON
{
"approved_at": "$TS",
"reason": $REASON_ESCAPED,
"flag_file": "$FLAG"
}
JSON
# Confirmation to the user
echo "Approval window opened at $TS"
if [ -n "$REASON" ]; then
echo "Reason: $REASON"
fi
echo ""
echo "Close the window with: rm $FLAG"
echo "The next tool call will be permitted without policy evaluation."
```
## What to show the user
```
Approval window opened at 2026-04-17T12:34:56Z
Reason: Approving LGTM on PR #42 after visual inspection
Close the window with: rm ./.review-approved
The next tool call will be permitted without policy evaluation.
Remember: every attempt in the approval window still produces a signed
receipt. Auditors can see exactly what you approved and when.
```
## Important notes
- **This does NOT grant blanket approval.** It opens a short window during
which the Cedar policy's review-surface rules are bypassed. Everything
else still runs through the policy.
- **Every action in the window is still receipted.** The chain records
that the action happened under an approval window, including the reason
you provided.
- **The window stays open until closed.** If you forget to `rm ./.review-approved`,
the agent could make additional review actions without prompting. Close
the window immediately after the approved action.
- **The flag file is session-scoped.** A new Claude Code session in the same
project directory starts clean if the file was removed at the end of the
previous session.
## References
- Plugin README: `../README.md`
- Policy authoring: `../agents/review-policy-author.md`
- Close the window: `rm ./.review-approved`
- See recent denials: `/list-pending`
@@ -0,0 +1,149 @@
---
description: "List recent denied review actions from the receipt chain. Shows what the agent tried to do that was blocked by the review-governance policy."
argument-hint: "[--last N]"
---
# List Pending Reviews
Walk the receipt chain at `./review-receipts/` and print any recent
`decision: deny` entries. These are the review-surface actions the agent
attempted that were blocked by Cedar, and represent candidates for human
approval via `/approve-review`.
## Usage
```
/list-pending
/list-pending --last 5
```
## What this does
1. Reads all receipts under `./review-receipts/` (or the directory set by
`REVIEW_GOVERNANCE_RECEIPTS`).
2. Filters to entries where `decision == "deny"`.
3. Sorts by `event_time` descending.
4. Prints the most recent N (default 10) with tool name, command pattern
or path, and timestamp.
## Implementation
Run this in the Bash tool:
```bash
set -euo pipefail
N="${1:-10}"
N="${N#--last }"
N="${N//--last/}"
N="${N:-10}"
RECEIPTS_DIR="${REVIEW_GOVERNANCE_RECEIPTS:-./review-receipts/}"
if [ ! -d "$RECEIPTS_DIR" ]; then
echo "No receipt directory at $RECEIPTS_DIR"
echo "Either no actions have been attempted yet, or the plugin is not active."
exit 0
fi
python3 <<PY
import json, os, sys
from pathlib import Path
from datetime import datetime
d = Path("$RECEIPTS_DIR")
if not d.exists():
print("No receipts directory.")
sys.exit(0)
denies = []
for f in d.rglob("*.json"):
if "approvals" in f.parts:
continue
try:
r = json.loads(f.read_text())
except Exception:
continue
if r.get("decision") != "deny":
continue
denies.append((r.get("event_time", ""), r, f))
denies.sort(key=lambda x: x[0], reverse=True)
if not denies:
print("No denied actions found. The review-governance policy is not currently blocking anything.")
sys.exit(0)
print(f"Recent denials (most recent first, top $N):")
print()
for ts, r, f in denies[:$N]:
tool = r.get("tool_name", "?")
ti = r.get("tool_input") or {}
summary = (
ti.get("command") or
ti.get("file_path") or
ti.get("url") or
"(no detail)"
)
if len(summary) > 72:
summary = summary[:69] + "..."
policy = r.get("policy_id", "unknown")
print(f" {ts} {tool:10} {summary}")
print(f" policy={policy} receipt={f.name}")
print()
print("To approve one of these and retry, run:")
print(' /approve-review "<reason>"')
print("Then retry the original tool call.")
print()
print(f"To audit the full chain: npx @veritasacta/verify $RECEIPTS_DIR/*.json")
PY
```
## What to show the user
```
Recent denials (most recent first, top 10):
2026-04-17T14:23:01Z Bash gh pr review 42 --approve --body 'LGTM'
policy=review-agent-governance receipt=2026-04-17T14-23-01Z.json
2026-04-17T14:22:45Z Write .github/workflows/ci.yml
policy=review-agent-governance receipt=2026-04-17T14-22-45Z.json
2026-04-17T14:20:11Z Bash gh issue comment 18 --body '...'
policy=review-agent-governance receipt=2026-04-17T14-20-11Z.json
To approve one of these and retry, run:
/approve-review "<reason>"
Then retry the original tool call.
To audit the full chain: npx @veritasacta/verify ./review-receipts/*.json
```
## When there are no denials
```
No denied actions found. The review-governance policy is not currently
blocking anything.
```
This is the common state. It means either the agent has not attempted any
review-surface actions, or the approval flag has been present for every
attempt.
## Notes
- Denials recorded before the current `./review-receipts/` directory was
created will not appear here. Use `@veritasacta/verify` directly against
any older receipt location.
- The command does not modify the receipt chain. It only reads.
- `./review-receipts/approvals/` (the log of explicit approvals) is
excluded from this listing since those are not tool-call receipts.
## References
- Approve an action: `/approve-review "<reason>"`
- Verify the chain: `npx @veritasacta/verify ./review-receipts/*.json`
- Plugin README: `../README.md`
@@ -0,0 +1,26 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "if [ -f \"${REVIEW_APPROVAL_FLAG:-./.review-approved}\" ]; then exit 0; fi; npx protect-mcp@0.5.5 evaluate --policy \"${REVIEW_GOVERNANCE_POLICY:-./review-governance.cedar}\" --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
}
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "npx protect-mcp@0.5.5 sign --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --output \"$TOOL_OUTPUT\" --receipts \"${REVIEW_GOVERNANCE_RECEIPTS:-./review-receipts/}\" --key \"${REVIEW_GOVERNANCE_KEY:-./review-governance.key}\""
}
]
}
]
}
}
@@ -0,0 +1,103 @@
// review-agent-governance: default Cedar policy
//
// Forbids an AI agent from taking consequential review actions without an
// explicit human approval signal. A human opens a short approval window by
// creating a ./.review-approved flag file (or using the /approve-review
// slash command shipped with this plugin). When the flag is present, the
// PreToolUse hook short-circuits to permit; when it is absent, this policy
// evaluates.
//
// Every tool call, approved or not, still produces an Ed25519 receipt via
// the PostToolUse hook. The chain records what was approved and when.
// ─────────────────────────────────────────────────────────────────────────
// Forbid rules: the set of actions that require human approval
// ─────────────────────────────────────────────────────────────────────────
// 1. Posting review-surface GitHub actions via gh CLI
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in [
"gh pr review",
"gh pr comment",
"gh pr close",
"gh pr merge",
"gh pr edit",
"gh issue comment",
"gh issue close",
"gh issue edit",
"gh release create",
"gh release edit",
"gh api repos"
]
};
// 2. Posting via GitLab / Bitbucket CLIs
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in [
"glab mr comment",
"glab mr approve",
"glab mr merge",
"glab issue comment"
]
};
// 3. Git operations on protected branches
forbid (
principal,
action == Action::"Bash",
resource
) when {
context.command_pattern in ["git push", "git push --force", "git push -f"] &&
context.target_branch in ["main", "master", "release", "production"]
};
// 4. Direct writes to CI / CD configuration paths. Modifying these files lets
// an agent change how future builds or reviews run, bypassing every other
// safeguard. Always require human approval.
forbid (
principal,
action in [Action::"Write", Action::"Edit"],
resource
) when {
context.path_starts_with in [
".github/workflows/",
".github/CODEOWNERS",
".gitlab-ci.yml",
".circleci/config.yml",
"buildkite/pipeline.yml"
]
};
// 5. WebFetch POSTs to known review / chat platforms
forbid (
principal,
action == Action::"WebFetch",
resource
) when {
context.method == "POST" &&
context.url_host in [
"api.github.com",
"api.gitlab.com",
"api.bitbucket.org",
"hooks.slack.com",
"discord.com"
]
};
// ─────────────────────────────────────────────────────────────────────────
// Permit everything else
//
// This plugin is focused. Non-review actions pass through this policy
// unchanged. Run it alongside protect-mcp if you want general tool-call
// policy enforcement too.
// ─────────────────────────────────────────────────────────────────────────
permit (principal, action, resource);
@@ -0,0 +1,188 @@
---
name: review-agent-setup
description: Configure human-in-the-loop gating for AI agent review actions in Claude Code. Use when setting up a project where an agent may post PR reviews, comments, merges, or edit CI configuration, and you want a cryptographically auditable approval trail with Cedar-enforced gates.
---
# review-agent-governance — Setup
Gate AI agent review actions (PR reviews, comments, merges, CI edits) behind
explicit human approval. Every attempt, approved or denied, produces an
Ed25519-signed receipt.
## When to use this plugin
Install it in projects where a Claude Code agent:
- Reviews, comments on, or merges pull requests (`gh pr review`, `gh pr merge`)
- Triages issues (`gh issue comment`, `gh issue close`)
- Publishes releases (`gh release create`)
- Modifies CI configuration (`.github/workflows/`, `.gitlab-ci.yml`)
- Pushes to protected branches (`main`, `master`, `release`, `production`)
- Posts to external notification surfaces (Slack webhooks, Discord)
If the agent is only doing local file edits and running tests, this plugin is
overkill. Use `protect-mcp` for general tool-call policy enforcement and skip
this one.
## One-time setup
### 1. Install the plugin
```bash
claude plugin install wshobson/agents/review-agent-governance
```
### 2. Copy the default policy to your project
```bash
cp .claude/plugins/review-agent-governance/policies/review-agent-governance.cedar \
./review-governance.cedar
```
You can edit this file to match your project's specific rules. See
`../agents/review-policy-author.md` for guidance on authoring review
policies.
### 3. Create a receipts directory and sign key
```bash
mkdir -p ./review-receipts
echo "./review-receipts/" >> .gitignore
echo "./review-governance.key" >> .gitignore
echo "./.review-approved" >> .gitignore
```
The first invocation of `protect-mcp sign` will create the key. Commit the
public key from the first receipt so auditors can verify later.
## Per-session workflow
The Cedar policy denies review-surface actions unconditionally. To approve
a specific action, open an approval window before it and close it after.
### Flag file (simplest)
```bash
# Before the action you want to approve
touch ./.review-approved
# Let Claude Code run the review / comment / merge
# Immediately after
rm ./.review-approved
```
### Slash command (from within Claude Code)
```
/approve-review "Reviewing PR #123 authored by contributor X"
```
This creates `./.review-approved` with the given reason embedded as a note,
and writes a human-approved receipt to the chain. A follow-up `rm` is still
needed to close the window.
### Dry-run everything (force full policy evaluation)
If you want every tool call to go through Cedar with no approval bypass:
```bash
export REVIEW_APPROVAL_FLAG=./.never-approve
```
Any tool call matching a forbid rule will be denied; approved windows have
no effect. Useful for CI or for a locked-down audit run.
## Verifying the chain
List all receipts:
```bash
ls -la ./review-receipts/
```
Verify the entire chain offline:
```bash
npx @veritasacta/verify ./review-receipts/*.json
```
Exit 0 means every receipt is authentic and the chain is intact. Exit 1
means one receipt has been tampered with. Exit 2 means a receipt is
malformed.
Look at recent denials:
```
/list-pending
```
Within Claude Code this slash command walks the receipt chain and prints
any recent `decision: deny` entries with the tool name, command pattern,
and timestamp.
## Example: approving a PR review
```bash
# 1. Human reviews the agent's proposed comment
$ /list-pending
Recent denials:
- 2026-04-17T14:23:01Z Bash "gh pr review 42 --approve --body 'LGTM'"
- 2026-04-17T14:23:02Z Bash "gh pr comment 42 --body 'Looking good'"
# 2. Human decides the first one is appropriate, approves it
$ /approve-review "Approving LGTM on PR 42 after visual inspection"
./.review-approved created
# 3. Agent retries the action; this time it succeeds
$ agent: gh pr review 42 --approve --body "LGTM"
[receipt: rec_XXX, decision=allow, reason=human_approved]
# 4. Human closes the window
$ rm ./.review-approved
```
Every step is in the receipt chain. The chain is offline-verifiable for
regulators, counterparties, or downstream auditors who want to confirm
that no review action bypassed the human gate.
## Composing with protect-mcp
If both plugins are installed, run them side by side:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "npx protect-mcp@0.5.5 evaluate --policy ./protect.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
}
]
},
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "if [ -f ./.review-approved ]; then exit 0; fi; npx protect-mcp@0.5.5 evaluate --policy ./review-governance.cedar --tool \"$TOOL_NAME\" --input \"$TOOL_INPUT\" --fail-on-missing-policy false"
}
]
}
]
}
}
```
Both hooks must pass for the tool call to proceed. Cedar deny in either
policy blocks it.
## Standards
- **Ed25519** — RFC 8032 (digital signatures)
- **JCS** — RFC 8785 (deterministic JSON canonicalization)
- **Cedar** — AWS's open authorization policy language
- **IETF draft** — [draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)