Skip to content
Back to Blog
agentic-ai-offsec

Your Agent Has Tools. Can You Prove What They Did?

Agentic AI for Offensive Security, Part 9. CLIs and MCP turn an agent's decisions into actions. A useful tool interface also tells you what was authorized, what reached the target, what failed and what the resulting evidence can support.

By Jason JeskeSeptember 9, 202612 min read3 topics
Your Agent Has Tools. Can You Prove What They Did?

Agentic AI for Offensive Security, Part 9. CLIs and MCP turn an agent's decisions into actions. A useful tool interface also tells you what was authorized, what reached the target, what failed and what the resulting evidence can support.

Give an agent a scanner and it can run a scan. That is the easy part. When the connection times out, the target redirects somewhere else or the output contains instructions from an attacker, the tool interface has to decide what the result means. Otherwise the agent's final report can look complete while the underlying work is not.

Earlier in this series, the problem was controlling a nondeterministic agent without pretending it could be made deterministic. This chapter returns to the tools and MCP layer in Part 9 of the original foundations track, examined through a small, isolated test rather than another promise that the harness will handle it.

The test did not use a language model. That was deliberate. Before asking whether an agent makes a good decision, establish what the execution layer does when it receives a bad one.

A shell and an MCP server solve different problems

A CLI is often the cheapest route to an existing capability. A utility already has an executable, arguments, exit status and output. A wrapper can expose the small part needed for an engagement without rebuilding the utility. When the tool supports structured output, use it. Parsing a machine-readable result is usually less ambiguous than asking the model to interpret a terminal progress display.

A generic shell exposes much more freedom. The agent can combine commands, redirect output and call other installed programs. That is valuable for an authorized operator working through an unfamiliar problem. It also means the apparent tool name, “run command,” says very little about the actual authority being granted. The process's filesystem access, credentials and network reach define much of the consequence.

MCP standardizes how a client discovers tools and invokes them. The current tools specification defines names, input schemas, optional output schemas and structured or unstructured results. A local CLI can sit behind an MCP server; an API or findings store can expose the same interface. The protocol reduces integration differences. It does not decide whether the caller is entitled to run a particular test against a particular target. (See the MCP tools specification.)

Use the interface that fits the operation. A narrow tool is a good choice for repeatable work with a stable contract. A shell is reasonable when the approved task needs its flexibility and the surrounding restrictions can contain it. Wrapping a shell in MCP does not make it narrower; accepting an arbitrary command parameter still exposes arbitrary commands within that process's privileges.

Define the operation before exposing the executable

Consider a response-inspection tool for an authorized assessment. The model should not need to invent command-line flags every time it wants the same observation. Give it an operation with a defined input, a bounded effect and an outcome it can interpret.

For the isolated test in this chapter, the request accepts exactly one field, target_id. A trusted registry maps that identifier to a route on a temporary loopback HTTP fixture. The caller cannot supply a hostname, port, redirect policy or shell command. The runner itself chooses a GET request, a timeout and a response-size limit.

This is intentionally less powerful than a general web tool. That is what makes its promise understandable: inspect one registered fixture response. It is not an internet scanner, a crawler or an exploitation interface.

A production version needs a larger contract:

  • Principal: derive the caller from verified authentication, not a user-editable argument.
  • Scope: bind the target and allowed operation to the engagement and its current authorization.
  • Request: specify methods, parameters, request budget and destinations the operation can use.
  • Execution: bound runtime, output size, subprocess behavior and network reach.
  • Outcome: distinguish denied, not started, incomplete, completed and failed validation.
  • Evidence: preserve the observation, tool version, normalized request and protected artifact reference.

The model can recommend an action. The runner decides whether that action is permitted. Those decisions must remain separate even if the model's explanation is persuasive.

What the isolated test actually did

The test used Python's standard-library HTTP client and a temporary server bound to 127.0.0.1 on the Mac Mini. The server recorded every request it received. The runner connected only to that process's chosen loopback port. The fixture offered a normal response, a redirect, a slow response, an oversized response and a response containing hostile instructions. No customer system, public target or real credential was involved.

The runner had a 100-millisecond socket timeout and a 1,024-byte response limit. These are small fixture values selected to exercise failure behavior, not recommended production defaults. The slow endpoint delayed its response for 250 milliseconds; the large endpoint returned 2,048 bytes. The response containing hostile instructions told the reader to fetch a URL at example.invalid.

Nine initial cases completed with the expected outcomes:

  • Allowed identifier: completed, with a seven-byte evidence artifact and a content hash. The fixture recorded one request.
  • Wrong principal, unknown identifier, a URL used as the identifier, or an extra argument: denied before a request reached the fixture. Each was a separate case.
  • Redirect: the fixture received the initial request and returned HTTP 302. The runner rejected the redirect rather than following it.
  • Timeout: incomplete. The fixture recorded the request even though the runner did not receive a completed response.
  • Oversized response: incomplete, not a silently truncated success.
  • Hostile response content: collected as evidence. It did not change the registry or execute another request.

A separate follow-up attempted the URL named in the hostile response. The runner denied it as out of scope. Across the test, the fixture recorded exactly five requests: one for each registered response path. The invalid inputs and rejected follow-up added none.

The outcome is narrow but useful. The runner enforced these specific input and execution rules, and the fixture supplied a second observation of what reached it. This is stronger than merely reading a denial message in an agent transcript.

A timeout is not “nothing happened”

The timeout case is the one to carry into an engagement. The caller saw an incomplete result. The target had already received the request.

For a simple GET against a fixture, the consequence is small. For a tool that creates an account, changes a policy, submits a job or sends a message, retrying after a timeout can repeat an action that already occurred. The absence of a response tells you about the caller's knowledge, not necessarily the target's state.

