Running AI coding agents in GitHub Actions without leaking secrets
Why AI agents in CI leak secrets through PR titles and issue comments, and the workflow design that contains it: triggers, token scopes, and split jobs.
- Published
- Reading time
- 7 min read

Key takeaways
- An AI agent in CI that reads pull request titles, issue bodies, or comments is processing attacker-controlled input, so any secret in the same job should be assumed reachable.
- In 2026 researchers showed prompt injection through PR titles and issue comments exposing credentials from the Claude Code Security Review Action, the Gemini CLI Action, and GitHub Copilot Agent.
- Run agents that read untrusted content under the pull_request trigger with a read-only token, not under pull_request_target, which exposes secrets and a write token to fork pull requests.
- Split the work: an unprivileged job lets the agent produce output as an artifact, and a separate privileged job validates and publishes it without running the model.
- Never let anyone without write access trigger an agent, and never interpolate event text directly into a shell script or prompt template.
On this page
If an AI coding agent in your GitHub Actions workflow reads pull request titles, issue bodies, or comments, it is reading text an attacker wrote, and anything in that job's environment is within reach of a successful prompt injection. You cannot make the model reliably ignore those instructions. What you can do is design the workflow so the job the agent runs in holds nothing worth stealing and cannot write anywhere that matters.
This is no longer a theoretical concern. Two research disclosures, a year of CI supply chain incidents, and an industry research note all point to the same pattern. This article explains the pattern and gives a workflow design that contains it.
What actually went wrong in the published attacks?
In December 2025, Aikido Security published PromptPwnd, describing a class of vulnerabilities in CI pipelines that use AI agents. The pattern has two halves: untrusted text such as issue titles, PR bodies, or commit messages is inserted into the agent's prompt, and the agent runs with privileged tokens and tools that can run shell commands or edit issues. Aikido named Gemini CLI, Claude Code Actions, OpenAI Codex Actions, and GitHub AI Inference integrations, and reported that Google's own Gemini CLI repository was affected and patched within four days of disclosure.
In April 2026, security researcher Aonan Guan, with Johns Hopkins researchers Zhengyu Liu and Gavin Zhong, documented an attack they called "Comment and Control", reported by SecurityWeek. Against three agents:
- Claude Code Security Review Action: a crafted PR title led the agent to run commands and surface credentials in a security finding or the Actions log.
- Gemini CLI Action: issue comments carrying an injection bypassed guardrails and exposed a full API key.
- GitHub Copilot Agent: a payload hidden in an HTML comment bypassed environment filtering and the network firewall, after a maintainer assigned the issue to Copilot.
SecurityWeek reports that GitHub classified its case as a known architectural limitation, and that Anthropic's and Google's fixes changed the agent's allowed tools and its system prompt respectively. That is the honest answer: model-side mitigations reduce how often an injection works, but they do not change what the job can reach when one does.
Why is an agent in CI riskier than a normal workflow step?
Classic Actions injection is about a shell expression: interpolate github.event.pull_request.title into a run: script and an attacker can break out of the string. GitHub's secure use reference fixes that with an intermediate environment variable, because the value then "doesn't interact with the script generation process."
An agent removes that fix. Its whole job is to read the text and act on it, so there is no quoting that makes instructions inert. The Cloud Security Alliance's research note on AI-powered GitHub Actions makes the same point: the workflow's behavior is now decided at runtime by a language model rather than by fixed conditional logic. The same note lists the surrounding failure modes that turn an injection into an incident: over-privileged default tokens, floating action tags, and secrets encoded to evade log masking.
This is the CI version of a principle we argued in Prompt injection is an access-control problem: the model may propose actions, but something it cannot influence must decide what it is allowed to touch.
Which trigger should an agent workflow use?
The single most important choice is the event trigger. GitHub Security Lab's guide to preventing pwn requests spells out the difference:
| Trigger | Fork PRs get secrets? | Token | Use for agents? |
|---|---|---|---|
pull_request | No | Read-only for forks | Yes, for anything that reads PR content |
pull_request_target | Yes | Read/write | No, unless the agent never sees PR content |
pull_request_target runs in the context of the base repository, with secrets and a write token, even for pull requests from forks. Security Lab calls combining it with a checkout of the PR "a dangerous practice that may lead to repository compromise." An agent that needs an API key and permission to post a review is tempting to wire to this trigger, and that is exactly the combination of untrusted text, secrets, and write access the attacks exploited. Comment and issue triggers deserve the same caution: Aikido notes that some vulnerable workflows could be started by any external user who files an issue.
What does a contained agent workflow look like?
Split the work into two workflows, following the pattern Security Lab recommends for builds that need write access:
- An unprivileged job triggered by
pull_request. It checks out the code, runs the agent, and uploads the agent's output as an artifact. It has a read-only token and only the secrets the agent strictly needs, ideally a short-lived, low-limit API key. - A privileged job triggered by
workflow_run. It downloads the artifact, validates it as data, and posts the comment. It never runs the model and never executes anything from the artifact.
# .github/workflows/agent-review.yml (unprivileged)
name: agent-review
on: pull_request
permissions:
contents: read
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-commit-sha>
with:
persist-credentials: false
- name: Run review agent
env:
PR_TITLE: ${{ github.event.pull_request.title }}
MODEL_API_KEY: ${{ secrets.REVIEW_AGENT_KEY }}
run: ./scripts/run-review-agent.sh > review.json
- uses: actions/upload-artifact@<full-commit-sha>
with:
name: review
path: review.json
# .github/workflows/agent-review-publish.yml (privileged, no model)
name: agent-review-publish
on:
workflow_run:
workflows: [agent-review]
types: [completed]
permissions:
pull-requests: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@<full-commit-sha>
with:
name: review
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Validate and post
run: python3 scripts/post_review.py review.json
Note that on fork pull requests the pull_request job receives no repository secrets at all, so reviewing fork contributions needs an agent that works without a key, or a maintainer who moves the change onto a branch inside the repository first. That limitation is the point: the fork author should not be able to cause your key to be loaded next to their text.
What else belongs on the checklist?
Beyond the trigger and the split, these controls come straight from the sources above:
- Declare
permissionsin every workflow. GitHub recommends setting the defaultGITHUB_TOKENto read-only for contents and raising permissions per job only where needed. - Gate who can trigger the agent. Aikido flags settings that remove the write-access check, such as
allowed_non_write_users: "*"for Claude Code andallow-users: "*"for Codex. Restrict agent triggers to users with write access. - Never interpolate event text into scripts or prompt templates. Pass it through an environment variable or a file, and label it in the prompt as untrusted content. This does not stop injection, but it closes the shell-level bug.
- Restrict the agent's tools. Remove shell access, process listing, network egress, and the ability to write to issues or PRs from any job that reads untrusted content.
- Pin actions to a full commit SHA. GitHub notes that a tag "can be moved or deleted"; the CSA note lists tag hijacking among the incidents that defined the past year.
- Set
persist-credentials: falseon checkout, so the token is not left on disk for later steps or a compromised agent to read. - Scope and shorten API keys. Use a dedicated key per workflow with a low spending limit, and rotate it if a run ever behaves unexpectedly.
- Test it. Open a PR whose title asks the agent to print its environment, and confirm nothing sensitive comes back. Keep that PR as a regression test and re-run it whenever the agent, its prompt, or its tool list changes.
How does this relate to developer machines?
The same reasoning applies outside CI. The August 2025 Nx "s1ngularity" compromise, summarized in the CSA note, used malware that targeted AI coding tool credentials on developer machines. Coding agents on a laptop and in a runner share a failure mode: they act with the user's or the pipeline's authority while reading content someone else wrote. In both places the control is the same. Decide what the agent can reach before it reads anything, because after it reads attacker text you no longer decide what it does.



