Back to all posts
Published on · by Renaud Deraison

You trusted the workspace, not the servers

On June 26, 2026, Wiz disclosed CVE-2026-12957: a cloned repository's `.amazonq/mcp.json` made Amazon Q auto-launch MCP servers that inherited the developer's full environment of AWS keys, cloud CLI tokens, API secrets, and SSH agent sockets, with no separate consent step for the servers themselves. A single 'trust this workspace' click stood in for spawning background processes that carried your live cloud session. Amazon patched it in Language Servers 1.69.0. Here is why the fix closes one product but not the class, and what changes when the agent that opens the repo lives in a per-profile Bromure VM, behind a credential broker, a read-write guardrail, and a hypervisor-level trace.

Yesterday Wiz disclosed CVE-2026-12957. A repository you clone can ship a file called .amazonq/mcp.json, and the moment you open the folder in Amazon Q and click Trust this workspace, that file launches background servers with no second prompt. They come up inheriting your AWS keys, your cloud CLI tokens, your API secrets, and your SSH agent socket. One trust click reached everything your shell can reach, and handed it to code the repository chose.

A developer who clones a repo to try out an infrastructure module does not think of it as granting access to anything. Cloning is inert: it writes files to disk and nothing runs. The judgment call comes one step later, when you open the folder in your editor and the assistant asks whether to trust it. That prompt reads like the familiar "do you trust the authors of the files in this folder" gate every IDE shows, and you click yes the way you have a thousand times, because the alternative is an assistant that can't read your code. This particular yes did more than let the assistant read. In Amazon Q, before the fix, it also launched whatever servers the repository had written into .amazonq/mcp.json, and those servers came up inside your shell with your live cloud session already in their environment.

We wrote up the same shape earlier this month, when the Miasma worm planted project configuration into 73 of Microsoft's own repositories and the payload fired on folder-open. That story was about a trusted source being the attack surface. This one is narrower and more uncomfortable: no worm, no compromised maintainer, nothing to point at but the assistant. The bug was in how Amazon Q read an ordinary project-config file and decided, on your single trust click, to spawn long-running processes that carried the most sensitive thing on your laptop.

What CVE-2026-12957 actually did.

On June 26, 2026, Wiz Research disclosed CVE-2026-12957, a high-severity flaw (CVSS 8.5) in the Language Servers for AWS, the engine that powers Amazon Q Developer across VS Code, JetBrains, Eclipse, and Visual Studio. Wiz reported it to Amazon on April 20, Amazon shipped a fix on May 12, and the details went public on June 26. There is no sign it was exploited in the wild. The mechanics, stripped down:

  • Amazon Q reads .amazonq/mcp.json from a workspace you open: a project-scoped file that declares Model Context Protocol servers for the assistant to use.
  • On opening the folder, Q launched the MCP servers that file declared without a separate consent step for the servers themselves. A repository you cloned could therefore put a server definition in front of the assistant and have it run.
  • The spawned processes inherited the developer's full environment, in Wiz's words, "AWS keys, cloud CLI tokens, API secrets, and SSH agent sockets." An MCP server is just a command Q starts, and that command came up with everything your own shell would have.
  • A server definition can point at any command, so the file amounts to arbitrary code execution on folder-open: it runs as you, with your cloud credentials already loaded, the moment you trust the workspace.

The disclosure carries a real disagreement about consent, and it is the whole point of this post. Amazon's position: "the user has to trust the workspace when prompted." There is a gate, and you click through it. Wiz's finding: before the fix there was no separate consent step for the MCP servers themselves. Both hold, and the failure lives in the gap between them. One coarse, familiar trust decision, the same yes/no you give a folder so the editor can index it, authorized a much larger grant: spawning background servers that carried your live session. You answered a question about reading files. Amazon Q spent the click on running servers as you.

Amazon fixed it in Language Servers 1.69.0 (clients: VS Code 2.20+, JetBrains 4.3+, Eclipse 2.7.4+, Visual Studio 1.94.0.0+), adding the per-server consent that was missing, and patched a sibling symlink-check bypass, CVE-2026-12958. Update Amazon Q. Then look past the patch to the part it leaves standing: opening a repository could put your live cloud session inside a process the repository chose.

