toolCallResult payloads are unbounded, and a truncated result is indistinguishable from a complete one #154
rocklambros
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
steps/toolCallResultrequiresoutputs, each item requiresvalue, andvalueis declared as{}. No type, no size bound, and nomaxLengthormaxItemsanywhere in the schema.Tool results are the one payload class with no natural ceiling. A file read, a database query, a log fetch or a web scrape can produce megabytes, and the hook that carries them is the hook where output redaction and exfiltration policy actually run.
The failure. A large result meets a deployment's transport or envelope limit and something has to give. Either the request fails, which turns a legitimate large result into an outage, or the client truncates to fit. Nothing on the wire distinguishes a complete
valuefrom a truncated one.A Guardian that scans a truncated body and returns
allowhas decided on partial evidence and recorded the decision as though it were complete. The audit chain then commits achain_hashover a request that does not represent what the tool actually returned. That is worse than a failure, because it looks like a clean decision.It is also attacker-reachable in the obvious direction. If truncation is silent and the truncation point is influenceable, then padding a result past the limit pushes the interesting part out of the Guardian's view while the call still returns
allow.Shape. Inline the first N bytes, plus a digest over the complete body and a flag saying the inline portion is partial, with the Guardian able to fetch the remainder through a reference when its policy needs the whole thing. None of
content_ref,digestortruncatedexists in any of the 44 schemas today.The digest is the part that carries the security property rather than the convenience. It lets a Guardian that chooses not to fetch the remainder still commit to what the full body was, so the chain binds the complete result even when the decision was made on a prefix.
Alternatives considered. A hard ceiling with deny-on-exceed, which turns every large legitimate result into a denial and gets the limit raised until it stops meaning anything. Unbounded envelopes, which is a denial-of-service surface pointed at the Guardian, since the agent side chooses the size. Leaving it to deployments, which is the status quo and produces per-deployment truncation behavior that no cross-harness policy can reason about.
Open question. Whether the reference is a Guardian-initiated fetch, which gives the Guardian a new outbound dependency and a new attack surface, or whether the client pushes the remainder on request. The first is simpler and the second keeps the Guardian from making outbound calls on attacker-influenced URIs, which matters more than the simplicity does.
All reactions