Skip to content

critical: Agent transport allows cross-agent approval #168

Description

@jonathanchang31

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

  1. Initialize a KB and add a source.
  2. Propose a claim through JSONL as VOUCH_AGENT=alice.
  3. Try approving as alice to confirm self-approval is blocked.
  4. Approve the same proposal through JSONL as VOUCH_AGENT=bob.
  5. 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 agents proposals without human review. This weakens vouchs 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions