Executive summary
AWS published four security bulletins yesterday. Two are in its own MCP servers, one is an incomplete fix for a bulletin from earlier this year, and one is in the EFS CSI driver. Three of the four are the same bug wearing different clothes.
The clearest is CVE-2026-85787 in awslabs postgres-mcp-server, versions below 1.1.7. The server offers a read-only mode. Read-only was enforced by an incomplete list of disallowed inputs in the SQL validation component, and the consequence is exactly what that phrase implies: an unauthenticated actor could modify data beyond the read-only scope by placing crafted SQL when interacting with the authenticated MCP server.
The interesting part is not the missing keyword. It is where the guarantee was implemented. A database already has a permission system that can express read-only precisely, and the MCP server reimplemented one in application code by enumerating things to reject. AWS's own remediation says so: alongside the upgrade, run the MCP server with minimal-privilege database credentials, granting only necessary permissions like CONNECT, USAGE, and SELECT. With that in place the validator can still be wrong and nothing happens.
What changed
| Bulletin | Component | Affected | Fixed in |
|---|---|---|---|
| CVE-2026-85787 | awslabs postgres-mcp-server |
below 1.1.7 | 1.1.7 |
| CVE-2026-85654 | awslabs.dynamodb-mcp-server, CDK generator |
2.0.10 through 2.1.5 | 2.1.6 |
| CVE-2026-85786 | ion-java |
an incomplete fix for CVE-2026-75936 | see the bulletin |
| CVE-2026-85781 | Amazon EFS CSI Driver | unverified access point ownership | see the bulletin |
Architecture
Why a SQL denylist cannot work
Deciding whether an arbitrary SQL string mutates data is not a string-matching problem. It is a parsing problem, and a hostile one: comments, string literals, nested statements, common table expressions that write, functions with side effects, procedural blocks, and dialect-specific syntax all give a writer of crafted input more surface than a list of rejected tokens can cover.
That is why the phrase in the bulletin — an incomplete list of disallowed inputs — is a description of the design rather than of the defect. Any list of disallowed inputs is incomplete; the only question is whether anybody has found the gap yet. The fix in 1.1.7 closes the gap that was found.
The same shape, twice more, on the same day
CVE-2026-85654 in awslabs.dynamodb-mcp-server is improper neutralization of special elements used in a template engine in the CDK generator component. The server reads a data model file and generates CDK code from it; a threat actor could inject malicious code through crafted table, index, or attribute names in data model files, and the result is that an authorized individual could potentially execute arbitrary code on the host that deploys the generated application.
Same failure, different substrate. A table name is data, it was interpolated into a template as though it were code, and the escaping did not cover everything it needed to. Note where the code executes: not on the MCP server, but on the host that deploys the generated application — a CI runner with deployment credentials, which is a considerably better prize than a developer laptop.
And CVE-2026-85786 makes the pattern explicit rather than inferred: it is an incomplete fix for CVE-2026-75936, a memory-amplification denial of service in ion-java. The first attempt at enumerating the cases to handle was also not complete.
The unauthenticated detail is worth reading twice
The postgres bulletin says an unauthenticated actor could modify data when interacting with the authenticated MCP server. Those two clauses sit together deliberately. The MCP server holds the database credential; whoever can send it a request inherits whatever that credential can do, without needing any database authentication of their own.
That is the general property of every MCP server, not a defect of this one, and it is the thing to hold on to when deciding where such a server may run and who may reach it. The server is a credential-holding proxy. Its read-only claim was the only thing distinguishing "an agent can query our database" from "an agent can change our database".
Business value
Nothing to gain here, only two upgrades and a design question worth asking once.
The question: for every tool your agents can reach, where is the boundary actually enforced? If the answer is "the tool checks", the tool is one bug away from not checking. If the answer is "the credential cannot do it", the tool being wrong is survivable. That distinction costs nothing to apply at setup and is expensive to retrofit, because narrowing a database role after the fact means finding out what the application actually needed.
Security considerations
Three things follow from these two bulletins, in descending order of how much they generalise.
-
Give every MCP server its own credential, scoped to what it is allowed to do. AWS's own advice for the postgres server is the template:
CONNECT,USAGE,SELECT, and explicitly avoiding superuser or master user roles. A read-only tool holding a write-capable credential is a read-only tool by convention. -
Treat generated code as untrusted input all the way to deployment. The DynamoDB flaw turns a table name into code on the deploying host. AWS's interim guidance — manually reviewing the contents of
dynamodb_data_model.jsonfor unexpected or unintended parameters, and refraining from processing data model files from unverified sources — is really a statement that the generator's output inherits the trust level of its input. - Put MCP servers in the dependency-scanning process. These are pypi and npm packages that execute privileged actions. They are not in most teams' SBOM scope because nobody thinks of a developer tool as production software, and the CI runner it runs on says otherwise.
Cost considerations
Both fixes are version bumps and cost nothing. The change that has a cost is the defence-in-depth one: creating a dedicated, minimally-privileged database role per MCP server rather than reusing an application credential.
That cost is discovery time, not money. Working out the minimum grant means finding out which schemas, tables and functions the tool genuinely touches, which nobody has written down. Budget an afternoon per server and do it once — the alternative is discovering the answer during the next bulletin.
Operational considerations
The version pinning question is the practical one. MCP servers are typically launched by a client configuration that names a package and lets the resolver pick a version, so "which version are we running" is answered per developer machine and per CI job rather than centrally.
Pin them. An unpinned MCP server means an upgrade you cannot verify happened and a downgrade nobody would notice. And because the DynamoDB flaw executes on the deploying host, the CI runner's version matters more than any laptop's.
Second, note that the affected DynamoDB range is bounded at both ends — 2.0.10 through 2.1.5. Anyone on 2.0.9 or earlier is unaffected by this one, which is worth checking before scheduling emergency work.
Tradeoffs
| Where read-only is enforced | Survives a validator bug? | Cost |
|---|---|---|
| A denylist in the tool | No — this is the bulletin | None, which is why it is the default |
| An allowlist of permitted statements | Better, but still parsing hostile input | Breaks legitimate queries you did not anticipate |
| A database role with only SELECT | Yes — the write fails at the database | Discovery time to establish the minimum grant |
| A read replica as the target | Yes, structurally | An instance, and slightly stale data |
Implementation guidance
Upgrade first, then remove the reason it mattered.
pip install --upgrade "awslabs.postgres-mcp-server>=1.1.7"
pip install --upgrade "awslabs.dynamodb-mcp-server>=2.1.6"
# "Everywhere" includes CI runners and container images, not just laptops.
# The DynamoDB flaw executes on the host that deploys the generated app.
Then give the postgres server a credential that cannot do the thing the validator was supposed to prevent.
CREATE ROLE mcp_readonly LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE appdb TO mcp_readonly;
GRANT USAGE ON SCHEMA public TO mcp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;
-- And for tables created later:
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO mcp_readonly;
-- Not the application user. Not the master user. Not a superuser.
Test it by asking the agent to do something destructive and confirming the database refuses. That is a five-minute check and it is the only evidence that the guarantee is where you think it is.
Best practices
- Enforce a boundary where the platform enforces it, not where your code checks it. A database role, an IAM policy and a network rule are all things that fail closed without your help.
- Treat a denylist as a usability feature, not a control. Rejecting obvious mistakes early is useful; being the only thing between a query and the data is not a job it can do.
- Give every agent tool its own identity. Shared credentials make "what can this tool do" unanswerable, and these bulletins are entirely about that question.
- Pin MCP server versions and scan them. They are dependencies that execute privileged actions, and an unpinned one cannot be verified as patched.
- Assume generated code carries the trust of its inputs. A template engine turns data into code by design; that is the feature and the vulnerability.
Who should adopt this
Anyone running awslabs postgres-mcp-server below 1.1.7 or awslabs.dynamodb-mcp-server between 2.0.10 and 2.1.5 should upgrade today, and check CI images as well as workstations.
The wider audience is anyone whose agents can reach a database, a filesystem or a deployment pipeline through any MCP server, first-party or not. The specific bugs are AWS's; the pattern is not, and the question — where is this boundary actually enforced — applies to every tool in the catalogue.
If you run no MCP servers, the EFS CSI Driver bulletin is the one that may still apply to you.
Key takeaways
- Upgrade
postgres-mcp-serverto 1.1.7 anddynamodb-mcp-serverto 2.1.6. The DynamoDB range is bounded — 2.0.10 through 2.1.5. - The postgres server's read-only mode was a list of disallowed SQL inputs, and the list was incomplete. Every such list is; only the discovery date varies.
- AWS's own remediation moves the guarantee to the database —
CONNECT,USAGE,SELECT, and no superuser or master user role. - An MCP server is a credential-holding proxy. Whoever can reach it inherits what its credential can do, without authenticating to the backend themselves.
- Three of the four bulletins share one shape, including one that is explicitly an incomplete fix for an earlier incomplete list.
Official AWS references
- AWS Security Bulletin — CVE-2026-85787, incomplete list of disallowed inputs in postgres-mcp-server
- AWS Security Bulletin — CVE-2026-85654, code injection in the dynamodb-mcp-server CDK generator
- AWS Security Bulletin — CVE-2026-85786, incomplete fix for CVE-2026-75936 in ion-java
- AWS Security Bulletin — CVE-2026-85781, unverified access point ownership in the Amazon EFS CSI Driver
- AWS Security Bulletins
Comments