Agent Steering: When the Attacker Uses Your Agent Against You Part 2
BY
Agent Security
8 MIN READ

Part Two: Weaponizing Agent Steering
In the first part of this series we set the stage, defining adversarial agent steering as a tactic and explaining why a planted skill is such an effective way to climb an agent's trust hierarchy.
In this part we show how an agent steering attack actually happens, and walk through the full attack flow. We take the trust model we described in part one and add the component that turns a generic injection, the kind a modern agent will often deflect, into something even the most powerful models will comply with. Then we run the attack end-to-end and look at how agents defend themselves, and where they hit their limits.
The scenario: an unvetted repository on a developer endpoint
The attack starts with a compromised code repository. It might be a forked project, an unauthorized change to a dependency, or simply an unvetted repo that a developer clones to reuse some code and save time. Developers pull repositories constantly, and no-one reads every line of code in them. Even worse, this simulation will show that even a capable, suspicious agent had a hard time finding the malicious payload in the repository.
We set a few constraints to keep the scenario realistic. The prompts had to make sense: something a real developer would actually ask an agent. They could not be over-engineered to push the agent toward a desired outcome. At the same time, the agent had to be the driver of the actions: it accesses the code, reads it, installs the packages, and ultimately executes the payload. Lastly, the payload had to use the real primitives such an attack requires - read the actual VSCDB from its real on-disk paths, build the malicious skill, and write it to a location where the next agent will load and trust it - all while a suspicious agent is looking at the code.
Why a skill, and the two problems we had to solve
As we argued in Part One, one of the most effective ways to earn an agent's trust is to give it a skill. Agents reach for skills to perform tasks and treat them with more trust than ordinary files on the endpoint. But modern agents do not follow skills blindly. They read the skill, analyze its instructions, and reason about them. A skill that says "send the SSH keys to this remote host" will be refused.
That left us with two problems to solve at once:
Delivery through code the agent will review. We needed a payload that a developer would plausibly run and that an inspecting agent would let through, even though it performs actions that a careful reviewer might find suspicious: reading local files, touching a database or reaching the network.
A skill precise enough to be trusted. We needed the resulting skill to blend into the developer's real work so well that the agent, and even a human reviewer, would treat it as a legitimate team convention rather than an implant.
The answer to the second problem is the same answer to why this tactic is dangerous at all: context and precision. A generic malicious skill gets refused. A skill that addresses the exact codebase, the exact framework, and the exact task the developer is in the middle of does not look like an attack. It looks like help. To get that context and precision, the attack first needs reconnaissance.
Reconnaissance
Coding agent platforms keep past conversations and actions in a local store. On Claude Code it is a collection of JSONL files; on Cursor, where we ran the simulation, the conversations live in the VSCDB. By targeting that local state database, the payload gets an unparalleled view of the victim's work: the developer's prompts, the agent's responses, the files and secrets that passed through the conversation, and the shape of the environment they are building in. For anyone who works inside an agentic system all day, that store is the center of gravity of their work, and it sits on disk, readable by user-level code. No privilege escalation, no sudo, to reach this much sensitive information.
That recon is what turns a generic implant into a targeted one. The attacker no longer has to guess what will look legitimate; the victim's own history tells them.
The attack flow
Delivery: We define a technique in the repository that causes the payload to run often, and in situations the developer will not think twice about. An npm postinstall script does this well: it fires on every npm install and update. Because installs frequently need to run outside the agent's sandbox, this repeatedly hands the payload an opportunity to execute as user-level code on the workstation - not once, but every time the developer touches dependencies.
Harvest: On execution, the malicious code reads the VSCDB. It scans for secrets, but just as importantly it captures context: what the developer is building, which services are involved, and how the setup is wired together. That harvested context can be handed to a remote LLM, which reviews it and synthesizes a custom-made skill. Because the skill is grounded in real context, it is much harder to flag as malicious - it is attached to a genuine workflow, so even a technical human reviewer would struggle to catch it.
Write-back: Finally, the package writes the skill back into a trusted skills location. Placed there, it inherits the elevated trust the agent gives skills, and it loads in future sessions, making the agent far less likely to refuse an instruction it would question coming from an ordinary file.
What actually happened across the runs
Whenever you run attacks against agents, many variables are in play, and they change between runs. The obvious one is the type of agent. But even against the same agent, with everything else held constant, there is a probabilistic element: in some runs the agent will refuse or warn the user about the risk, in others it will simply set up the project and execute the payload.
That said, the more capable agents read the code they were about to run. In the context of a freshly cloned repository and an npm script reaching for the local database, that was often enough to raise suspicion, and on a number of runs the agent balked or refused to proceed outright. Modern agents are not helpless - they are good at analyzing code, and in our case, at spotting suspicious skills. As the agents gained capability they detected the malicious code better and better, and the more capable the agent, the more evasive and confusing our code had to be to avoid detection, until it became difficult to reach the primitives we needed at all.
But this is an arms race with a ceiling, and it is not one a smarter model climbs past. Rice's theorem says that any non-trivial question about what a program actually does is undecidable in the general case; there is no procedure, however capable, that answers it correctly for every possible program.
An agent reviewing code before it runs is doing exactly that kind of analysis, so no matter how smart it gets, there will always be code whose behavior it cannot settle by reading. The most practical version of that limit is code that is not even present at analysis time: paths that do not exist while the agent is looking. The most common one is imported packages. Modern code pulls in dependencies, and the agent has no choice but to accept that; it cannot reason about every imported line, because that code is not in front of it and there is far too much of it.
That blind spot is what the packaged payload hides behind. The demo walks through it in three runs:
First install: The postinstall payload runs, but at this point the conversation history holds nothing matching the specific secret we are hunting (in the simulation, a Stripe key). The scan comes up empty, so the payload does nothing and leaves no obvious trace.
The secret enters the conversation: In the next prompt, the developer works with the Stripe secret as part of normal development. It is now sitting in the agent's local history, exactly where the payload is looking.
Second install: Now the secret exists in the history and there is context to work with. This time the scan finds the string, the remote LLM turns the harvested context into a custom skill for the developer's Stripe implementation, and that skill is written to the trusted skills directory - context-aware and tailored to the environment.
This payload is a preview of the coming wave of tailored agentic attacks: customized, built on the fly by an LLM, trust-building, and adapted to the coding agent they target.
Why your security product will stay quiet
Now walk back through the whole chain and grade each step in isolation, the way most detection does. An npm postinstall script ran: ordinary. A process read a local database file:unremarkable for developer tooling. A file was written into a skills directory: that’s the skill feature working as designed. Later, an agent loaded a skill whose description matched the task and acted on it - again, exactly what the feature is for. There is no single event you can point at and call malicious, because the maliciousness never lives in one event. It lives in the relationship between the goal that was planted and the actions taken to satisfy it - and that relationship only becomes visible when you reason across the whole session, not step by step.
That is the takeaway we want defenders to sit with. This is a simulation playbook for the kind of agentic attack an attacker can run to steer an agent toward their own goals by exploiting the agentic ecosystem itself. Defending against it means moving the unit of analysis from the individual action to the session - the only unit that actually carries the attacker's intent.
Part Two: Weaponizing Agent Steering
In the first part of this series we set the stage, defining adversarial agent steering as a tactic and explaining why a planted skill is such an effective way to climb an agent's trust hierarchy.
In this part we show how an agent steering attack actually happens, and walk through the full attack flow. We take the trust model we described in part one and add the component that turns a generic injection, the kind a modern agent will often deflect, into something even the most powerful models will comply with. Then we run the attack end-to-end and look at how agents defend themselves, and where they hit their limits.
The scenario: an unvetted repository on a developer endpoint
The attack starts with a compromised code repository. It might be a forked project, an unauthorized change to a dependency, or simply an unvetted repo that a developer clones to reuse some code and save time. Developers pull repositories constantly, and no-one reads every line of code in them. Even worse, this simulation will show that even a capable, suspicious agent had a hard time finding the malicious payload in the repository.
We set a few constraints to keep the scenario realistic. The prompts had to make sense: something a real developer would actually ask an agent. They could not be over-engineered to push the agent toward a desired outcome. At the same time, the agent had to be the driver of the actions: it accesses the code, reads it, installs the packages, and ultimately executes the payload. Lastly, the payload had to use the real primitives such an attack requires - read the actual VSCDB from its real on-disk paths, build the malicious skill, and write it to a location where the next agent will load and trust it - all while a suspicious agent is looking at the code.
Why a skill, and the two problems we had to solve
As we argued in Part One, one of the most effective ways to earn an agent's trust is to give it a skill. Agents reach for skills to perform tasks and treat them with more trust than ordinary files on the endpoint. But modern agents do not follow skills blindly. They read the skill, analyze its instructions, and reason about them. A skill that says "send the SSH keys to this remote host" will be refused.
That left us with two problems to solve at once:
Delivery through code the agent will review. We needed a payload that a developer would plausibly run and that an inspecting agent would let through, even though it performs actions that a careful reviewer might find suspicious: reading local files, touching a database or reaching the network.
A skill precise enough to be trusted. We needed the resulting skill to blend into the developer's real work so well that the agent, and even a human reviewer, would treat it as a legitimate team convention rather than an implant.
The answer to the second problem is the same answer to why this tactic is dangerous at all: context and precision. A generic malicious skill gets refused. A skill that addresses the exact codebase, the exact framework, and the exact task the developer is in the middle of does not look like an attack. It looks like help. To get that context and precision, the attack first needs reconnaissance.
Reconnaissance
Coding agent platforms keep past conversations and actions in a local store. On Claude Code it is a collection of JSONL files; on Cursor, where we ran the simulation, the conversations live in the VSCDB. By targeting that local state database, the payload gets an unparalleled view of the victim's work: the developer's prompts, the agent's responses, the files and secrets that passed through the conversation, and the shape of the environment they are building in. For anyone who works inside an agentic system all day, that store is the center of gravity of their work, and it sits on disk, readable by user-level code. No privilege escalation, no sudo, to reach this much sensitive information.
That recon is what turns a generic implant into a targeted one. The attacker no longer has to guess what will look legitimate; the victim's own history tells them.
The attack flow
Delivery: We define a technique in the repository that causes the payload to run often, and in situations the developer will not think twice about. An npm postinstall script does this well: it fires on every npm install and update. Because installs frequently need to run outside the agent's sandbox, this repeatedly hands the payload an opportunity to execute as user-level code on the workstation - not once, but every time the developer touches dependencies.
Harvest: On execution, the malicious code reads the VSCDB. It scans for secrets, but just as importantly it captures context: what the developer is building, which services are involved, and how the setup is wired together. That harvested context can be handed to a remote LLM, which reviews it and synthesizes a custom-made skill. Because the skill is grounded in real context, it is much harder to flag as malicious - it is attached to a genuine workflow, so even a technical human reviewer would struggle to catch it.
Write-back: Finally, the package writes the skill back into a trusted skills location. Placed there, it inherits the elevated trust the agent gives skills, and it loads in future sessions, making the agent far less likely to refuse an instruction it would question coming from an ordinary file.
What actually happened across the runs
Whenever you run attacks against agents, many variables are in play, and they change between runs. The obvious one is the type of agent. But even against the same agent, with everything else held constant, there is a probabilistic element: in some runs the agent will refuse or warn the user about the risk, in others it will simply set up the project and execute the payload.
That said, the more capable agents read the code they were about to run. In the context of a freshly cloned repository and an npm script reaching for the local database, that was often enough to raise suspicion, and on a number of runs the agent balked or refused to proceed outright. Modern agents are not helpless - they are good at analyzing code, and in our case, at spotting suspicious skills. As the agents gained capability they detected the malicious code better and better, and the more capable the agent, the more evasive and confusing our code had to be to avoid detection, until it became difficult to reach the primitives we needed at all.
But this is an arms race with a ceiling, and it is not one a smarter model climbs past. Rice's theorem says that any non-trivial question about what a program actually does is undecidable in the general case; there is no procedure, however capable, that answers it correctly for every possible program.
An agent reviewing code before it runs is doing exactly that kind of analysis, so no matter how smart it gets, there will always be code whose behavior it cannot settle by reading. The most practical version of that limit is code that is not even present at analysis time: paths that do not exist while the agent is looking. The most common one is imported packages. Modern code pulls in dependencies, and the agent has no choice but to accept that; it cannot reason about every imported line, because that code is not in front of it and there is far too much of it.
That blind spot is what the packaged payload hides behind. The demo walks through it in three runs:
First install: The postinstall payload runs, but at this point the conversation history holds nothing matching the specific secret we are hunting (in the simulation, a Stripe key). The scan comes up empty, so the payload does nothing and leaves no obvious trace.
The secret enters the conversation: In the next prompt, the developer works with the Stripe secret as part of normal development. It is now sitting in the agent's local history, exactly where the payload is looking.
Second install: Now the secret exists in the history and there is context to work with. This time the scan finds the string, the remote LLM turns the harvested context into a custom skill for the developer's Stripe implementation, and that skill is written to the trusted skills directory - context-aware and tailored to the environment.
This payload is a preview of the coming wave of tailored agentic attacks: customized, built on the fly by an LLM, trust-building, and adapted to the coding agent they target.
Why your security product will stay quiet
Now walk back through the whole chain and grade each step in isolation, the way most detection does. An npm postinstall script ran: ordinary. A process read a local database file:unremarkable for developer tooling. A file was written into a skills directory: that’s the skill feature working as designed. Later, an agent loaded a skill whose description matched the task and acted on it - again, exactly what the feature is for. There is no single event you can point at and call malicious, because the maliciousness never lives in one event. It lives in the relationship between the goal that was planted and the actions taken to satisfy it - and that relationship only becomes visible when you reason across the whole session, not step by step.
That is the takeaway we want defenders to sit with. This is a simulation playbook for the kind of agentic attack an attacker can run to steer an agent toward their own goals by exploiting the agentic ecosystem itself. Defending against it means moving the unit of analysis from the individual action to the session - the only unit that actually carries the attacker's intent.


