Skip to content

Tool responses are not recorded, so the trail cannot see how much data moved #45

Description

@blitzcrieg1

A tool call that reads a config file and one that reads a 40MB credential dump are the same event in the trail today. Nothing about a tool response is recorded at either capture tier, so the trail can say what an agent asked for and never how much it got back.

docs/agentmetry-event-schema.md already admits this in the coverage table:

| Tool outputs | Not logged on bus v1 | Roadmap |

Where it stands today

Tier A (MCP proxy). tools/mcp_audit_proxy.py reads result in two places only: to accumulate tools/list pages for the schema fingerprint, and to match an error response so it becomes a tool_failed event. Successful responses pass through untouched and unrecorded.

Tier B (IDE hooks). scripts/agentmetry_ingest.py receives tool_response from PostToolUse and uses it solely to decide the outcome:

if data.get("tool_response") is not None and outcome == "success":
    reason = reason or "tool_completed"

The payload is then discarded. So this is not a proxy-only gap, it is universal.

Why it matters

Exfiltration is visible in the response, not in the request. credential-exfil correlates an access with a later egress, which is the right shape and catches the sequence, but it cannot see magnitude. Magnitude is most of what separates a developer reading one key from a script walking a directory, and it is the single most useful number a responder wants when triaging a finding that is already on their screen.

It also closes a gap in the evidence story rather than the detection story. An audit record that omits how much data moved is answering half the question a compliance reviewer asks.

What to record

Metadata, never content. Same discipline as input_hash:

  • tool.output_bytes: size of the response payload
  • tool.output_hash: SHA-256, so two responses can be compared without either being stored
  • tool.output_truncated: whether the recorded size is the real size or a cap

A response body in the trail would be worse than not recording it. It would put file contents, query results and anything a poisoned tool returned into an append-only store that gets forwarded to a SIEM, which turns an audit trail into a data breach with good provenance. Hash and size answer the question that matters and carry none of that risk.

Open questions for whoever picks this up:

  • Where the byte count is measured. The serialised JSON-RPC result is the honest number at the proxy. At the hook tier the shape differs per IDE, so this may need a per-adapter helper rather than one rule.
  • Whether a size cap is needed before hashing very large responses, and what the cost is on a slow disk.
  • Whether tool_failed responses should carry the same fields. Probably yes, since an error body has a size too and a burst of large errors is its own signal.

Sequencing, deliberately

Recording is freeze-safe. It touches the proxy, the ingest script, the canonical builder and the API model, and none of detection/rules.py, detection/traits.py, detection/engine.py or mitre.py, so it does not move the ruleset fingerprint or restart the dogfood gate.

A detection rule that keys on response volume is not freeze-safe and must wait. The gate closes no earlier than 2026-09-05, and #44 lands first.

So: record the fields now, write the rule later. Landing the fields early is also the only way the rule has any history to be tuned against when it arrives, since a threshold picked with no data is a guess.

Prior art

Noticed while reviewing mcp-agent-security-gateway, an inline MCP gateway whose egress layer thresholds on response data volume. Different posture from Agentmetry, which records rather than blocks, but it surfaces a real hole on the recording side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions