Every CVE Search/ Get API key

CVE-2026-61788

@bytebase/dbhub's read-only mode does not prevent database writes

7.4 HighFix available

DBHub is a database MCP server for Postgres, MySQL, SQL Server, Oracle, MariaDB, SQLite. Prior to version 0.22.6, 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. Version 0.22.6 patches the issue.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
None

Exploit likelihood

0.40%Up 0.1 points since Sep 25, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.4

@bytebase/dbhub's read-only mode does not prevent database writes

DBHub is a database MCP server for Postgres, MySQL, SQL Server, Oracle, MariaDB, SQLite. Prior to version 0.22.6, 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. Version 0.22.6 patches the issue.

Record
CVE-2026-61788
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-184, CWE-636, CWE-863
Updated
2026-11-02
EUVDEUVD-2026-86326 7.4

DBHub is a database MCP server for Postgres, MySQL, SQL Server, Oracle, MariaDB, SQLite. Prior to version 0.22.6, 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. Version 0.22.6 patches the issue.

Record
EUVD-2026-86326
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-mwwr-p57h-56pf 7.4

@bytebase/dbhub's read-only mode does not prevent database writes

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):

// 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):

// 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…

Record
GHSA-mwwr-p57h-56pf
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-184, CWE-636, CWE-863
Updated
2026-11-02
NVD 7.4

DBHub is a database MCP server for Postgres, MySQL, SQL Server, Oracle, MariaDB, SQLite. Prior to version 0.22.6, 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. Version 0.22.6 patches the issue.

Record
CVE-2026-61788
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-184, CWE-636, CWE-863
Updated
2026-11-02
OSV 7.4

@bytebase/dbhub's read-only mode does not prevent database writes

DBHub is a database MCP server for Postgres, MySQL, SQL Server, Oracle, MariaDB, SQLite. Prior to version 0.22.6, 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. Version 0.22.6 patches the issue.

Record
CVE-2026-61788
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-184, CWE-636, CWE-863
Updated
2026-11-02
OSVGHSA-mwwr-p57h-56pf 7.4

@bytebase/dbhub's read-only mode does not prevent database writes

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):

// 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):

// 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…

Record
GHSA-mwwr-p57h-56pf
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Weakness
CWE-184, CWE-636, CWE-863
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
@bytebase/dbhub
npm
before 0.22.6
0.22.6

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-61788 is a high vulnerability (CVSS 7.4) in Bytebase Dbhub, published September 24, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.40%, the 34th percentile. Fixed in @bytebase/dbhub 0.22.6.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open