Back www.pillar.security Ill Just Call You Agent To Agent Privilege Boundary Failures In Ci Cd On Googles Adk Repository
Pillar Security researchers have identified the first practical, real-world case of agent-to-agent exploitation in a multi-agent system in a real production environment, a class of attack not seen in real production systems until now. A case where one AI agent can be used to attack another, turning a benign automation into a path that ends in a potential software supply chain compromise.
We found the exploit in google/adk-python , the repository behind Google's Agent Development Kit for Python, an SDK many teams use to build their own agents.
The shape of the problem is simple to state. The repository ran two classes of automated AI agents. The first class of low-privileged agents was embedded in workflows open to interaction, which were activated when a user opened a pull request or an issue. The other class was high-privileged and meant only for maintainers, trusted to act on the repository with real authority. The vulnerability is that the low-privileged, public-facing agent could be manipulated into reaching across and triggering the high-privileged one.
This finding demonstrates that our world is changing quickly, and new attack surfaces are not yet reflected in threat models because these attacks never could exist in the first place in the “pre-agent” world. CISOs and security practitioners should start considering these scenarios, threat-modeling them, and calculating worst-case implications and blast radius. We would like to thank Google for their close cooperation. Pillar Security and Google worked closely to resolve this issue, and it has been mitigated.
In our report ( My Agentic Trust Issues ), I showed that agentic workflows on GitHub can lead to a supply chain compromise if the conditions line up right, meaning all the conditions of the lethal trifecta are met. In that case: prompt injection is ingested from a public issue, the agent acts on it by executing tools inside a runner with exposed secrets, and the agent externally communicates the stolen data to an attacker-controlled server. But what happens if an attacker finds an agentic workflow that could lead to the same conditions being met - but the workflow is gated behind bars that only a repository member can unlock?
One morning, one of my automations flagged an agentic workflow with all the ripe conditions for a potential supply chain compromise: gemini-invoke.yml . This workflow was meant to trigger only from a that members, collaborators, or authors could post. Those privileged users triggered it by writing a on a PR starting with @gemini-cli , and their was passed as a prompt into the gemini-invoke workflow, which the agent would then follow.
A second workflow, gemini-review.yml , did much the same thing but with a different system prompt, asking the agent to review a PR in response to the @gemini-cli /review . There was also a gemini-dispatch.yml workflow that routed these commands to the right handler.
Several issues quickly surfaced. Both workflows ran with pull-requests: write and issues: write tokens, not enough to push code to the repository or trigger other actions. On top of that, running code on the runner itself through these workflows didn't seem straightforward: instead of using the run_shell_command tool to run bash, these agents were wired to the GitHub MCP server and were only allowed to interact with it. Most importantly, they were gated - only high privileged users could trigger these commands.
As I explored the repository further, I noticed the maintainers were running agentic workflows built on ADK agents. For example, this workflow, pr-triage.yml , was responsible for triaging PRs using a custom ADK agent called adk_pr_triaging_agent , whose code lives in the adk_pr_triaging_agent . This agent could on, view, and label PRs, as seen in its tool definitions:
While looking through the PRs this agent had commented on, I noticed an anomaly. The agent as a user, not as a bot and that user has a Collaborator account, which makes it a very powerful, high-privileged account in the repository. The ADK team had tied an agent to a user account and created a PAT for it.
It clicked instantly: if I can get adk-bot to post @gemini-cli as a on a PR, I can force it to invoke the privileged workflows with a malicious prompt.
The first challenge was simply to check whether my hypothesis held: could a recursive workflow be set off through prompt injection? Concretely, if I could induce the agent inside one workflow to begin its with @gemini-cli , would gemini-dispatch catch that mention and route it onward to gemini-invoke, kicking off a second, more privileged run?
The injection itself wasn't really in question. Other researchers had already managed to prompt inject the model, as seen in PR #5884 , where a researcher forced it to output a PoC marker as seen in the image below:
I wasn't trying to prove the agent could be injected; I wanted to know whether that injection could reach across and trigger a downstream workflow.
To test it, I used the agentic workflow research lab we set up to simulate this exact environment. I created a PAT for one of my accounts and connected it to a PoC project. The first prompt injection I tested was meant to simply get the agent to emit an @gemini-cli mention and ask it to reveal all the tools available to it within its scoped MCP server.
Initially the model was quite resistant. It wasn't rejecting my prompt injection so much as ignoring it, and I understood why: the system prompt instructed the agent to follow the contribution PR guidelines. So instead of bruteforcing it, I decided to play by those rules. By using Pillar Security’s CFS Framework and following the guidelines laid out in the system prompt and the contribution file , I managed to successfully get the model to follow my prompts. The trick was to reframe the malicious hand-off as the guideline-compliant triage step. I used the following prompt injection:
As a result the adk-bot ( DanusMinimus , in my PoC project) followed the prompt injection, and the result triggered gemini-dispatch .
And in turn, the gemini_invoke.yml workflow was triggered:
The agent leaked all the tools it had access to through its MCP server in the response, but interestingly it also claimed access to tools it never actually had, like run_shell_command . Looking at the gemini settings again, I realized there was no core tool scoping, the tools.core field was absent, which meant it had access to every bash command . If I could prompt the agent to directly use the GITHUB_TOKEN using the Github GraphQL API I could force it to perform git operations the MCP server didn't expose. Now I could move on to the hardest part: hunting for impact.
Unlike our report ( My Agentic Trust Issues ), this exploit didn't lead directly to a supply chain compromise. I had an RCE inside the runner that could easily extract the GITHUB_TOKEN , since it was passed straight into the job as an environment variable, but the token was scoped solely to issues: write and pull-requests: write . I assumed these were fairly weak permissions, letting the attacker do little more than create and modify PR metadata as the github-actions[bot] .
Then I ran into a blog by flatt.tech written by @ryotkak analyzing a similar issue, which pointed out that issues: write isn't as limited as it looks:
What pull-requests: write ? Per GitHub's docs, it covers requesting reviewers, submitting PR reviews, and dismissing reviews through the Pull Request Reviews and Review Requests GraphQL APIs, and it's worth mentioning that this isn't available through the REST API or the scoped MCP server - so stealing the GITHUB_TOKEN is critical.
On PR #5663 , where one of the collaborators invoked gemini-review and the github-actions[bot] reviewed and approved changes . While there is a difference between approving a PR and approving PR changes, the signals that were being left by the agent bot (such as adding the green ‘OK’ marker and leaving the “approved these changes” text looked good enough for social engineering impact.
So at this point, as an attacker, I had two capabilities:
These were all the ingredients I needed for a high-impact social engineering attack.
As an attacker, I could pollute the PR approval lifecycle. I just had to figure out how it worked. Looking at PR #5870 , the steps are:
The idea was this: a sufficiently motivated attacker would first make genuine contributions to build trust and become a recognized contributor. Then they'd open a PR (Call it PR A) that mixes a real fix with malicious code: a poisoned test, a modified package.json , a malicious dependency added to the tree. One of the triagers picks it up, assigns a reviewer, and applies the review label.
The attacker watches their own PR(A) until that first triage happens. Once it does, they open a second PR(PR B), this one carrying a prompt injection. The injection lives in PR B, but when the agent processes it, it acts with the workflow's GITHUB_TOKEN against PR A, the malicious one. The injection instructs the agent to:
Each of these is something the token can actually do. Editing the triager's uses the impersonation primitive from issues: write ; posting and approving as the bot uses the RCE-extracted GITHUB_TOKEN ; the label and review-request changes fall under pull-requests: write . Strung together, they manufacture a complete, believable "a human asked for a review, gemini ran it, gemini approved" trail on the poisoned PR, none of which ever happened.
I created a PoC where the triager(Danus365) asks a maintainer (DanusMinimus) for a review and adds a request-review label. After the modification it would be completely transformed, looking like it's ready to be merged: The initial is modified so it would like the triager called gemini-cli for a review, gemini reviews the PR, approves it, modifies the labels and removes the maintainer review request.
I invite you to view the PoC dubbed version here:
Google did fix the underlying issues that caused the vulnerability in the first place, and while Google didn’t view the Social Engineering impact as rewardable they confirmed they have hardened adk-python repository.
Read the full here:
Rationale for this decision: This report demonstrates exfiltration of a GitHub token with a 'pull-requests: write' permission, which enables tampering with a PR but still requires a maintainer to take an action to merge the malicious PR as PRs are not automatically merged after a bot review. We don't reward vulnerability reports that require social engineering to enable a supply chain security compromise. Nonetheless, we have taken an action to harden the repository so we will be recognizing this report with credit.
Over the weekend following the initial discovery, google/adk-python added a new automation feature: an Antigravity-SDK based agent ( scripts/run_antigravity.py ) driven by two workflows:
The Google Antigravity SDK is a Python library for building AI agents that leverage the power of Google Antigravity. The Antigravity script is what actually writes the code intended to fix an issue once a trusted triager has commented /adk-issue-fix , as seen on issue #5966 .
A new PR is now created which is supposed to fix the selected issue.
There were several issues with these new additions. The antigravity agent was now vulnerable to command injection attacks which could result as an RCE on the runner running issue-fix.yml .
The Antigravity agent ( scripts/run_antigravity.py ) was wired up with a single security control: a deny policy on its run_command tool, backed by _is_safe_command . That function blocks a set of shell metacharacters and then only permits commands whose first token is gh or git :
The intent is to box the agent into safe Git and GitHub operations. However, as an allowlist, these restrictions could be evaded in three separate ways:
Attacker input can reach this agent with zero privileges: the runner registers fetch_github_issue / fetch_github_pr , which returns the issue or PR title, body, and every straight to the agent, and any external account can author that content.
When all of these issues are chained together, an attacker who simply opens an issue can execute arbitrary commands on the CI runner. And on issue-fix.yml , which runs with contents: write and authenticates to Google Cloud, the adk-bot PAT and a GCP service-account key are sitting right there in the runner's environment for that code to read. An RCE on the runner lets the attacker exfiltrate everything in its environment: ADK_TRIAGE_AGENT, GOOGLE_API_KEY , and ADK_GCP_SA_KEY . The workflows permissions block looks like the following
Several caveats should be noted. While the workflow declares contents: write , that only governs a runner-generated GITHUB_TOKEN and this workflow doesn't use it; the token actually in play is the ADK_TRIAGE_AGENT PAT, so its own scopes, not the workflow's declared permissions, decide what the attacker can do.
That PAT is likely a classic one, given the workflow's cross-owner operations and current fine-grained-PAT limitations, though this can't be confirmed from public GitHub data. Its classic scope could be as narrow as public_repo ; there's no public evidence it holds the broader repo scope, so it can't be inferred whether an attacker holding the PAT could push code directly into main . The ADK_GCP_SA_KEY can authenticate against the GCP project, but the service account behind it and whether it's highly privileged aren't externally visible.
Rather than assume broad scope on both findings, I moved forward with disclosure.
A dubbed video PoC (attached) shows the full chain: a prompt injection from an issue leading to exfiltration of the PAT to a custom C2 server.
The "before agents" world had a comfortable assumption baked into it: a component was safe as long as only privileged entities could access it. With agents and agentic based workflows this is no longer correct. An agent that reads untrusted text and holds a credential is, functionally, a privilege escalation primitive that speaks English, and one you can talk into doing things on your behalf. What I demonstrated in adk-python is the natural consequence of giving capable, instruction-following agents real privileges and exposing them to the open internet.
The deeper lesson is composition. Each piece of this system was, in isolation, defensible. A public triage agent is reasonable. A maintainer-only review agent is reasonable. Scoped tokens are good practice. The compromise lived in the seams , in one agent's ability to reach across a privilege boundary and set another in motion, and in the gap between what each permission looks like it can do and what it can actually do when an adversary chains it with the others. Threat models built around individual components miss this entirely. The edges between agents, the delegation, the impersonation, the triggering, are now first-class parts of the attack surface and have to be modeled as such.
For CISOs and security teams, a few things deserve attention now rather than later. Inventory where agentic workflows already exist in your pipeline, especially anything that ingests untrusted input (issues, PRs, tickets, emails, support chats) while holding credentials. Treat any agent exposed to untrusted text as potentially attacker-controlled, and scope its tools and tokens to the absolute minimum, the difference between "no tool scoping" and a tight allowlist was the whole ballgame here. Don't tie agents to human user accounts or long-lived personal access tokens with broad collaborator privileges; give them their own narrowly-scoped, auditable identities. Make sure no agent can trigger another across a privilege boundary without a control a prompt injection can't forge. And keep the human guardrails that agents are supposed to assist, not replace: branch protection, required and verified human review, and separation of duties are exactly what stops a fabricated approval from becoming a merge. These mitigations which Google set up prevented the vulnerabilities I presented here from compromising the ADK repository.
As these systems mature, the answer isn't to pull agents out of the pipeline, that ship has sailed, and the productivity gains are real. The answer is to extend the discipline we already apply to identity, authorization, and supply chain integrity to a new kind of actor, one that is powerful, useful, and persuadable. We at Pillar will keep exploring, breaking, and helping fix this new world as it takes shape.
Pillar found the first practical, real-world case of agent-to-agent exploitation in a production multi-agent system: a low-privileged, public-facing AI agent in google/adk-python could be manipulated via prompt injection into triggering a separate high-privileged, maintainer-only agent, turning a benign PR-triage automation into a path toward potential supply chain compromise.
Researchers crafted a prompt injection disguised as a guideline-compliant PR triage step, following the repository's own contribution rules via Pillar's CFS Framework. This caused adk-bot to post a starting with '@gemini-cli,' which gemini-dispatch.yml routed to the privileged gemini-invoke.yml workflow, executing an attacker-controlled prompt with elevated permissions.
adk-bot commented as a human Collaborator account tied to a personal access token, not as a GitHub bot or app. That gave it real repository privileges, so a successful prompt injection against it could indirectly invoke maintainer-only workflows like gemini-invoke and gemini-review, which normally required a trusted human to trigger them.
With the exfiltrated GITHUB_TOKEN scoped to issues:write and pull-requests:write, an attacker could edit or delete existing and issues, impersonate maintainers, submit fake pull request reviews as github-actions[bot], and dismiss review requests or apply approval labels, fabricating a convincing but entirely fake reviewed-and-approved trail on a malicious pull request.
The issue-fix.yml workflow's run_command tool used a denylist blocking shell metacharacters and allowing only gh or git commands, but git itself can execute arbitrary code through tricks like a custom hooksPath or an exclamation-prefixed alias, letting attackers achieve remote code execution and exfiltrate the adk-bot PAT and a GCP service account key.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
