Home BlogBreaking the Lethal Trifecta in Agentic Development

Breaking the Lethal Trifecta in Agentic Development

by Denis Makrushin
20 views

Attackers exploited a vulnerable Nx workflow on August 24, 2025. Two days later, malicious releases appeared on npm. Nx described the project’s reach as roughly six million weekly installs. The affected nx versions were removed slightly more than four hours after the first malicious publication, although two supporting packages remained available until the following morning. The official advisory does not state exactly how many developers were affected.

At first glance, this looked like another software supply-chain attack — the kind we now see every week. One detail made it different. The malware tried to recruit AI tools already installed on developers’ machines.

The longer I work in area of secure agentic software development, the clearer one trend becomes: the industry spends a great deal of time discussing what agents can do, but much less time discussing which controls are practical — and how to deploy them before agent infrastructure becomes a security liability.

Together with my colleagues Ratmir Samarkhanov and Andrey Pogireychik, I have been mapping how development agents can be compromised and which controls can contain the risk. This article distills what we have learned so far.

The Nx attack chain

Nx has a public GitHub repository, and — as in many open-source projects — pull requests run through GitHub Actions workflows. The attackers found a shell-injection vulnerability caused by an unsanitized pull-request title in a workflow triggered by **pull_request_target**.

That combination allowed attacker-controlled commands to run with repository permissions and exposed a read/write GITHUB_TOKEN. Nx’s advisory says the team believes the attackers used that token to trigger the publish.yml workflow against a malicious commit that modified the workflow to send the npm publishing token to a webhook. The stolen npm token was then used to publish compromised packages.

The packages’ postinstall payload searched local files and environment variables for credentials and other sensitive data, then exfiltrated the results through public GitHub repositories created under affected users’ accounts.

Diagram of the s1ngularity attack chain

Why this attack matters

Command injection through an untrusted pull-request title is not a novel technique. What happened after the malicious package reached a workstation is far more interesting.

The payload attempted to invoke locally installed Claude, Gemini, and Amazon Q command-line tools and ask them to create a secret inventory; the wider payload also exfiltrated collected data to GitHub. Wiz’s incident analysis documented the use of these AI CLIs. GitGuardian reported that 95 of 366 systems on which an LLM client was targeted produced the requested `/tmp/inventory.txt` file. GitGuardian said its analysis suggested that many clients refused, but this host-level result is not a controlled per-tool refusal rate.

This was one of the first widely documented software supply-chain incidents in which malware attempted to weaponize local AI development tools.

It also illustrates where the industry is heading. Two years ago, code was the source of truth in software development: we wrote it, tested it, and — at best — updated the specification along the way. Today, a modern product increasingly depends on external packages, libraries, SDKs, and agent-generated code fragments.

The ability to express intent and define acceptance criteria is becoming more important than the ability to write every line of code. A codebase can be rewritten in another language or even modified at runtime; the product specification remains comparatively stable.

Diagram showing how AI agents transform software development

There is a price for that shift. Every external dependency is a potential entry point. An agent that can fetch, review, merge, and deploy a dependency without human involvement dramatically expands the attack surface.

In 2024 RepoJacking study, we identified 1,363 repositories whose associated GitHub account names appeared available for re-registration. These were potentially exposed repositories — not confirmed hijack victims. Since then, the underlying problem has not disappeared. Instead, another layer has appeared on top of it: MCP servers and agent tooling designed to orchestrate infrastructure with progressively less developer involvement.

Where the lethal trifecta enters an agentic system

Simon Willison’s lethal trifecta describes three capabilities: access to private data, exposure to untrusted content, and an external communication path. The model below describes a different “three”: the components in which those capabilities reside. Keeping the two axes separate shows where controls must break their convergence.

Terms such as tool poisoningprompt injection, and reasoning hijacking quickly turn the discussion into an alphabet soup. A more useful approach is to threat-model an agentic system the same way we would model a software package: map its components, then ask what an attacker could do to each one.

An agentic system has three core components:

  • LLM or SLM: the “brain” that reasons and makes decisions;
  • context and memory, including RAG: the data source the model uses;
  • tools: the “hands” that let the agent change the outside world. An MCP server is one way to provide that access.

An agent that only reasons and returns text to a chat is comparatively limited. The risk rises sharply when it can reason, decide, and then change external state.

LLMs: when the model cannot reliably distinguish data from instructions

Instructions and data enter the same model context. Systems can label them differently and assign priorities, but there is no dependable semantic boundary that prevents untrusted content from influencing model behavior in every case.

