Amazon disclosed a critical command execution flaw in its Postgres MCP server on September 9, 2026 — patched by version 1.1.7, the same release its September 4 bulletin credited with fixing a lesser issue, and shipped to PyPI 76 days before either bulletin appeared.
What happened
On Wednesday, September 9, 2026, Amazon published security bulletin 2026-104-AWS at 12:30 p.m. PDT, covering CVE-2026-87911 in awslabs.postgres-mcp-server — the Model Context Protocol server that connects a large language model to a PostgreSQL database. The CVE record reached NVD at 3:21 p.m. Central the same day, its published and lastModified timestamps identical, meaning it has not been revised since.
The record describes “an OS command injection weakness in the read-only enforcement of the SQL validation component in Amazon awslabs postgres-mcp-server before 1.1.7” that “might allow an unauthenticated actor to execute operating system commands on the host of a self-managed PostgreSQL server by placing a crafted COPY … TO PROGRAM statement into content that is processed when an authenticated user interacts with the MCP server in its default read-only mode.”
Amazon, acting as its own CNA, scored it twice: 9.6 critical on CVSS v3.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H) and 9.0 critical on CVSS v4.0. Two CWEs are assigned — CWE-78, OS command injection, and CWE-184, incomplete list of disallowed inputs. GHSA-fph8-pg5w-78fv credits Mordehai Attia of Corsen AI and Ryan von Brubeck.
The bulletin and the advisory narrow the affected configuration further than the CVE description does, scoping it to deployments “using self-managed PostgreSQL deployment profile (PG_WIRE_PROTOCOL connection method) where the configured database role holds superuser or pg_execute_server_program privileges.” COPY ... TO PROGRAM is PostgreSQL’s own facility for piping table data to a shell command on the database host; without one of those privileges the database refuses it regardless of what the MCP server allows through.
The fixed version is 1.1.7. That number should be familiar. Severity Daily covered this package on September 4, when bulletin 2026-101-AWS disclosed CVE-2026-85787 — “an incomplete list of disallowed inputs in the SQL validation component” allowing an actor “to modify data beyond the read-only scope.” AWS scored it 7.1 on CVSS v4.0, 6.5 on v3.1. Its fixed version is also 1.1.7.
One release, two CVEs, five days apart, the second three points higher.
PyPI dates the 1.1.7 upload to June 25, 2026. Bulletin 2026-101 appeared 71 days later, bulletin 2026-104 at 76 days. In between the project shipped 1.1.8, 1.1.9, 1.1.10, 1.1.11, 1.2.0, and — on September 8, the day before this bulletin — 1.2.1, which is current.
Diffing the 1.1.6 and 1.1.7 wheels downloaded from PyPI shows the release changed exactly two files: a version string in __init__.py, and mutable_sql_detector.py. Both fixes live in that one file. The command execution fix is a single new regular expression, COPY_PROGRAM_PATTERN, checked before anything else and independent of the server’s read/write mode. The comment AWS shipped above it reads:
“COPY … TO PROGRAM / COPY … FROM PROGRAM executes an arbitrary shell command on the database host (remote command execution). It requires the COPY privilege plus pg_execute_server_program (or superuser), but where those are held it is a direct RCE primitive — COPY t TO PROGRAM ‘curl …’ or COPY t FROM PROGRAM ‘whoami’. COPY is already a mutating keyword (rejected in read-only mode), but PROGRAM turns it into RCE that must be blocked even when writes are enabled, so it gets a mode-independent check here.”
That comment and the advisory do not agree. The advisory says the server can be made to run a shell command “in its default read-only mode.” The patch comment says read-only mode already rejected the statement, and that the new check exists to cover the case where writes are enabled.
The shipped code supports the comment. In 1.1.6, COPY is already a member of MUTATING_KEYWORDS, and run_query calls detect_mutating_keywords on every statement when the connection is read-only — which is the default, set by readonly_query = not args.allow_write_query. Importing 1.1.6’s detector and calling it directly on COPY t TO PROGRAM 'whoami' returns ['COPY'], the match that causes the rejection — as do the lowercase spelling, a leading or embedded comment, the parenthesized COPY (SELECT 1) TO PROGRAM form, and the stacked variant. We did not find a spelling that evaded it.
The server’s second layer does not close the gap either. Both connection classes wrap the query in a database-level read-only transaction — SET TRANSACTION READ ONLY on the direct psycopg path, begin_transaction on the RDS Data API path — and a read-only transaction blocks writes to the database. COPY ... TO PROGRAM reads rows and hands them to a shell; it is not a write. In 1.1.6 the only thing between that statement and the shell was the keyword list.
Why it matters
The practical lesson is about how upgrade decisions get made. On September 4, an operator reading bulletin 2026-101 saw a 6.5 that let an attacker modify data beyond read-only scope, and weighed a change window against it. Some upgraded and, without knowing it, closed a 9.6 command execution flaw; some deferred and left it open. The bulletin’s severity was not the release’s severity, and nothing published that day would have told them otherwise.
A version number is a bundle. When a vendor discloses the contents of that bundle one item at a time over 76 days, the severity attached to any single disclosure understates what the upgrade is worth. The signal — “1.1.7 also fixed a critical” — existed inside AWS from the moment the release shipped, and the only way an outside reader could have found it was to diff the wheels.
The fix shape repeats, too. CWE-184 — incomplete list of disallowed inputs — now appears on both records against this package. On September 4 the answer to a read-only bypass was a longer denylist; on September 9 the answer to command execution is one more regex on top of it. Each fix is correct for the case it names. Neither changes the property that the guardrail is a hand-maintained list of things an attacker might write, and that its completeness is the security boundary.
Which is why the disagreement between the advisory and the patch comment is not academic: the two texts point at opposite configurations. An operator running with --allow_write_query — writes enabled, the configuration the patch comment identifies as exposed — who reads “read-only enforcement bypass” in the advisory title may reasonably conclude the finding does not describe their deployment. It is the deployment the shipped code says was least protected.
Both bulletins land on the same mitigation, which says something about where the real boundary sits. The GHSA workaround is not a flag in the MCP server. It is a database role: connect with CONNECT, USAGE, and SELECT only, not as a superuser, rds_superuser, or cluster master account. A role grant is enforced by the database, and it did not need to anticipate COPY ... TO PROGRAM in advance. The list did.
What to do
Upgrade awslabs.postgres-mcp-server. Version 1.2.1, uploaded September 8, 2026, is current; 1.1.7 is the floor that carries both fixes. If you vendored or forked the server, AWS asks that the patch be carried across — the changed file is awslabs/postgres_mcp_server/mutable_sql_detector.py, and the addition is COPY_PROGRAM_PATTERN and its call site in check_sql_injection_risk.
Check the role before you check the version. Both preconditions AWS names are database privileges: superuser, or membership in pg_execute_server_program. Revoke that membership from the role the MCP server connects as, and do not point the server at a database using a master or superuser account. That control holds whether or not the next denylist entry lands.
If you run with writes enabled, do not treat this as inapplicable. The version that fixes it is the same version either way, and AWS’s own patch comment describes the write-enabled mode as the one the new check was added for.
The affected profile is a self-managed PostgreSQL server reached over PG_WIRE_PROTOCOL, per the bulletin’s own scoping. If your deployment uses the RDS Data API path, this bulletin does not name it — but the upgrade is the same, and CVE-2026-85787 is not scoped that narrowly.
Sourcing note
CVE-2026-87911 was read from the NVD API at services.nvd.nist.gov, which supplied the description, both CVSS vectors and their source, the CWE assignments, the reference list, and the publication timestamp. AWS security bulletin 2026-104-AWS was read at aws.amazon.com, and GHSA-fph8-pg5w-78fv at github.com; the affected-configuration language, the workaround, and the credits are from those two. Release dates and version ordering are from the PyPI JSON API, a package registry rather than a report about one.
The description of what 1.1.7 changed is not from a changelog. The 1.1.6 and 1.1.7 wheels were downloaded from PyPI and diffed, and the block quote above is the comment shipped inside the 1.1.7 package. The statements about 1.1.6’s behavior come from importing that package’s mutable_sql_detector module and calling its functions on the statements described — the detector in isolation, not a running server against a live database, which cannot rule out a path that reaches the query without passing the check.
Unresolved: AWS does not explain why two CVEs fixed by one June release were disclosed 71 and 76 days later, five days apart, and neither bulletin references the other. The disagreement between the advisory text and the shipped patch comment about which mode was exposed is unresolved on the public record, and neither document has been revised. Neither CVE carries a CISA KEV entry or a federal remediation deadline, and no exploitation of either has been reported by any party we checked.
