Summary
The review-gate docs say agents do not have an approve tool, but the JSONL/MCP tool surface exposes kb.approve to agents. The self-approval guard blocks the same VOUCH_AGENT, but a second agent identity can approve another agent’s proposal through vouch serve --transport jsonl, landing durable claims without any human CLI review.
Reproduction Steps
- Initialize a KB and add a source.
- Propose a claim through JSONL as
VOUCH_AGENT=alice.
- Try approving as
alice to confirm self-approval is blocked.
- Approve the same proposal through JSONL as
VOUCH_AGENT=bob.
- Search the KB and inspect
.vouch/decided/<proposal>.yaml.
Actual Result
Self-approval is blocked, but cross-agent approval succeeds:
-- alice proposes via JSONL server --
{"id": "p", "ok": true, "result": {"proposal_id": "20260605-160837-e5a60549", "status": "pending", "kind": "claim", "dry_run": false}}
-- same actor alice self-approval is blocked --
{"id": "self", "ok": false, "error": {"code": "invalid_request", "message": "forbidden_self_approval: alice cannot approve their own proposal ..."}}
-- different actor bob approves via JSONL server --
{"id": "approve", "ok": true, "result": {"kind": "claim", "id": "cross-agent-approval-lands-without-human-cli"}}
The durable claim exists:
claim/cross-agent-approval-lands-without-human-cli «cross» «agent» «approval» lands without human CLI (fts5)
The decided proposal shows agent-to-agent approval:
proposed_by: alice
status: approved
decided_by: bob
decision_reason: agent-side approval
Expected Result
Agent-facing transports should not expose kb.approve / kb.reject by default, or should require an explicit trusted-host/approver mode. A different agent identity should not be enough to satisfy the human review gate.
Evidence
Capabilities expose approve/reject:
approve/reject exposed: True True
selected methods: ['kb.propose_claim', 'kb.approve', 'kb.reject', 'kb.import_apply']
Docs conflict:
docs/review-gate.md:
Agents don't have an `approve` tool. They cannot self-promote.
Baseline validation:
86 passed, 6 skipped in 6.55s
Successfully built vouch_kb-0.1.0.tar.gz and vouch_kb-0.1.0-py3-none-any.whl
Root Cause
jsonl_server.py registers kb.approve as a normal handler and approves using only _agent():
def _h_approve(p: dict) -> dict:
a = approve(_store(), p["proposal_id"], approved_by=_agent(), reason=p.get("reason"))
approve()` only rejects `approved_by == proposed_by`. Any different `VOUCH_AGENT` passes.
## Security/Business Impact
In multi-agent setups, one compromised or untrusted agent can approve another agent’s proposals without human review. This weakens vouch’s central guarantee: agents propose, humans approve. The audit log records the event, but the unreviewed content is already durable and searchable.
## Suggested Fix
- Do not expose `kb.approve` / `kb.reject` in default agent-facing MCP/JSONL capabilities.
- Add an explicit config gate, for example:
```yaml
review:
expose_decision_tools: false
- Require
expose_decision_tools: true or review.approver_role: trusted-agent before registering decision tools.
- Keep CLI approval as the default human review path.
Summary
The review-gate docs say agents do not have an approve tool, but the JSONL/MCP tool surface exposes
kb.approveto agents. The self-approval guard blocks the sameVOUCH_AGENT, but a second agent identity can approve another agent’s proposal throughvouch serve --transport jsonl, landing durable claims without any human CLI review.Reproduction Steps
VOUCH_AGENT=alice.aliceto confirm self-approval is blocked.VOUCH_AGENT=bob..vouch/decided/<proposal>.yaml.Actual Result
Self-approval is blocked, but cross-agent approval succeeds:
The durable claim exists:
The decided proposal shows agent-to-agent approval:
Expected Result
Agent-facing transports should not expose
kb.approve/kb.rejectby default, or should require an explicit trusted-host/approver mode. A different agent identity should not be enough to satisfy the human review gate.Evidence
Capabilities expose approve/reject:
Docs conflict:
Baseline validation:
Root Cause
jsonl_server.pyregisterskb.approveas a normal handler and approves using only_agent():expose_decision_tools: trueorreview.approver_role: trusted-agentbefore registering decision tools.