Nobody Can Tell You Who Wrote the Bug

On June 18, 2026, a pull request titled "Update jira workflows" merged into Snowflake's .NET connector repository. Five days later, an autonomous agent had a Jira API token with read access to their engineering, security compliance, and bug bounty projects.
The interesting question is not how the agent got in. It is who wrote the line that let it.
Nobody can tell you.
We built a way to ship code faster than we can attribute it.
What actually broke
The repo had a workflow that opened a Jira ticket whenever someone filed a GitHub issue. It used to do that safely: pass the issue title through an env: variable, then build the JSON payload with jq --arg. That pattern exists precisely because issue titles are attacker-controlled and jq --arg refuses to treat them as anything but data.
The PR removed it. In its place, the title got interpolated straight into a shell command, with sed escaping applied afterward.
Afterward is the whole bug. GitHub expands ${{ github.event.issue.title }} into the script before the shell ever runs, so the sanitizing happens to a string that has already become code. File an issue whose title contains a single quote and you break out of the echo. Everything after it is Bash, running on Snowflake's runner.
That is a textbook GitHub Actions injection, and it is the kind of change that looks completely unremarkable in a diff. A workflow file got tidied. The env: block went away. Reviewers see churn in YAML, not a removed control.
The dangerous refactor isn't the one that adds something, it's the one that quietly removes the reason something was there.
The part everyone got wrong
Most coverage ran with a clean story: GitHub Copilot Autofix wrote the vulnerability, and then another AI found it. Machine breaks it, machine breaks in. It is a great headline.
It is also not what the evidence supports, and Wiz, who found the bug, say so plainly. Copilot Autofix's documented contribution in that PR was a separate fix to a different file, jira_close.yml. The unsafe change landed in jira_issue.yml. In Wiz's own words: it is unclear whether the code change was AI-assisted.
Both changes were folded into one squash merge. That commit lists Copilot Autofix among its co-authors. So the honest answer to "who introduced this vulnerability" is that the history no longer distinguishes.
Sit with that, because it is worse than the headline.
If an AI wrote the bug, that is a tooling problem with a tooling fix: better generation, better linting, review gates on generated code. Solvable, and people are already solving it.
But this is a provenance problem. The commit that contains the vulnerability has an AI in its author list and a human in its author list, and the record cannot tell you which one touched the line that mattered. You cannot gate on generated code if you cannot identify generated code.
You can't review what you can't attribute.
We wrote in June that once discovery becomes a commodity, trust becomes the bottleneck. This is the sharpest version of that we have seen. The bottleneck is not "is this finding real." It is "can I establish where this change came from," and a squash merge answered no.
The other half: the agent that debugged itself
The discovery side deserves its own look, because the detail that matters is not that Wiz's Red Agent found the bug. It is what happened when the exploit failed.
Its first payload used # to comment out the rest of the line. That broke: the # swallowed the closing parenthesis of TITLE=$(...), and Bash threw a syntax error.
The agent read its own error, worked out what it had eaten, rewrote the payload to use ; echo ' instead, and got a callback from the runner on the second attempt. No human corrected it.
That is the capability shift, and it is easy to miss under the louder framing. Finding a bug is pattern matching, and we have known for a while that machines are good at it. Failing, diagnosing the failure, and adapting the exploit is something else. It is the part of offensive work that used to be the reason you hired a person.
Finding the flaw was never the hard part, not giving up was.
The credential it walked away with was a Jira token for qa@snowflake.net, with read access across engineering, security compliance, and bug bounty tracking. Read access to a bug bounty project is read access to every unfixed vulnerability someone has reported to you.
Five days from merge to exfiltration.
What this actually changes
Three things worth doing, and none of them are "stop using AI to write code."
Stop squashing merges that contain generated changes. Squash merge is a hygiene habit from a world where every commit had one human author. It now destroys the only record of which changes a model produced. If you keep one thing from this post, keep the commit history that lets you answer the question later.
Review removals harder than additions. Every scanner you own is tuned to notice new dangerous code. The Snowflake change was dangerous because of what it took out. An env: block disappearing from a workflow should be a bigger deal in review than fifty lines appearing.
We taught our tools to flag what code adds and never taught them to miss what it takes away.
Treat CI credentials as production credentials. The token here was a QA account for filing tickets, which is exactly why nobody scoped it tightly. It read the bug bounty queue. We have said before that the agent does the work and the human owns the outcome; that ownership has to include what the pipeline can reach when someone talks it into misbehaving.
The uncomfortable thing about this incident is how ordinary it is. No zero-day, no novel technique, no nation-state. A tidy-up PR, a squash merge, an unscoped token, and five days. Then something that never gets tired read the diff before anyone else did.
Related Posts
You Trusted the Folder. The File Changed.
Five CVEs across four vendors in thirteen months, all the same bug: your coding agent trusts a directory, then executes a file that can change underneath it. Here is the pattern, and the one line in Claude Code's restricted mode that actually fixes it.
Invisible From the Layer Above
Seven defects surfaced in one migration. None of them crashed, none failed a test, and none turned a dashboard red. A broken control and a working control send the same signal: silence.
The Refusal Was the Exploit Path
An agent refused to run the attacker's binary, then wrote its own decoder and ran the payload anyway. Three incidents and one of our own show the same thing: a constraint you hand an agent is a preference it can route around.