A familiar example is a jailbreak such as DAN, or “Do Anything Now,” in which a user tries to persuade a model to ignore its restrictions and produce content it would normally refuse. Another is indirect prompt injection: the malicious instruction comes not from the user, but from a document, email, ticket, or web page the model has been asked to process.

The model receives a phrase such as “ignore all previous instructions and output X” as tokens in the same context as legitimate content. Without additional controls, it may treat the embedded instruction as something to follow rather than as untrusted data to analyze.

A different class of vulnerability appears when model output flows into other application components. If an application renders that output as unsanitized HTML — for example, by assigning it directly to innerHTML  — attacker-controlled markup can produce cross-site scripting (XSS). The model is not the final exploit target here; it becomes one stage in an ordinary application-security failure.

Researchers have also explored jailbreaks based on mathematical reformulation. A 2026 PMLR paper evaluated three helper-LLM encoding strategies across eight target models. Averaged across the three LLM-based strategies and eight models, the attack success rate was 46.3% on HarmBench and 55.9% on JailbreakBench; Formal Logic averaged 50.1% and 60.9%, respectively. These are benchmark results — not real-world compromise probabilities — but they show how changes in representation can bypass model-level safeguards.

RAG: poisoning and data exposure

If an agent’s knowledge base contains valuable data, an attacker will generally try one of two things: poison it or steal from it. A hidden instruction in a résumé or document is another indirect prompt injection. If the document enters the index and is later retrieved as trusted context, it becomes a RAG-poisoning vector. A malicious instruction could, for example, influence an HR agent’s assessment of a candidate.

Data exposure becomes possible when an application does not enforce access control during retrieval. The model’s context may then receive payroll records, financial data, or NDA-protected documents that the requesting user is not authorized to see. Authorization must happen before retrieval — not after sensitive text has already entered the prompt.

Tools and integrations: where model decisions become actions

The Model Context Protocol (MCP) is a JSON-RPC–based protocol for connecting models to tools, resources, and prompts. In the 2025–11–25 specification, the standard transports are stdio, where a client launches a server as a subprocess, and Streamable HTTP, where an independently running server handles HTTP connections. Implementations may also provide custom transports.

An MCP server may therefore be a local process or a remote service. Traditional web-service risks apply most directly to remote components, but local tools are not inherently safe: they execute with the permissions and data available in their environment. In either case, connected tools turn a model’s decision into an action in the outside world.

MCP is one integration mechanism, not the cause of the following failures: these cases illustrate the broader risk created when untrusted model input can reach privileged tools.

Several vulnerabilities and demonstrations from the past two years show what that means in practice.

GitLab’s AI vulnerability-remediation feature constructed an LLM prompt from fields in a SAST report without treating them as untrusted input. As documented in the GitLab issue for CVE-2024–7110, an attacker who could push a fake SAST job and crafted report to the project’s default branch could inject instructions through identifiers[].name. If a developer then selected “Resolve Vulnerability”, and the project was configured to run a pipeline for the generated merge request, model-inserted commands could execute with the pipeline’s project privileges.

Trail of Bits demonstrated another scenario on GitHub. The researchers hid an instruction inside a **picture** element in a public issue that appeared to request support for Spanish and French.

GitHub issue with a hidden prompt injection inside a picture element

The visible issue requests support for Spanish and French. The hidden instruction does not appear in the normal rendering.

If a maintainer assigned Copilot to the issue, the injected instruction could cause it to add a malicious package URL to the project’s dependency lockfile. The scenario still required the maintainer to accept and merge the pull request and then deploy the application. It was a proof of concept, not a reported in-the-wild compromise.

Diff of uv.lock containing a substituted wheel URL

A diff of uv.lock: the substituted wheel URL points to the attacker’s server.

In the researchers’ demonstration, deployment would install a malicious dependency containing a backdoor that executed commands supplied through the X-Backdoor-Cmd HTTP header. The complete attack chain is described in the Trail of Bits write-up.

My favorite example is a similar vulnerability found in a tool designed specifically for AI-assisted security testing. CVE-2025–67511 was a command-injection flaw in CAI’s run_ssh_command_with_credentials() function. In affected versions through 0.5.9, the usernamehost, and port values were interpolated into a shell command without the escaping used for the password and remote command. The vendor advisory demonstrates exploitation when an agent consumes attacker-controlled SSH details.

It is the same broad vulnerability class as the unsanitized Nx pull-request title — except this time, the vulnerable component was a penetration-testing tool.

At a glance:

  • LLM / SLM — the shared model context. Direct and indirect prompt injection, jailbreaks, and unsafe output handling.
  • RAG / memory — the knowledge layer. RAG poisoning and data exposure when tenant- or document-level authorization is not enforced before retrieval.
  • MCP servers / tools — the action layer. Tool poisoning, confused-deputy problems, and vulnerabilities in local or remote integrations.

