Why we gave the agent a shell
3 min read

Updated

Why we gave the agent a shell

An early web-testing experiment showed how tool interfaces can consume an investigation. A cookie jar made the problem concrete.

The agent knew it needed to log in and request another user’s profile. It spent its budget trying to pass the session cookie between two tools.

Our original web-testing interface exposed separate tools for crawling, form submission, and HTTP requests. Each was straightforward in isolation. Together, their different representations of state made a simple authenticated sequence difficult to complete.

We tried a smaller interface built around shell execution.

Let the HTTP client manage its own state

The shell gave the agent a familiar way to preserve cookies between requests. This simplified illustration shows the pattern, rather than a literal benchmark transcript:

# In an authorized local test environment:
curl -c cookies.txt -d 'username=tester&password=test-password' http://target/login
cat cookies.txt
curl -b cookies.txt http://target/api/users/2

The cookie jar keeps the session in one representation. The agent can inspect it and reuse it without translating a login tool’s output into another tool’s input schema.

A structured tool could also support this well. Our result exposed a weakness in our interface, not an inherent inability of typed tools to handle authentication.

What the early run returned

The historical ten-challenge slice used one attempt per challenge on a patched version of the public web benchmark. It wasn’t a full-suite score.

Challenge type Turns Outcome
IDOR 10 Flag recovered
Template injection 5 Flag recovered
Authentication / privilege escalation 9 Flag recovered
File upload 12 Flag recovered
Markdown injection 10 Flag recovered
Deserialization 4 Flag recovered
Blind SQL injection 20 Flag recovered
Bobby Payroll SQL injection 24 No flag
Melodic Mayhem N/A Azure timeout
GraphQL N/A Azure timeout

That is 7 successful challenges out of 10 attempted. The 2 provider timeouts remain in the denominator, but they should be distinguishable from completed investigations that failed to find a flag.

A separate blind-SQL-injection attempt exhausted a 15-turn limit. With a 25-turn limit, the recorded successful attempt took 20 turns. It was a reason to investigate budget sensitivity, not enough data to estimate the general benefit of longer runs.

Check the conversation loop before interpreting failure

During this work, our historical Responses API adapter serialized assistant text using the wrong content type. Requests failed as conversation history accumulated.

Those runs measured a broken integration. They couldn’t support conclusions about the model’s ability to solve the target. Preserving provider errors separately from unsuccessful attacks made the distinction visible.

A shell moves responsibility into the harness

General tools let the agent combine an HTTP client, a parser, and a short script without adding a new wrapper for each task. They also produce large outputs and expose broad capabilities.

The surrounding system must control credentials, filesystem access, networking, command lifetime, and output retention. The correct limits depend on the engagement and must be enforced outside the model’s instructions.

For our early workflow, shell access simplified state handling. The durable lesson was to inspect where the agent spent its turns. An investigation stalled on cookie formatting needs a different fix from one stalled on the target’s security logic.