All posts
Agentic AI

Every tool sees everything

Building the tool layer, we made one decision that kept everything simple: the agent passes the full execution context to every tool.

result = await tool(context, **arguments)   # every tool, the same call, the whole context

That uniform call is good engineering. It is also a privilege decision, made once, for every tool at once, and worth looking at directly.

What "the whole context" actually contains

The execution context is not a small object. By the time an agent is a few steps into a run, it holds:

  • The full event history. Every message, every tool call, and every tool result, which means the web pages fetched, the file contents read, the records pulled back.
  • The scratch pad. Whatever tools and the agent wrote to state: intermediate values, task progress, sometimes configuration or user details a tool stashed for later.
  • The run's identity. The execution id, the user and tenant if you recorded them.

Every tool receives all of it, on every call, whether it needs it or not. A calculator that needs two numbers is handed the entire conversation and everything every other tool has returned.

One convenient call hands each tool the full run: history, scratch pad, identity.One convenient call hands each tool the full run: history, scratch pad, identity.

For your own tools, it is untidy. For third-party ones, it is a boundary.

A tool you wrote seeing more than it needs is a least-privilege smell. Your code, your process, your data, so the exposure stays inside your own trust boundary. Worth tightening, rarely urgent.

A tool you did not write is a different matter. From the MCP posts earlier in this series: a tool can come from a published server, a package a teammate added, another team's service. When the agent hands the full context to that tool, it hands a third party your run's history and scratch pad, on every call.

Walk it through with our ticket agent. It reads customer tickets, pulls internal figures, then calls a third-party search tool for current pricing. The search tool needs one thing: the query. Under the uniform call, it receives the query and the ticket contents, and the internal figures already in the history. None of that was necessary for a web search, and all of it just crossed into code you do not control.

Nobody attacked anything. The convenient design shipped the data, exactly as written.

Two problems, one root

The over-broad grant is two distinct issues wearing one cause.

Least privilege. A tool holds access far beyond its job. A calculator that can read the whole run is a calculator with the reach of the whole agent, and if it misbehaves, that reach is what it misbehaves with.

Confidentiality across a trust boundary. For a third-party tool, the excess is not just untidy, it is data leaving your control. The full context can carry customer text, internal figures, and identity, none of which a search tool needed to see.

The tool needsThe tool receivesThe excess
A calculatorTwo numbersThe whole runThe entire history and scratch pad
A third-party searchThe queryThe whole runCustomer text and internal figures, sent offsite
A file readerA pathThe whole runEvery other tool's results

Scope what a tool receives

The fix is least privilege, applied to the context object instead of only to the tool list.

ControlWhat it changes
Pass tools their arguments, not the whole contextA tool that does not ask for context does not get it
Give context-aware tools a scoped viewExpose the slice of state a tool needs, not all of it
Never put secrets or raw PII on the shared contextIf it is not in the shared object, no tool can receive it
Keep third-party tools off the sensitive pathThe agent that calls untrusted tools is not the one holding the sensitive history
Log what each tool was givenSo an exposure is visible after the fact, not invisible by design

Least privilege for the context: each tool gets the slice it needs, third-party tools get the least.Least privilege for the context: each tool gets the slice it needs, third-party tools get the least.

The fourth row is the structural one, and it echoes the read-versus-act split from the tool-results post. An agent that talks to third-party tools should not be the same agent holding your customer data and internal figures. Separate them, and the convenient call stops being a leak, because there is nothing sensitive in the context it hands over.

What this touches

For the frameworks, this sits on the MAESTRO Agent Frameworks layer. The primary mapping is T3 Privilege Compromise, which the pack defines as weaknesses in permission management, including dynamic role inheritance, and the uniform context grant is exactly an over-broad inheritance of permissions. Where the receiving tool is third-party, T17 Supply Chain Compromise applies, since the pack notes a compromised component can obtain data. In the OWASP Agentic Top 10 the anchors are ASI03 and, for the third-party case, ASI04.

On the regulatory side, framed as scope rather than a citation: this lands in data minimisation, confidentiality and third-party handling, which every GCC data-protection and outsourcing regime addresses. Sending customer text to a third-party tool that did not need it is a disclosure those rules already govern, even though they did not picture a context object as the delivery mechanism. Which obligations apply depends on your regulator and architecture, and that mapping is an advisory estimate until someone checks it properly.

The part worth keeping

The uniform interface was the right call. Handing every tool the same shape is what kept the agent loop simple, and simplicity is a security property in its own right.

The refinement is narrow. Uniform interface does not have to mean uniform access. A tool can be called the same way and still receive only what its job requires. Keep the clean call, scope what travels through it, and keep the sensitive history away from the tools you did not write.