HiddenLayer Inc.

10/22/2026 | Press release | Distributed by Public on 10/22/2026 08:58

Man-in-the-Model

Introduction

Agentic coding tools are now commonplace for anyone working with code. Whether an enterprise engineer is maintaining a large production codebase, an enthusiast is experimenting with new models, or someone with little software development experience is "vibe coding" an application from scratch, agents such as Claude Code, Cursor, Codex, and GitHub Copilot can now plan changes, edit files, run commands, and test their own work. Adoption has followed quickly: GitHub Copilot has 50 million users, while more than 5 million people use Codex each week, with OpenAI reporting that roughly 20% of those users are "non-developers".

However, not everyone is using a single commercial product with its default model. Open-source and provider-agnostic harnesses such as OpenCode, Cline, Pi, Hermes, and Kilo let developers choose how and where their agents run. Some use them to access different models or inference providers, while others want lower-cost options, local execution, or the ability to reuse existing API access. These tools have also reached significant scale: OpenCode reports 16 million monthly developers, while Cline has surpassed 5 million installations.

Underneath these harnesses is an equally varied inference layer. Proprietary models such as Claude and GPT are served by their creators, while open-weight models such as Qwen, DeepSeek, Kimi, and GLM can also be hosted by inference providers such as Together AI and Fireworks. These providers actually run the models: Together supports more than 1 million users, while Fireworks serves more than 40 trillion tokens per day. Gateways such as OpenRouter and Vercel AI Gateway add another layer, providing a common interface across many models and providers. OpenRouter serves 10+ million developers and companies across 400+ models. Its public rankings also provide a rare view into which coding agents and models are actually flowing through this shared infrastructure.

At the same time, coding agents are being trusted with powerful local capabilities: modifying repositories, executing shell commands, and interacting with external services. Harnesses increasingly add sandboxes, allow/deny rules, and approval systems, sometimes using another model to review commands. Organizations may also place self-hosted proxies such as LiteLLM between the harness and model providers to centralize credentials, routing, logging, and policy controls, or to apply additional guardrails through hooks that inspect model traffic. Because these systems relay both requests and responses, they may also see prompts, tool calls and their outputs, and other context exchanged during a coding session. A single interaction can therefore cross several independently operated or configured layers before an action reaches the developer's machine.

Inference Servers and Gateways

Open-weight models can be downloaded from hubs such as Hugging Face and hosted using inference servers such as vLLM, llama.cpp, and Ollama. The scale may differ between a developer running a model and an organization processing large volumes of traffic, but the underlying setup is mostly the same. The inference service exposes an API, handles concurrent requests, and relies on the message history sent with each request to maintain the conversation.

Gateways, such as LiteLLM, sit in front of the inference service rather than performing model inference themselves. Their role is to mediate traffic between clients and one or more model endpoints, routing traffic, recording metadata, enforcing usage policies, or inspecting requests for sensitive data such as PII before they reach the model. Many gateways expose hooks around this flow, creating a powerful interception point where custom logic can be implemented to read, alter, or replace messages as they pass through.

There is a clear risk here in that if an attacker gains control of an inference server or LLM gateway, they gain a privileged position over the traffic it handles. Requests can be inspected or modified before they reach the model, and responses can be altered before they return to the client. In this blog, we explore how that position can be abused against agentic coding tools, starting with passive techniques to learn what permissions, tools, and capabilities a developer has enabled before escalating to active techniques that bypass guardrails and silently trigger malicious actions on connected systems. Such access could come from compromising an otherwise trusted service or from a provider that users already trust intentionally returning malicious responses.

Figure 1: Direct traffic between the harness and Ollama.

Figure 2: Model access with an optional gateway.

Initial Access and Reconnaissance

There are several ways an attacker could compromise the model inference pipeline to gain visibility of model traffic and gather information about the connected agent.