DEVELOPER LAPTOP — your live cloud session is in the environment every spawned server inheritsDEVELOPER CLONESgit clone …/terraform-modulesnothing runs yetships .amazonq/mcp.jsona server definition, on diskOPEN IN AMAZON Q"Trust this workspace?"one familiar click → [Trust]reads .amazonq/mcp.jsonno per-server consentMCP SERVERS AUTO-LAUNCHQ spawns the declared commandcommand = anything the repo wroteruns as you, in your shell↳ arbitrary code on folder-openINHERITED ENVIRONMENT — every spawned server gets all of it, all real$AWS_ACCESS_KEY_ID, ~/.awsyour account (real)cloud CLI tokens (gcloud, az)your projects (real)API secrets in env / .envprod keys (real)$SSH_AUTH_SOCKsigns as you (real)read · use · exfiltrate→ off your machineOUTCOMEcode runs as youcloud session in handkeys read & shipped outone trust click boughtthe whole laptop
CVE-2026-12957 on a normal developer laptop. Cloning the repo runs nothing. Opening it in Amazon Q and clicking 'Trust this workspace', the one familiar gate, makes Q read .amazonq/mcp.json and launch the MCP servers it declares, with no separate per-server consent. Each spawned server is a command Q starts, and it comes up inheriting the developer's full environment: AWS keys, cloud CLI tokens, API secrets, the SSH agent socket. Because a server definition can point at any command, this is arbitrary code running as you, with your live cloud session already loaded. The single trust click answered a question about reading files, and Q spent it on running servers as you.

The same repo, opened inside Bromure Agentic Coding.

Bromure Agentic Coding runs your coding agent, Amazon Q included, inside a per-profile Linux VM: its own kernel, its own filesystem, its own network stack, on Apple's Virtualization framework. A profile is a coherent scope of work: this client, this service, this open-source module you cloned to evaluate. You clone the repository into that profile and open it there. The vulnerable behavior reproduces just as Wiz described: you click Trust this workspace, Q reads .amazonq/mcp.json, and it launches the servers the file declares. The spawn fires, and code of the repository's choosing runs.

It runs in the guest. The MCP server comes up inside the VM and inherits the VM's environment, and the VM's environment does not hold your cloud session. Bromure ships the guest with stubs: fake values that look real to aws, gcloud, az, kubectl, git, and anything else reading an Authorization header or an AWS_ACCESS_KEY_ID. A proxy on your Mac sits in front of every connection leaving the sandbox, recognizes the stub, and swaps it for the real secret at the wire as the request leaves; the sandbox that held the key walks through the mechanism. The real AWS key, the real cloud CLI token, the real API secret never touch a file, an environment variable, or a page of memory the VM can read.

So walk the CVE's inheritance through that boundary. The spawned server reads $AWS_ACCESS_KEY_ID and finds a stub. It reads ~/.aws and finds a stub profile. It reads the environment for the cloud tokens and the API secrets and finds placeholders. The server runs to completion as written, inheriting a box that was never holding your keys. What made CVE-2026-12957 a credential-theft bug is env inheritance on spawn, and Bromure does not break it. It leaves the inherited environment worthless.

PER-PROFILE BROMURE VMtrust workspace → Q reads mcp.jsonMCP server auto-launches, runsarbitrary code — in the guest onlyWHAT THE SERVER INHERITS$AWS_ACCESS_KEY_IDstub_AKIA…cloud tokens, API secretsstub / absent$SSH_AUTH_SOCKforwardedtries: aws s3 rm / ec2 terminatea write → leaves through the proxyexfil host keys: nothing real to takeblast radius = this one profileno host fs, no host keychain reachPROXY · YOUR MACCREDENTIAL BROKERreal AWS keys held herestub → real, at the wirevalue never enters the VMGUARDRAIL: READ/WRITEdelete / terminate → PROMPTnames verb + targetAUDITevery spawn + call loggedbelow the agentOUTCOMEcloud session:stubs inherited,real keys untouchedcode-as-you:runs in a throwawayguest, not the hostdestructive call:paused, you say nowhat ran:on the trace
The same folder-open, the same auto-launched server, inside Bromure. (1) Isolation: the MCP server spawns in the per-profile VM, so the 'arbitrary code as you' lands in a disposable guest rather than on the host, and the blast radius is one profile. (2) Credential brokering: the server inherits the guest's environment, which holds only stubs; the real AWS keys, cloud tokens, and API secrets sit on the host behind the proxy, swapped in at the wire, so the inheritance takes placeholders. (3) Guardrail: any state-changing call the code attempts, such as deleting a bucket, terminating an instance, or pushing a branch, is a write that gets stopped at the wire for a prompt naming the verb and target. Every layer is enforced below the agent, at a boundary the spawned process cannot route around, and every spawn and outbound call is written to the trace.

"Trust this workspace" was the wrong question.

