On June 18, a pull request tidied up a GitHub Actions workflow in one of Snowflake's public repositories. It deleted a few lines that had been passing a user-supplied issue title into a shell script through an environment variable, and replaced them with something shorter that dropped the title straight into the command line. The commit carried a co-author credit: "Copilot Autofix powered by AI".
Five days later a different machine found it. An AI agent broke out of that command line and walked off with a credential to Snowflake's internal Jira.
The finder was Wiz Red Agent, an autonomous security research tool that Wiz Research points at public code. Gal Nagli of Wiz published the account of the exercise on August 17, under Snowflake's HackerOne vulnerability disclosure program. Snowflake fixed the workflow the same day it was told, rotated the leaked credential, and pulled its audit logs to confirm that Wiz was the only party to touch the exposure during the five days it was open. Wiz says every piece of data it retrieved during testing was deleted afterwards.
The safe pattern that got removed
The vulnerable file was .github/workflows/jira_issue.yml in snowflakedb/snowflake-connector-net, the .NET connector for Snowflake. The workflow fired on the issues: opened event, which means any GitHub account in the world could start it simply by filing an issue.
What made that dangerous was the way the workflow handled the issue title. The version that had been there before built its JSON payload with jq, reading the title from an env: variable, so the text was always handled as data. The June 18 commit swapped that for a direct template expansion inside a shell command, roughly TITLE=$(echo '${{ github.event.issue.title }}' | sed ...), with a pair of sed calls doing the quoting.
Those sed calls look like escaping, and that is presumably why the line survived review. They do not help. GitHub expands ${{ }} into the script text before the shell ever runs, so by the time sed exists as a process the attacker's words are already part of the command. A single quote in an issue title closes the echo string, and whatever follows it is a fresh shell command running on the runner.
Snowflake's own repository record confirms where that change came from. Pull request #1218, titled "SNOW-2069227 : Update jira workflows", was opened by a Snowflake engineer and merged on June 18, 2026, and GitHub lists "Copilot Autofix powered by AI" as a co-author on the commit inside it. The autofix did not introduce a typo. It replaced a structured parser with string concatenation, which is the exact substitution that turns untrusted input into executable code.
A gate that was never closed
The workflow did carry something that looked like a guard. Its condition read, in part, github.event_name == 'issues' and github.event.pull_request.user.login != 'whitesource-for-github-com[bot]'. Read quickly, that seems to restrict who can set the job running.
Read carefully, it restricts nobody. On an issues event there is no pull request attached, so github.event.pull_request is null, and comparing null against a bot's username is true for everybody. The gate passed every GitHub user on earth. It is the kind of defect that survives a hundred readings by people who already believe the line is a filter.
The agent debugged its own payload
Red Agent's CI/CD capability flagged the file while scanning Snowflake's GitHub organization, then tried to prove the finding by exploiting it. Its first payload used a hash character to comment out the rest of the line, which is the standard move. It failed. The comment also swallowed the closing parenthesis of the TITLE assignment, and the runner returned a bash syntax error.
That is usually where an automated scanner stops and files a "potential" finding for a human to confirm. Instead the agent read the error, worked out that the shell block had to be closed rather than commented, rewrote the payload to close it with a semicolon and a fresh echo, and ran it again. The second attempt worked.
The payload base64-encoded three environment variables, the Jira API token, the Jira user email and the Jira base URL, and carried them out in a callback to an out-of-band listener at hxxps://subdomain[.]oast[.]me. Within seconds the callback arrived from a GitHub Actions runner at 20[.]106[.]182[.]197, an Azure address inside GitHub's hosted runner fleet.
What one Jira token opened
The credential authenticated as qa[at]snowflake[.]net against Snowflake's Atlassian tenant. Wiz reports that it granted read access across the company's engineering, security compliance and bug bounty tracking projects.
That last one deserves a moment. A bug bounty tracker is, by definition, a running list of security problems that have been reported and not yet fixed. Read access to it is read access to that list.
Nothing indicates anybody other than Wiz got there. Snowflake's audit review matched every anomalous query in the five-day window to Wiz's own testing addresses, the token was revoked and rotated on June 24, and the company says its investigation found no evidence of unauthorized access.
What this proves, and what it does not
This is one workflow, in one repository, found under a disclosure program, by a tool built to find exactly this, and written up by the company that builds the tool. It does not establish that AI-authored code is more vulnerable than human-authored code, and the measurement that would settle that question has not been published. What it does establish is narrower and still uncomfortable. An automated fix removed a deliberate security control, the removal survived human review and got merged, and the resulting hole was live for five days before another machine found it and proved it exploitable with nobody in the loop.
Put the title back in an env variable
Snowflake's fix, commit 1dc7766 in pull request #1402, restored the original pattern: the issue title goes into an env: variable, and jq --arg builds the JSON around it. That is the general remedy for this entire class of bug and it costs nothing. Do not interpolate a ${{ github.event.* }} expression directly into a run: block. Bind it to an env: variable and let the program that consumes it deal with quoting.
Two other things are worth an hour this week if you run GitHub Actions. Look at every workflow triggered by issues, issue_comment, pull_request_target or workflow_run, because those are the events that attacker-controlled text can reach. Then read your conditions against the event payload they actually receive rather than the one you had in mind, because a field that is null on the triggering event makes any comparison against it pass.
The broader control is process rather than syntax. Wiz's recommendation is that AI-generated pull requests face the same static analysis and security scrutiny as human ones, and that teams add guardrails stopping automated agents from replacing structured data parsers with direct string interpolation. Both are cheap. Neither is the default in most repositories today.
We have written before about what happens when attackers aim hostile prompts at Copilot, and the industry has spent two years building that threat model. This case runs the other way. No attacker touched the AI at all. It was asked to make code safer, and it made it exploitable, inside a repository belonging to a company that has already lived through a credential-driven campaign that reached 165 of its customers. The failure is not that the model can be tricked. It is that the model did not know why the line it deleted had been written.
This briefing is provided by IntelFusions for informational and defensive purposes only. It is based on sources assessed to be reliable at the time of writing, and analytic judgments carry the confidence levels indicated. Indicators of compromise are defanged; re-arm them only in controlled environments. IntelFusions is not affiliated with the organizations named and makes no warranty as to completeness or accuracy.