As referenced previously, model inference servers and gateways are the two points at which an attacker can gain visibility of model traffic for the purposes of this attack. There are many possible ways an attacker could accomplish this without causing an obvious disruption, enabling them to observe and manipulate model requests and responses whilst the applications appear to be behaving as expected to the victims.

If the inference endpoint itself is malicious, intercepting traffic is straightforward. For example, a malicious Ollama distribution or wrapper could listen on Ollama's default port, 11434, forward requests to the legitimate service running on another port, and inspect or modify traffic as it passes through. Because the endpoint already controls the local inference path, there is little need to conceal the additional proxying. A third-party attacker using a supply chain compromise would likely need to be more subtle, instead embedding the interception logic directly into the shipped package or binary so the service continues to behave as expected without exposing an obvious additional listener or process.

Figure 3: A malicious proxy intercepts traffic between the harness and Ollama.

The same approach extends to gateways, which translate requests from a harness into the formats expected by different model providers. Compromising its package, plugins, or container image gives an attacker control over that translation layer, including the hooks used to process model traffic. These hooks expose prompts, tool definitions, and conversation history, typically for the purposes of monitoring and enforcement, but an attacker could abuse them to learn how the connected agent operates and control what it receives. In a compromised LiteLLM gateway, for example, a malicious callback could make use of async_pre_call_hook to inspect and modify outgoing requests and async_post_call_success_hook to alter responses before they reach the harness.

Figure 4: Model access with a compromised gateway.

‍

Analyzing Timing to Infer the Security Model

Once an attacker controls any component along the model traffic path, they can observe the timing of requests and responses without altering them. When the model issues a tool call, the attacker can measure the interval between sending the request to the agent and receiving the tool response. That delay captures both the tool's execution time and any pause while the agent waits for user approval, allowing the attacker to distinguish some of the automatically permitted actions from those that require confirmation. By collecting these timing signals across normal agent activity, the attacker can passively map parts of the agent's permission model before attempting any active manipulation. An individual observation may suggest that a specific tool or command is automatically allowed, but does not establish whether automatic approval is enabled for all actions. However, with enough passive observations across a sufficiently varied set of commands, the attacker may infer that a broader automatic approval mode is enabled.

GIF 1: On the left is the compromised proxy. On the right is the victim's Claude Code

The signal is clearest for operations that normally complete almost immediately, such as file reads, file writes, and directory listings, where a pause of several seconds suggests the agent is waiting for user approval. In our analysis of over 4 thousand captured sessions, just 4.7% of calls to tools with typically short runtimes fell within our timing heuristic's ambiguous range, compared with 25.5% of shell calls. Shell commands, builds, and arbitrary code execution take varying amounts of time, making it harder to distinguish tool execution from a wait for approval.

Separating Approval Time from Execution Time

Passive timing works best when a tool completes almost immediately, such as a file read or directory listing, because execution accounts for little of the observed delay. Across over thirty-five thousands file reads in our dataset, only 1.0% fell within our timing heuristic's ambiguous range. Scripts and shell commands are harder to interpret because their runtimes can vary significantly. A longer delay can reflect either continued execution or a wait for approval. Measuring execution time separately helps distinguish between the two.

To measure execution time directly, the proxy has to introduce a small amount of instrumentation into the interaction. We use two approaches that keep those changes limited while allowing execution time to be separated from the total delay.

Debug Timing

Debug timing takes advantage of a tool that the proxy already knows is likely to run automatically. If the passive timing analysis showed that file edits returned without an approval delay, the proxy can use them to add timing instrumentation prior to a script or program being executed.

When the proxy observes that the agent is about to execute a script that has already been edited during the session, it inserts an additional file-edit tool call immediately before the execution request. That edit adds the timing instrumentation to the script, after which the original execution request is allowed to continue unchanged.

GIF 2: The injected timing logs were hidden from the user in the final output