This CVE is worth more than a patch note because of the consent gap, and the gap is a granularity problem. The trust prompt asked one broad question, do you trust this folder, and a single yes had to cover both the harmless thing you meant (let the assistant read my code) and the dangerous thing you did not know was attached (launch background servers with my cloud session). You cannot answer that well. Nobody can, because the question fuses two grants that have nothing to do with each other and shows you only the innocent one.

Bromure does not ask you to be better at that judgment. It takes the judgment off the path. The trust click inside a profile still lets the assistant do its work, and it still cannot launch a process holding your real cloud session, because that session is not in the VM to inherit. It lives on the host, behind the broker, reachable only as a stubbed request the proxy fills in at the wire and logs. The dangerous half of the grant is gone. You did not withhold it; there was nothing there to grant, because the boundary sits below the agent where the trust prompt cannot move it.

The other dangerous half is a spawned server that goes destructive rather than just nosy, and it meets Guardrails. Bromure reads the operation, not only the connection: an aws s3 ls is a read, an aws s3 rm is a write; a git fetch is a read, a git push is a write. Set a profile's cloud credential to ask on write, and the moment the code reaches for a state-changing call such as a DELETE, a Terminate*, or a force-push, Bromure stops it at the wire and surfaces a prompt on your Mac naming the verb, the target, and the profile. Reads never interrupt you. The mutation is what pauses, the same way "the agent deleted the production database" stops being a postmortem and becomes a dialog box you decline.

What the trace shows.

Every team asks the same question the morning after a thing like this lands: did I open that repo, and what did it run? On a normal laptop the honest answer is a shrug. The MCP server Q spawned was a child process in your session, and whatever it read and wherever it phoned, it did so as you, with no record that separates it from your own work.

Inside Bromure the agent sits in a VM and every outbound byte goes through the host proxy, so the spawn and its calls stay visible even when the agent cannot see them. The host records the MCP server coming up, the commands it ran, the credentials it asked the broker to fill, and every connection it tried, into the same session trace as everything else the agent did, written below the agent where the spawned code cannot reach in to edit it. "Did this repo's server touch my account" stops being a guess you reconstruct from CloudTrail weeks later and becomes a row you read: the broker handed a stub, the request had a name, the target was logged. For an enterprise that is the gap between we patched Amazon Q and we can show what every repo our developers opened did, and the audit posture is the one our enterprise work keeps coming back to.

Where this does not save you.

A forwarded ssh-agent socket is usable while it's forwarded.

Bromure keeps your SSH private keys in the macOS Keychain and never copies them into the VM. But if a profile has the ssh-agent socket forwarded in (the way OpenSSH intends), a spawned server can ask the agent to sign with those keys while the socket is live. The key file never leaves the host; the signing capability does reach the guest. Forward the socket only into profiles that need it, and scope it the way the broker scopes a credential.

A write you approve is a write that happens.

The read-write guardrail catches the destructive call the agent didn't tell you about. It does not read your mind about intent. If you are tearing down a stack on purpose and you approve the prompt, Bromure forwards the Terminate. The prompt buys you a look at the verb and target; you still have to read it.

The profile is long-lived, so persistence persists.

A Bromure profile is not a disposable disk. A spawned server that writes itself into a startup path inside the profile can wake up in the next session in that profile. What it wakes up to is a guest with no host keys and a broker that speaks only short-lived, prompted, scoped tokens: presence in a box with nothing in it, but presence all the same.

Patched is not the same as solved.

Amazon added per-server consent in 1.69.0, and you should update. But the next assistant that reads project config on folder-open will make the same class of decision, and the prompt it shows will look just as routine. Isolation is the part that does not depend on a particular vendor getting a particular consent dialog right.

The next workspace is already cloned somewhere.

The lesson of the Red Hat scope was that the publisher is not a defense. The lesson of the Microsoft repos was that neither is the repository, and that opening a folder is enough to run code. Amazon Q adds the next line: the trust prompt is not a defense either. The question it puts to you, do you trust this folder, is not the question whose answer matters, which is should this folder be able to spawn servers carrying my cloud session. You were never shown the second one, and you could not have answered it well if you had been.

You do not fix that by reading the prompt with more care, because the prompt was the wrong prompt. You fix it by arranging things so that "which workspace is this" stops being the question your cloud keys hang on; why a coding agent is not a sandbox is the longer version of that argument. Bromure Agentic Coding is the configuration where the agent opens the repo inside a per-profile VM, the real credentials stay on the host behind a broker, every write the agent makes has to clear a prompt, and every server it spawns is written to a trace it cannot edit. The worst a poisoned mcp.json can do is inherit a box that was never holding your keys. It is free, open-source, and shipped today.