Define duplicate handling before the model calls the operation. An idempotency key may be appropriate when the downstream API supports it. Otherwise use a read-back path to determine whether the action occurred, and hold the retry when the outcome cannot be established safely. A generic “retry three times” instruction is not a transaction strategy.

MCP can carry execution failures through isError, and the current specification distinguishes completed results from cases requiring additional input. Those transport fields do not define your business outcome for you. Your application still has to distinguish an accepted request from a verified change and a verified change from a valid security finding. (See tool results and error handling.)

Do not call active reconnaissance harmless because it reads

Port scanning and content discovery are often placed in a read-only bucket. That label is incomplete. Requests consume resources, can trigger expensive application behavior and may reach systems that were never included in the engagement. A tool can cause trouble without issuing an obvious write operation.

Classify the actual behavior. Reading an already collected artifact is different from sending new traffic. A passive lookup is different from active discovery. Credential retrieval deserves a separate boundary from public-content retrieval. An evidence exporter that sends data to another service changes where that data exists even if it never modifies the original.

Keep routine authorized work efficient. You do not need a fresh human click for every permitted fixture request. You do need enforced scope and an agreed request budget. Reserve approval for consequences that require a decision, and bind it to the exact operation, target and material parameters. A changed payload should not inherit an earlier approval merely because the tool name stayed the same.

Redirects are the first scope test, not the last

The fixture deliberately disabled redirects. It did not implement a general-purpose safe URL fetcher. That distinction matters because a production hostname allowlist has several failure paths: a destination can redirect, its DNS answer can change, or a permitted service can itself reach something outside the intended scope.

If redirects are needed, check every destination before connecting and enforce the route outside the model. DNS validation must remain consistent with the address used for the connection. Network egress policy supplies another layer, but its exceptions also need to match the engagement. An allowlisted name is not proof that every request behind it is authorized.

The MCP security guidance covers SSRF risks around OAuth metadata discovery and related URLs, including redirects and private-network destinations. That is a protocol-specific example of the same engineering problem: following a URL is an action with a destination, even when the URL arrived inside something labeled metadata. (See the MCP security guidance.)

The response is evidence, not a new engagement order

A target can return an instruction in a page, document or tool result. The instruction may be written to look like an internal policy, a debugging hint or a message from the person running the assessment. Its location in a response does not grant it authority.

In the fixture, the hostile sentence remained ordinary bytes in an evidence file. The deterministic runner did not interpret language at all. When the separate follow-up supplied its suggested URL, the same scope check denied it.

This is not a prompt-injection resistance score. No model saw the response, so the test says nothing about whether a particular model would try to obey it. It shows that the runner can reject the resulting out-of-scope request even if something upstream proposes it. That is a property worth testing independently of a model's refusal behavior.

Do not confuse tool metadata with enforcement either. The tools specification says annotations must be treated as untrusted unless they come from trusted servers. A trusted description still does not prove that a process cannot write files or use a powerful downstream credential. Review what executes and what it can reach, not just what the catalog calls it. (See tool annotations.)

Authentication must survive the entire route to the tool

The fixture's principal check used a supplied trusted value. It was not an authentication implementation and did not test token verification. Production access needs the latter.

That distinction is not theoretical. LiteLLM's CVE-2026-59822 advisory describes an MCP fallback path that could replace failed key validation with an empty authentication object. The stated impact was access to configured MCP tooling with a fabricated Authorization header. The advisory identifies 1.84.0 as the minimum fixed version. A tool can have a beautifully defined input schema and still be exposed to the wrong caller if the authentication path fails before the tool is invoked. (Read the maintainer advisory.)

Test discovery and execution with the credentials and denied cases your application actually supports. Validate the intended token audience. Do not pass an unrelated upstream bearer token through and assume a downstream service will infer the right authority. MCP's security guidance explicitly forbids token passthrough and requires tokens to be issued for the server accepting them. A shared protocol does not create a shared identity boundary automatically.

What the evidence can support

A completed response-inspection operation supports a statement about the response obtained from that endpoint at that time. It does not establish that the application is secure, that an exploit succeeded or that the agent fulfilled the entire engagement.

Keep enough information for another operator to reconstruct the observation: the tool and contract version, scope reference, normalized arguments, decision, timestamps, outcome and evidence location. A hash identifies the bytes. It does not prove those bytes are accurate or that the interpretation is correct. Protect sensitive responses and credentials rather than copying everything into a general-purpose transcript.

The earlier article You Do Not Fix an Agent by Making It Smarter dealt with recurring failure modes. The foundations finale put accountability around the operating model. This chapter makes that accountability inspectable at one boundary: what the tool was asked to do, what it permitted and what the target received.

The fixture does not establish production security. It leaves authentication, DNS, operating-system isolation, actual MCP transport, state-changing operations and model behavior untested. Those are separate tests, not footnotes to a passing result.

Part 10's subject is multi-agent coordination. Its starting question is whether another worker improves the task, and whether the evidence survives the handoff. Giving more agents access to an ambiguous tool does not resolve the ambiguity. It gives you more transcripts to reconcile.

Independent engineering analysis. Views are my own and do not represent my employer. The reported experiment used an isolated local HTTP fixture with synthetic inputs. Protocol claims refer to the 2026-07-28 MCP documentation; the fixture was not an MCP implementation or a live model evaluation.

Series
  1. 01Everyone Selling You an AI Agent Isn't Telling You the Whole Truth
  2. 02Agentic AI Is a Weapon You Can't Aim
  3. 03Your AI Agent Is Chaos. Tame It.
  4. 04How Do You Actually Hack an AI?
  5. 09Your Agent Has Tools. Can You Prove What They Did?
Krypteia Sec ResearchSeptember 9, 2026