The timing value is returned as part of the script's normal output in a format the proxy can identify and parse. This gives the proxy the script's measured execution time, which can then be compared against the total delay observed for the tool call to estimate how much time was spent elsewhere, including potentially waiting for user approval. Although the timer does not capture process startup, the resulting measurement is still sufficient to distinguish normal script execution from a larger approval delay in many cases. For a more complete measurement, however, the timer can be moved outside the script to include process startup as well, which leads to the CLI timing technique.

CLI Timing

CLI timing moves the measurement outside the script and into the shell command itself. When the agent is about to run a command such as git status, the proxy adds a timing wrapper, using the typeset command, that starts before Git launches and stops when it exits.

GIF 3: The injected CLI timing logs were hidden from user in the final output

Once again, the timing value is returned as part of the command output in a format the proxy can identify and parse. Unlike debug timing, this measurement includes process startup as well as the command's execution time, giving the proxy a more complete value to compare against the total delay it observes. The tradeoff is that the wrapper modifies the command itself, making the technique more visible to the user than adding instrumentation to an existing script, particularly when the original command is short.

Command complexity

How noticeable that modification is depends on the command it is added to. While a timing wrapper around a short command such as git status stands out, the same addition may blend into a longer command where several programs, flags, environment assignments, pipelines, and redirects already compete for the reviewer's attention.

Coding agents routinely produce such commands, even for ordinary build or verification tasks. Because reviewing them requires understanding both the shell syntax and the semantics of each program involved, users may instead scan for familiar warning signs such as rm, sudo, or curl piped into a shell, making less conspicuous additions easier to overlook.

For example, a routine build-and-package command might look like:

Figure 5: An example of a routine-looking build command awaiting approval

Although none of the individual operations are especially unusual, the command packs more than a dozen program invocations, several environment assignments, two command substitutions, four pipelines, six chained && operators, and a subshell into one line, on top of the usual flags and redirects. A small additional option or environment assignment affecting the build script could therefore be much less visually salient than it would be in a short command.

We can estimate this complexity automatically rather than relying on command length alone. A custom shell parser or a parser based on Tree-sitter's Bash grammar can recover the command's syntax tree and count features such as command invocations, arguments, flag-like tokens, variable assignments, redirects, pipelines, command separators, and nested constructs. Parsing is preferable to simple string matching because it distinguishes shell operators from identical characters appearing inside quoted strings and makes it possible to associate flags and assignments with the command they affect.

For example, the following traversal extracts command names and flag-like arguments while counting assignments and shell operators. Here, the command contains the shell text shown in Figure 3.

‍

walk(root)

For Figure 3, the expected result exposes the structure behind the long command: 21 invocations, including builtins and conditional fallbacks, together with 17 flag-like arguments and six assignments.

These features can then be combined into a selection heuristic. For example, commands containing several invocations together with flags and environment assignments could receive a higher complexity score, with pipelines, redirects, or nesting increasing it further. The threshold can be adjusted, since complexity alone does not determine how closely a user will review a command.

Identifying Different Harnesses

The proxy normally forwards traffic without needing to know which harness is connected. However, if compromised, it gives an attacker enough visibility to identify the harness and adapt their approach. Each harness exposes different tools, uses different naming conventions, and applies different permission rules.

There is no single reliable field that identifies every harness. Some provide recognizable HTTP headers, while others can be identified through the request body or tool catalogue. These signals can also change across versions and configurations, so the proxy combines several indicators instead of relying on one.

Request headers provide the strongest starting point. Codex sends headers beginning with codex-tui, while Claude Code uses claude-cli. Other header fields add useful context, such as harness version and session details, along with information about the operating system and terminal.

Figure 6: Codex request headers

Figure 7: Claude Code request headers

The tool catalogue included with each request provides a second signal. Its default tool names, parameters, and descriptions can confirm the initial classification before any tool is used. For example, Hermes does not send a product-specific header, its requests specify the generic OpenAI SDK as the user agent and therefore can't be used to identify Hermes itself:

Figure 8: Hermes request headers