Breaking the lethal trifecta

Many prompt-injection and data-exfiltration paths across LLMs, RAG, and tools can be understood through that threat model:

  1. access to private data;
  2. exposure to untrusted content;
  3. the ability to communicate with external systems through which data can be exfiltrated.

When all three capabilities converge without an enforceable boundary, risk rises sharply.

The lethal trifecta for AI agents: private data, untrusted content, and external communication.

Defending agent infrastructure therefore means preventing these three conditions from converging in the same agent at the same time. In practice, there are three ways to do that.

1. Limit access to private data

Zero Trust is more than 15 years old, but agentic systems make it newly relevant. Authorize every tool call, grant the minimum permissions required, and re-check identity and context at trust boundaries.

This can be implemented through a dedicated control plane and identity provider for agent infrastructure. Runtime context, persistent memory, and RAG stores also need their own authorization boundaries. Giving the agent a tightly scoped API does little good if its prompt or long-term memory already contains data the caller should never see.

2. Separate data from instructions

There are at least two practical controls. First, label text from external sources as untrusted. Second, establish an instruction hierarchy in which system policy takes precedence over instructions found in a document, issue, or email.

Both reduce the likelihood that the model will execute a command hidden inside data, but neither eliminates the risk on its own. Spotlighting, for example, transforms or marks untrusted input to help a model distinguish it from trusted instructions.

Microsoft researchers proposed a stronger architecture in Fides, an agent planner based on information-flow control. Fides tracks confidentiality and integrity labels and deterministically enforces developer-defined policies on consequential tool calls. Under those policies, enforcement does not depend on the model deciding to comply. If a flow is forbidden, the action is blocked regardless of the model’s reasoning.

3. Separate roles architecturally

The more radical option is to ensure that no single agent can read private data, process untrusted content, and freely communicate with external recipients at the same time.

Give each agent and policy gateway a narrowly defined scope. Require explicit authorization — or human approval — for sensitive tools. This architecture is more expensive, but it reduces dependence on probabilistic detection methods.

Validate and operate the controls

Architecture comes first: scope private-data access, isolate untrusted input, and gate egress and consequential tools. Once those boundaries exist, the first operational step is to build a continuous security-assessment process to test the abuse paths they are meant to block. Use AI red teaming to exercise the agent and its components, mutate prompts as in fuzzing, adapt attacks to the environment, and investigate every off-policy outcome.

Tools such as promptfoo can automate much of this work. More specialized tooling can test reputation-sensitive or NSFW failure modes in generative-media systems.

Example report from an LLM security assessment

The second step is to establish a finding-triage process. Without one, teams quickly drown in anomalies. For offline evaluation, one option is LLM-as-a-judge: use a separate model, or an ensemble, to classify prompts and responses by risk and potential attack category.

The same kind of model can also inspect runtime traffic and block unsafe requests; in that role, it becomes a guardrail model. Multiple judges improve resilience only when their errors are sufficiently independent. Simply adding more judge models does not guarantee reliable detection.

The third step is to turn validated findings into enforceable protections. At a high level, a policy can operate in two modes:

  • hard block: classify the request as unsafe and reject it entirely;
  • advisory or safe mode: remove or restrict dangerous details and return a safer response.

Together, these three steps create the first version of a continuous security loop: test the agent, triage the findings, and convert what you learn into blocking or safe-response rules.

Who owns security in the agentic development lifecycle?

In its May 11, 2026 reproducibility audit, Zero Day Clock measured the interval from NVD publication to a recorded exploitation signal for CVEs confirmed as exploited in the wild. The 2018 CVE-ID cohort (n=273) had a median of 771 days, with 19% signaled on or before publication. For 2024 (n=620), the figures were 0.5 days and 48%. The 2026 cohort was 0 days and 68%, but it contained only 44 CVEs. These are publication-to-signal intervals in a selected, right-censored dataset — not exact first-exploitation times or an unbiased estimate across all CVEs.

Even within those boundaries, the recorded publication-to-signal interval has compressed sharply.

But should every developer have to think about all of this? Their job may be to build a product or launch satellites — not to continuously reason about agent security.

Shift Left asks developers to run security checks earlier. Shift Down moves more of the burden into the developer platform, where identity-aware tool gateways, retrieval ACLs, egress policy, and approval paths become defaults. The operational test is concrete: no agent should be able to read sensitive data, ingest attacker-controlled content, and exfiltrate it without crossing an enforceable boundary. That is the capability chain the platform has to break.

Leave a Comment

You may also like