Amazon Q — Three Ways Into the Same Product (2025–2026)¶
The Lesson: In fourteen months, Amazon's AI coding assistant was breached or nearly breached three separate times. Not one of those was a bug in its code. Every one came in through a boundary that looked like configuration — a CI trigger filter, a prose file, and a JSON file in a repo you cloned.
The one that got called in advance
We wrote in The LLM Threat Landscape that MCP server definitions launch processes, and that the trigger is cloning the repository and opening it.
That was written as a general warning about a class of file. Four months later Wiz published CVE-2026-12957: Amazon Q Developer read .amazonq/mcp.json out of any workspace you opened, with no consent prompt and no workspace-trust check, and spawned the processes it named with your full environment attached.
I take no pleasure in this. A prediction that specific means the failure mode was legible to anyone who looked, which raises a harder question than "were we right" — it asks why a shipping product with a million installs hadn't looked.
What Happened¶
On June 26, 2026, Wiz disclosed CVE-2026-12957 (CVSS 8.5) in Amazon Q Developer.1
Amazon Q Developer automatically loaded Model Context Protocol server definitions from .amazonq/mcp.json inside whatever workspace directory you had open. It did this without prompting for consent and without checking VS Code's workspace-trust state.
MCP servers are local processes. When Q spawned them, they inherited the victim's complete environment — AWS credentials, API keys, and the SSH agent socket.1
So the attack was: put a file in a repository. Wait.
That's the whole thing. No install step, no build, no execution of the project, no postinstall hook, no social engineering beyond "here is an interesting repository." A developer clones it, opens the folder, and the extension that's supposed to help them write code starts a process the repository chose, holding the credentials the developer forgot they had loaded.
Wiz demonstrated the reach: backdoored IAM users, persistence established in the cloud account, and pivots into internal production systems using inherited VPN context.1
Vulnerable: Amazon Q Developer for VS Code before 2.20, and Language Servers for AWS before 1.69.0.
Wiz's Maor Dokhanian found it on April 17, 2026 and reported it on April 20. Amazon shipped the initial patch on May 12. There is no public evidence it was exploited before the fix.
Three Ways In¶
That was the third time. Here is the set, because the set is the actual lesson:
| When | What | The way in | Outcome |
|---|---|---|---|
| Jul 2025 | Extension v1.84.0 shipped a wiper | Over-scoped GitHub token in a CodeBuild config | Shipped to users. Failed on a typo |
| Aug 2025 → disclosed Jan 2026 | CodeBreach | Two missing characters in a regex | Full admin on the AWS JS SDK. Caught by researchers |
| Apr 2026 → disclosed Jun 2026 | CVE-2026-12957 | A JSON file in a cloned repo | Clone to cloud compromise. Caught by researchers |
Three incidents, three entirely different mechanisms, one shared property: the vulnerable surface was configuration, and configuration is the part nobody reviews.
Act 1 — The payload was English¶
In July 2025, someone using the alias lkmanka58 got a commit merged into aws/aws-toolkit-vscode on July 13. The route in was an improperly scoped GitHub token sitting in a CodeBuild configuration.2
The payload landed in scripts/extensionNode.bk and shipped in version 1.84.0. It was a natural-language instruction addressed to the agent: wipe the user's home directory, then enumerate AWS profiles and delete cloud resources with the CLI.3
Sit with that for a second. The attacker had write access to the repository of a tool with roughly a million installs. They could have written anything. They could have written a JavaScript payload, an obfuscated blob, a downloader — the whole standard kit.
They wrote a sentence.
Why prose is the better weapon here, and this is the part that generalizes:
- The agent already holds the credentials and the permissions. You don't need to write an AWS API client and smuggle it past review — you ask the thing that already has one.
- A sentence in a
.bkfile defeats every scanner in the industry. There is no signature for a well-formed English request. SAST has nothing to say. A dependency scanner has nothing to say. A diff reviewer sees prose in a backup file and moves on. - It survives code review because it isn't code.
The malicious version was live on the marketplace for about two days and sat in the repository for ten. AWS pulled it, revoked the credentials, and released 1.85.0 on July 26.
And then this, from the advisory, which is the sentence the whole page hangs on:
"AWS Security has inspected the code and determined the malicious code was distributed with the extension but was unsuccessful in executing due to a syntax error. This prevented the malicious code from making changes to any services or customer environments."2
It ran on real machines. It did nothing, because the attacker mistyped.
Read the advisory's next instruction, not just its reassurance
AWS still told everyone to remove 1.84.0 rather than merely upgrade past it, because the malicious code remained present in existing installations. "It didn't execute" and "it isn't on your disk" are different claims, and only one of them was being made.
Act 2 — Two missing characters¶
The Q compromise sent Wiz researchers Yuval Avrahami and Nir Ohfeld to go look at CodeBuild generally. What they found was worse, and they named it CodeBreach.4
AWS repositories used CodeBuild webhook filters to decide who was allowed to trigger a build. The ACTOR_ID filter listed approved GitHub numeric account IDs, pipe-separated as alternatives.
It had no ^ and no $.
Without anchors, a regex engine doesn't look for a string that matches the pattern. It looks for one that contains it. So the filter didn't mean "the actor is this maintainer." It meant "the actor's ID has this maintainer's ID somewhere inside it."
GitHub assigns account IDs sequentially. Maintainer accounts from years ago carry six- and seven-digit IDs. New accounts get nine digits. Which means that as the counter climbs, it periodically passes through a nine-digit number that contains an old maintainer's ID as a substring — and whoever registers that account is, as far as the filter is concerned, that maintainer.
Wiz calculated the eclipse window arrives roughly every five days for any given maintainer ID. They used GitHub's App manifest flow to stand up 200 bot accounts and raced the registration when a target number came up.
It worked. They pulled a GitHub Classic personal access token for aws-sdk-js-automation — full admin over the AWS SDK for JavaScript, plus three private mirrors.4
Four repositories were exposed this way: aws/aws-sdk-js-v3, aws/aws-lc, the Amazon Corretto Crypto Provider, and the Registry of Open Data on AWS.
AWS anchored the filters and revoked the token within two days of the August 25, 2025 report. Public disclosure came January 15, 2026.
This one is yours too
The Q extension bug was Amazon's to fix and they fixed it. The unanchored-regex bug is a pattern, not a product defect, and it is sitting in CI configurations everywhere — CodeBuild webhook filters, branch protection patterns, allowed-actor lists, artifact path matchers, anything where a config file holds a regex that decides who or what is trusted.
An unanchored regex in an allow-list is a config typo that grants access. It reviews clean, tests green, and works correctly for years against every input except the crafted one.
Act 3 — The file you never opened¶
Which brings it back to CVE-2026-12957, and the reason the three belong on one page.
The July 2025 attack needed repository write access to a major AWS project. CodeBreach needed a five-day timing window and 200 bot accounts. Both are real, and both are work.
.amazonq/mcp.json needed a repository. Any repository. Yours, if someone opened a pull request against it and you checked out the branch to look.
The trend line across fourteen months runs in exactly the wrong direction: the cost of the attack fell every time, and the thing being attacked was configuration every time.
What ties them together¶
Three distinct mechanisms, and none of them touched the product's source code in the way a vulnerability normally does. What they have in common is a boundary that used to be inert and isn't anymore.
Configuration files earned their reputation for safety honestly. For thirty years a config file was a set of values that a program read and then decided what to do about. The program held the agency. The file held parameters. You reviewed the code and you skimmed the config, and that was a correct allocation of attention.
That deal is off:
.amazonq/mcp.jsondoesn't parameterize a process. It names one, and something spawns it.scripts/extensionNode.bkdidn't configure the agent. It instructed the agent, in a language that has no parser to audit and no linter to fail.- The CodeBuild
ACTOR_IDfilter wasn't describing policy. It was the policy, and two absent characters inverted its meaning without changing its appearance.
The common failure isn't AI, and it isn't AWS. It's that the review effort a file gets is still allocated by what the file looks like, and a class of file that used to be safe stopped being safe without changing how it looks.
See Configuration Management for the review discipline this implies, and The Detonator Pattern for the architecture that survives it — because the durable fix isn't reviewing harder, it's making sure that what reads untrusted input has nothing dangerous to do afterward.
The near-miss problem¶
Two of these three were found by Wiz. The third failed because the attacker made a typing mistake.
The number of these that were caught by a control someone deployed on purpose is zero.
That is worth naming plainly, because "no customers were impacted" is technically true of all three and it is the least informative true sentence available. A near miss tells you about the distribution of attacker competence. It tells you nothing about your defenses, because your defenses were not what produced the miss.
The syntax error is not a control
If the attacker in July 2025 had tested their payload once, the outcome is a wiper running inside a million developer environments with AWS credentials in scope. The delta between that and what happened is one keystroke, and it was theirs, not ours.
The industry logged this as a success. It was a coin flip that landed well, and coin flips regress. When you write the incident up, write down what your controls actually caught — and if the honest answer is nothing, write that down instead, because that sentence is the only version of this that improves anything.
Andrew's two most-hated showstoppers are config typos and off-by-one errors. CodeBreach was both at once: an off-by-two-characters error, in a config file, that silently converted an allow-list into a substring search. You do not get to feel superior to this bug. You get to go check yours.
What to Check¶
The Q-specific bits, which are just patching:
- Amazon Q Developer for VS Code at 2.20 or later; Language Servers for AWS at 1.69.0 or later
- Version 1.84.0 of the extension removed, not merely upgraded past — the file stays behind
The part that outlives the incident:
# 1. Which repositories carry files that execute on open?
# Run this over every repo you clone, not just the ones you publish.
find . -type d -name .git -prune -o \
\( -path '*/.amazonq/mcp.json' -o -path '*/.vscode/tasks.json' \
-o -path '*/.claude/settings*.json' -o -path '*/.devcontainer/*' \
-o -name '.mcp.json' \) -print
# 2. Every CodeBuild webhook filter that decides trust, checked for anchors.
# A pattern without ^ and $ is a substring match wearing an allow-list costume.
aws codebuild list-projects --query 'projects[]' --output text \
| xargs -r aws codebuild batch-get-projects --names \
| jq -r '.projects[] | . as $p | .webhook.filterGroups[]?[]?
| select(.type | test("ACTOR|HEAD_REF|BASE_REF"))
| select((.pattern | startswith("^")) and (.pattern | endswith("$")) | not)
| "\($p.name)\t\(.type)\t\(.pattern)"'
- Anchor every allow-list regex — CI trigger filters, branch protection, artifact paths, actor lists
- Scope CI tokens to the repository and permission they need; the July 2025 token had no business being able to commit
- Treat a new or modified agent/editor config file in a PR as a review-stopping event, the same way an added
postinstallscript is - Prefer workspace trust prompts you actually read over the ones you clicked through in 2023
Where It Sits¶
- The LLM Threat Landscape — the config-as-executable-content class this belongs to
- The Detonator Pattern — the architecture, including why forged tool calls make prompt-layer guardrails irrelevant
- Configuration Management — reviewing the files that stopped being inert
- Build Environment — who is allowed to start a build that holds credentials
- keyv / ChainDrop — the same editor-config hooks, planted across hundreds of npm packages
- Understanding Risk — why a near miss is not evidence of a control
Timeline¶
| Date | Event |
|---|---|
| Jul 13, 2025 | Malicious commit merged into aws-toolkit-vscode |
| Jul 17, 2025 | Extension v1.84.0 published with the wiper prompt |
| Jul 23, 2025 | Compromise reported to AWS |
| Jul 26, 2025 | v1.85.0 released; advisory GHSA-7g7f-ff96-5gcw published |
| Aug 25, 2025 | Wiz discloses CodeBreach to AWS |
| Aug 27, 2025 | AWS anchors the filters, revokes the aws-sdk-js-automation token |
| Jan 15, 2026 | CodeBreach disclosed publicly |
| Apr 17, 2026 | Wiz discovers the .amazonq/mcp.json auto-load |
| Apr 20, 2026 | Reported to Amazon |
| May 12, 2026 | Initial patch — VS Code extension 2.20, Language Servers 1.69.0 |
| Jun 26, 2026 | CVE-2026-12957 disclosed publicly |
What I actually take from this
A million developers ran a wiper and were saved by a stranger's typo. Researchers, not defenders, found the other two.
I don't think the lesson is that Amazon is careless — three findings in fourteen months against a product under this much scrutiny is what attention looks like, and most tools in your dependency tree are getting none. The lesson is that the file classes we learned to skim became the file classes that execute, and nobody sent a memo.
Go look at what's in .vscode/ in the last repository you cloned. You have never once looked, and neither had anyone else.
-
Wiz Research (Maor Dokhanian). "CVE-2026-12957: Amazon Q Developer MCP configuration auto-load." Disclosed June 26, 2026. ↩↩↩
-
aws/aws-toolkit-vscode. "Malicious script injected into Amazon Q Developer for Visual Studio Code (VS Code) Extension." GHSA-7g7f-ff96-5gcw; AWS security bulletin AWS-2025-015, July 26, 2025. ↩↩
-
The Register. "Compromised Amazon Q extension told AI to delete everything – and it shipped." July 24, 2025. ↩
-
Wiz Research (Yuval Avrahami, Nir Ohfeld). "CodeBreach: Supply Chain Vulnerability & AWS CodeBuild Misconfiguration." January 15, 2026. ↩↩