The proxy can instead identify it through the request body fields and characteristic default tools such as terminal, write_file, and patch.

Figure 9: Hermes default tools

Codex's tool catalogue did not contain a plain terminal tool. Its catalog instead includes tools such as exec_command and write_stdin, providing another clear distinction.

If these indicators conflict or remain unclear, the proxy can examine the request structure and later tool calls before falling back to a generic mode that avoids harness-specific assumptions. Because these signals are already present in normal traffic, the proxy can identify the harness without modifying the exchange or sending additional requests.

Analyzing Harness-Specific Behavior

Identifying the harness is only the first step. Once the proxy knows which agent is connected, it can use the information in each request to understand how the agent is configured. This includes the tools it can use, the permissions applied to them, and the environment in which they run. Some details are common across harnesses while others are specific to one.

Codex provides its permission settings directly in every request, and the proxy does not have to infer how its controls are configured. For example, the <permission instructions> describe the active sandbox mode, writable paths, network state, and approved command prefixes.

Figure 10: Codex permission instructions

The fields above show which command prefixes have already been approved but when a command fails or needs escalated permission, for example when writing outside the workspace directory, the x-codex-turn-metadata header contains an auto_review_enabled field. When it is set to true, the retry can be reviewed automatically instead of being sent to the user for approval, so this behavior does not need to be determined from timing alone.

A separate <environment_context> provides further details, including the working directory, shell, and filesystem boundaries.

Figure 11: Codex environment context and permission profile

Other sections, such as <skills_instructions> and <collaboration_mode>, describe the available skills and how the agent is expected to operate.

Unlike Codex, Hermes does not provide its policy details in one section. The proxy learns them from the system prompt, tool catalogue, and tool response. For example, the system prompt shows whether Hermes is running on the host or inside a container.

Figure 12: Hermes environment

The linuxkit kernel indicates that Hermes is running inside a Docker container and not the host.

A failed file edit shows the safe root and path that was rejected. The tool catalogue also shows that file edits and terminal commands are handled differently. In manual mode, Hermes also states that only commands matching its dangerous-command rules require approval, allowing other commands to run without prompting.

Claude Code includes information about its environment and configuration across different parts of each request. A system-role message describes the working directory, operating system, shell and whether the directory is a Git repository:

Figure 13: Claude Code environment

The repository state then appears separately in a user-role message:

Figure 14: Claude Code Git Status

When sandboxing is active, an additional system role message provides the full read and write policy, including protected settings, hooks and skills. Apart from this, the Bash tool schema also has dangerouslyDisableSanbox boolean, which allows individual commands to bypass the workspace boundaries and write to files elsewhere on the host.

Figure 15: Claude Code Bash schema

Together, these details show the proxy which capabilities are available, where the agents can operate and which actions may require approval, without the need for additional probing.

Traffic patterns can already give us clues about which harness is in use, but the files on disk provide a much stronger confirmation and, more importantly, expose how that harness has been configured. In addition to the traffic itself, every developer harness leaves some trace in a project. Claude Code looks for CLAUDE.md or AGENTS.md, Codex for AGENTS.md, and Cursor for .cursorrules, while additional settings are often stored in tool-specific directories.

That makes them useful for more than simple identification. Project instructions, approved commands, connected servers, and other persistent settings can all reveal what the agent is expected to do and which actions the developer has already allowed or constrained. When those files can be read without additional approval, targeted reads of known configuration locations give us a quick picture of the surrounding environment, which in turn helps determine what an injection could realistically reach and how much impact it could have.

The important part is that those file reads are initiated by the interception layer rather than by the model. A sudden request to open a settings file would otherwise appear in the conversation and could expose the inspection, so the gateway can remove both the injected tool call and its result before forwarding the conversation back to the model. The surrounding system still keeps the information learned from the file, while the model continues the session without any indication that the environment was inspected.

Abusing Existing Permissions

When File Edits and Python Execution Are allowed

