mirror of
https://github.com/wshobson/agents
synced 2026-06-21 14:13:58 +00:00
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:
@@ -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"
|
||||
}
|
||||
@@ -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/)
|
||||
Reference in New Issue
Block a user