Magic Tools
Developer ToolsBy CooconAugust 18, 2026111 views3 min read

Copilot Said "All Clear." Five Days Later, It Was Exploited.

Copilot Said "All Clear." Five Days Later, It Was Exploited.

Someone broke into a public Snowflake repo recently, and it barely took any effort. The attacker opened a GitHub issue with a carefully crafted title and walked away with Snowflake's internal Jira credentials — read access to engineering, security compliance, and bug bounty projects.

On the squash commit that introduced the bug, the authorship line lists a peculiar co-author: "Copilot Autofix powered by AI."

A textbook shell injection, introduced as an "improvement"

Snowflake's snowflake-connector-net repo had a workflow called jira_issue.yml that syncs GitHub issues to Jira. The original version did it the safe way: it passed the issue title through an env: variable and built the JSON payload with jq. That's the standard pattern for avoiding injection.

On June 18, PR #1218 changed it to direct string interpolation — ${{ github.event.issue.title }} dropped straight into a shell script, wrapped in echo '...'.

Anyone who's written shell code can spot the problem instantly: put a single quote in the title and you break out of the echo string. From there, arbitrary command execution.

Worse, the workflow's if: guard looked protective. But on issue events, github.event.pull_request is always null, so the condition is always true. Every GitHub user gets through.

AI showed up twice in this pipeline, and failed twice

The interesting part isn't "another injection bug." It's the two places AI appeared in the process.

First, the merge. PR #1218's squash commit lists "Copilot Autofix powered by AI" as co-author. Copilot's name is literally on the change that introduced the bug.

Second, the review. Wiz updated its blog on August 17 with a clarification: Copilot was the co-author that checked the PR and the code change, and marked it all-clear without noticing the critical vulnerability. Whether the code itself was AI-written is now unclear.

Either way — AI-written or human-written — the AI "review gate" missed a textbook shell injection.

Meanwhile, an AI attacker found it in five days

Wiz's Red Agent is an autonomous AI security research tool. It scanned Snowflake's GitHub org, flagged jira_issue.yml, crafted an issue title, and exfiltrated the credentials of a Jira account (qa@snowflake.net) through an out-of-band callback.

One detail stands out. Red Agent's first attempt used # to comment out the trailing code. But # also swallowed the closing parenthesis of TITLE=$(...), triggering a bash syntax error. It didn't stop and wait for a human — it analyzed the error, switched the payload to ; echo ' to close the shell block properly, and got its callback seconds later.

Five days from bug (June 18) to discovery (June 23). To Snowflake's credit, the response was fast: they fixed the workflow and rotated the credentials the same day, and confirmed no third party got in. Still — an AI failed to catch an old-school injection, while another AI found, debugged, and exploited it autonomously. That asymmetry matters more than the bug itself.

Don't blame Copilot. The problem is closer to home.

Blaming Copilot is the lazy move. The core issue isn't "AI wrote bad code." It's "the team treated an AI review as a security guarantee."

Wiz put it well: AI coding tools predict code from probabilistic patterns, so they can quietly reintroduce deprecated or insecure patterns. They lack the historical context — the reason the original code used env: + jq instead of direct interpolation. That pattern exists precisely to block this kind of injection. The AI saw it as redundancy to optimize away.

Treat AI-generated diffs like human-written code. Static analysis, security scanning, manual review — none of it becomes optional just because "AI said it's fine."

Three things you can do today

  1. Audit your repos for workflows triggered by issues: opened. If any of them drop ${{ github.event.issue.title }} or the body straight into a shell script, switch to env: + jq.
  2. Set a hard rule for Copilot/Cursor autofixes: any diff that replaces structured parsing (jq, parameterized queries) with string concatenation gets a second manual look.
  3. Use short-lived tokens in CI, and never give a GitHub Actions runner credentials that can read internal systems.

Sources:

✨ Drafted by DeepSeek, reviewed and polished by Claude.

Related Articles

Dev Breakfast · 2026-10-02

Today's headline: Figma's MCP Only Accepts Whitelisted Clients, and the MCP's Creator Has Spoken Out. Plus 3 more: turbopuffer Demotes ANN to a Secondary Index: Are Vector Databases Really Dead?; From v0.1.9 to v0.1.13: TileLang Writes GPU Kernels in Python; and more.

daily-intelOct 2, 20267 min
31

Dev Breakfast · 2026-10-01

Today's headline: The Same Prompt: Why Some People's Output Looks Like a Designer Made It. Plus 4 more: Pi.dev: From "No MCP" to Shoving MCP into the Kernel; Gemini 4 Argon Is Priced at $2: Cybersecurity Teams Get It First; and more.

daily-intelOct 1, 20268 min
55

Dev Breakfast · 2026-09-30

Today's headline: Anthropic Prospectus: Revenue Increased 12 Times, Loss of 42 Billion. Plus 4 more: 0.8B Model Trained at Home: Choose One from 254 Options in 28 ms; 7 ESP32-S3 Chips Chained Together to Run a 0.5B 1.58-bit Model; and more.

daily-intelSep 30, 20268 min
100
Claude Code install errors, reproduced: EACCES, a 600s mirror stall, Node 20 silently getting an old version, a region-block install.sh, and the native installer removing your npm copy

Claude Code install errors, reproduced: EACCES, a 600s mirror stall, Node 20 silently getting an old version, a region-block install.sh, and the native installer removing your npm copy

I reproduced every Claude Code install failure I could on macOS: 15 verbatim errors, each with wall time and exit code. npm -g into /usr/local fails with EACCES, exit 243. A cache dir that is merely 0555 gets blamed on root-owned files, with sudo chown advice. From Beijing, npmmirror took 147s and then >600s, npmjs 11-12s (2 samples each). On Node 20, an unpinned install silently lands on 2.1.197. Fetching claude.ai/install.sh from a blocked region gives curl exit 0 and a 447 KB HTML page. The native installer runs npm uninstall -g on your npm copy without saying so; it removed mine.

claude-codetroubleshooting+5
pitfallsSep 29, 202611 min
118

Published by Magic Tools