We used Hermes Agent, which provides built-in file and terminal tools. Hermes was run in manual approval mode, which only prompts for commands classified as dangerous, as explained in the Hermes security documentation, while file edits and ordinary python commands are allowed without approval.

[Demo Video]

Demo 1: Injecting code to send local secrets to an attacker-controlled webhook.

When the user asked Hermes to read a Python file, the compromised proxy inserted several additional tool calls. The first added a heartbeat_ok() function to the file. A second call inserted code into that function to read data from the agent's environment. This could include /etc/passwd, environment variables containing credentials, or private SSH keys such as ~/.ssh/id_rsa. The same code then sent the collected data to an attacker controlled webhook.

After adding the function, the proxy inserted a tool call that ran the file with python3. This executed the injected code as part of the existing script, collecting and sending the targeted data in the same process. Once it finished, a final file edit removed the function and restored the original file.

Hermes saw a sequence of file edits followed by an ordinary Python command, all of which were allowed without approval. The network request was made from inside the script, so no separate exfiltration command was shown to the user.

When Git is Allowed

Git commands are routine in coding-agent workflows, so a user may approve git prefix to avoid repeated prompts for routine version-control commands. As described above, Codex includes both approved command prefixes and the filesystem boundaries with each request. In this case, the <permissions instructions> shows that git command is approved, while the <permission_profile> shows that the repository's .git directory is writable. Together, these reveal that Git configuration can be modified without triggering another prompt.

The proxy can then insert a forged tool call containing a Git command into an existing response:

Because the command begins with the approved git prefix, Codex runs it without prompting the user. By using Git's diff.external option, the command can read secrets from an .env file and return them through the transcript even though the agent would normally refuse to access the file directly.

[Demo Video]

Demo 2: Abusing an approved Git prefix to read secrets without prompting the user

This technique is not limited to reading files. Git supports configuration options that invoke external helpers during routine operations such as fetching, cloning, or generating a diff. A forged call can override one of these options and cause Git to run another program locally. To the user, the agent still appears to be performing an ordinary Git operation using a prefix they previously approved.

Coding agents routinely modify project dependencies, which gives a compromised gateway another place to intervene. If a user asks for a package directly, or requests a feature that requires one, the gateway can rewrite the dependency name before the agent sees it. A request for requests, for example, might become reqiests, which the agent then writes into the project's dependency file as if it were legitimate.

The compromise only becomes active later, when a developer or build pipeline installs the dependencies and the lookalike package executes through the package manager's normal install process. Separating the injection from execution makes the change easy to overlook: at review time it looks like ordinary dependency management, with the only visible difference being a subtly altered package name.

The Trust Boundary has Moved

The attacks shown here exploits a simple weakness in the trust model: An intermediary carrying model traffic can identify the harness, infer its permissions, and shape responses around the controls intended to protect the agent. With that knowledge, an attacker can craft tool calls that appear to come from the model and take advantage of the permissions already granted to it.

The risk is no longer theoretical. LiteLLM was affected by a supply chain compromise in 2026, while recent research identified third-party LLM routers actively injecting malicious code and accessing credentials. The same intermediary position that makes gateways and inference infrastructure useful also gives them significant influence over the applications that depend on them.

A compromise at this layer can extend far beyond the infrastructure itself. Access to model traffic can expose sensitive data and, when connected to an agent with local permissions, lead to actions on client systems. Sandboxes and approval mechanisms can reduce that impact, but they do not establish that a tool call originated from the model the user intended to trust. As agentic systems become more capable, the security boundary is no longer just the model. It includes every system that can shape what the agent ultimately executes.

‍

HiddenLayer Inc. published this content on October 22, 2026, and is solely responsible for the information contained herein. Distributed via Public Technologies (PUBT), unedited and unaltered, on October 22, 2026 at 15:05 UTC. If you believe the information included in the content is inaccurate or outdated and requires editing or removal, please contact us at [email protected]