The Reviewable Unit Is the Pull Request, Not the Commit
A workflow refactor in Snowflake's .NET connector deleted the environment-variable indirection GitHub documents against Actions script injection. The pull request carries a commit attributed to Copilot Autofix — and nothing attributes the deletion to it.
Wiz published research on 17 August describing a script injection in a GitHub Actions
workflow in Snowflake's public .NET connector repository. An issue title was crafted to
break out of a shell string and call curl against an out-of-band domain; the runner
returned a base64-encoded Jira API token, a user email and a base URL. Wiz reported it on
23 June, the repository was patched the same day, and the credential was rotated on
24 June. The workflow fired on issues being opened, which any GitHub account can do, and a
guard clause comparing a field on github.event.pull_request against a bot login read null
on an issue event, so it was always true.
One line in that timeline is why this is worth your morning. The pull request that introduced the flaw, merged on 18 June, carries a commit attributed to Copilot Autofix.
The pattern, named
Machine-generated changes enter a repository inside a human's pull request, and the pull request is the unit that gets reviewed, approved and merged. That is where provenance stops. The branch lands recording that a suggestion was involved somewhere inside it — not which lines it produced, and not whether a reviewer read those lines differently because a model wrote them.
What the artifact says
Pull request 1218, titled SNOW-2069227 : Update jira workflows, changed two workflow files,
not one: .github/workflows/jira_close.yml and .github/workflows/jira_issue.yml. The
second is the substantial one, roughly 60 lines added against 90 removed.
Before the change, jira_issue.yml bound the issue title and body to environment
variables named ISSUE_TITLE and ISSUE_BODY and referred to them as shell variables.
After it, the script computed those values inline with command substitution, placing
${{ github.event.issue.title }} directly into the shell it was generating.
That is the shape GitHub's security hardening page tells you not to write. The page uses a pull request title as its worked example of untrusted input, and among the mitigations it lists is binding the expression to an intermediate environment variable so the value never participates in generating the script. The refactor removed exactly that indirection.
The current file on master has it back. jira_issue.yml today triggers on issues being
opened and on issue comments, binds ISSUE_TITLE and ISSUE_BODY from the event context,
and hands them to jq as arguments. The remediation was not an insight; it restored the
shape that had already been there.
What the artifact cannot say
The pull request holds four commits, one of them labelled as a Copilot suggestion. Nothing in the pull request view attributes the removal of the environment variables to that commit rather than to the three around it. The merge went through a second person on the Snowflake side.
So the honest description is not that an AI wrote a vulnerability. It is that a workflow refactor containing a model suggestion removed a documented mitigation, and the review record does not resolve which part came from where. Nothing in the pull request view asked anyone to approve a model output as such.
GitHub's documentation on Autofix is careful about this. Suggestions are never applied automatically and require developer review, and it lists what can go wrong: non-determinism, syntax errors, fixes placed at the wrong location, fixes that are syntactically valid but change program semantics, and fixes that fail to remediate the underlying alert. Every item is about the fix failing at the alert it was generated for. Deleting a hardening pattern in a neighbouring file, in a pull request about something else, is not on that list.
The second instance
A paper posted to arXiv in April by Rabbi, Turzo, Champa and Zibran went through more than 33,000 AI-generated pull requests from the AIDev dataset, restricted to repositories above 100 stars, and isolated 675 security-related ones across Codex, Copilot, Devin, Cursor and Claude Code. Of those, 52.4 percent were merged and 32.4 percent closed unmerged. Semgrep produced 853 findings across the set: inefficient regular expression complexity at 36.2 percent, OS command injection at 13.0 percent, path traversal at 10.3 percent.
The paper does not cover this incident — its pull requests were created before August 2025 — and the authors say plainly that Semgrep both misses vulnerabilities and reports false positives. Take it for one thing only: a slight majority of security-labelled AI pull requests in that set were merged rather than closed, and the authors report that some vulnerable ones passed review. The rate is not per-finding — Semgrep was run across all 675, merged and unmerged alike — so the number to carry away is the direction, not the decimal.
Why teams keep walking into it
Because the review surface was built for people, and it assumes intent. A colleague who deletes an environment-variable indirection normally had a reason, and a reviewer who asks gets one. A suggestion has no reason to give, and the reviewer supplies a charitable one by default. This is the same gap we keep pointing at in From PoC to Production: the model is not where the budget goes, the pipeline around it is — and CI is a production system that reads attacker-supplied strings and holds long-lived credentials. Treating AI output as an integration problem rather than a code-quality one is the whole argument in why enterprise AI doesn't ship.
What to do this quarter
Run this against every repository you own:
grep -rnE '\$\{\{ *github\.event\.(issue|pull_request|comment|review)' \
--include='*.yml' --include='*.yaml' .github/workflows
Every hit inside a run: block is a shell-injection sink. Every hit under env: is the
pattern you want. Hits in if: or in an action's with: are neither, and need reading —
but in most repositories those are the small remainder, which makes the first pass fast.
Two more, in order of cost: set
permissions: explicitly at the top of any workflow triggered by issues,
issue_comment or pull_request_target, and move third-party credentials such as a Jira
token out of workflows that untrusted input can reach at all.
The falsifiable claim: the binding constraint is provenance granularity, not model accuracy. If Autofix suggestions were attributed line by line in the diff and a branch protection rule could demand a separate approval for those lines, this class of incident would get rarer without the model improving at all. What would show me wrong is a version of this incident where the removed mitigation is traceable to the suggestion and a reviewer read it and approved it anyway. That is a training problem, and no amount of provenance fixes it.
Sources
- Wiz Research — Red Agent, Snowflake, and a Copilot CI/CD bug
- snowflakedb/snowflake-connector-net pull request 1218
- snowflakedb/snowflake-connector-net pull request 1218 — files changed
- snowflakedb/snowflake-connector-net pull request 1402
- jira_issue.yml on master
- Responsible use of Copilot Autofix for code scanning
- Security hardening for GitHub Actions
- Insights into Security-Related AI-Generated Pull Requests
Turn this into a plan for your team.
One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.