Note: This is responsible disclosure. A private security advisory (GHSA-fvvm-2jhg-5jgj) was filed 45 days ago. No maintainer response has been received. The repository appears unmaintained (last commit: 2025-08-27). These findings are being disclosed publicly to protect users.
Three verified vulnerabilities in mcp-database-server
All findings verified from source code on the main branch (2026-04-10).
Finding A (CRITICAL): SQL Injection via ReadResource URI — PostgreSQL and SQLite adapters
Files: src/handlers/resourceHandlers.ts, src/db/postgresql-adapter.ts, src/db/sqlite-adapter.ts
handleReadResource() extracts tableName from the URI path with no sanitization:
const tableName = pathComponents.pop(); // attacker-controlled, unvalidated
const query = getDescribeTableQuery(tableName!);
PostgreSQL interpolates directly:
WHERE c.table_name = '${tableName}' // unsanitized
SQLite — no quoting at all:
return `PRAGMA table_info(${tableName})`;
PoC (PostgreSQL): URI db:///database/users' UNION SELECT usename,passwd,NULL,NULL,NULL FROM pg_shadow--/schema → returns password hashes.
PoC (SQLite): URI sqlite:///app.db/x);ATTACH DATABASE '/etc/passwd' AS s--/schema
Fix: Validate tableName against existing table list before constructing the query. Use parameterized queries.
Finding B (CRITICAL): MySQL multipleStatements: true enables stacked-query injection
File: src/db/mysql-adapter.ts
The constructor unconditionally sets multipleStatements: true (default in mysql2 is false). The exec() method uses connection.query() (not execute()), which executes all stacked statements. Tool validation is prefix-only:
if (!query.trim().toLowerCase().startsWith("create table")) {
throw new Error("Only CREATE TABLE statements are allowed");
}
await dbExec(query); // entire query string passes through
PoC: create_table with "CREATE TABLE x(id int); DROP TABLE users;" — prefix check passes, all statements execute.
Fix: Remove multipleStatements: true. It defaults to false in mysql2.
Finding C (HIGH): SQLite ATTACH DATABASE / arbitrary file read via multi-statement dbExec()
createTable and alterTable apply prefix-only checks then call dbExec() with the full input. dbExec routes to sqlite3.Database.exec(), a multi-statement executor by design.
PoC: create_table with "CREATE TABLE x(id int); ATTACH DATABASE '/etc/passwd' AS shadow;" → SQLite opens /etc/passwd. Follow-up read_query reads the file. Can escalate to RCE via load_extension.
Fix: Use db.run() (single-statement) for DDL tools. Blocklist ATTACH, DETACH, .load patterns.
Summary
| Finding |
Severity |
Impact |
| A — URI SQL injection (PostgreSQL + SQLite) |
Critical |
Full DB read, credential exfil |
| B — MySQL multipleStatements |
Critical |
Arbitrary stacked SQL |
| C — SQLite ATTACH via dbExec |
High |
File-system read, potential RCE |
All published versions are affected. No patched version exists.
If you are using this package, consider:
- Running it in an isolated environment with no access to sensitive data
- Restricting which clients can connect to the MCP server
- Migrating to an actively maintained alternative
Private advisory: GHSA-fvvm-2jhg-5jgj
Reporter: @piiiico
Three verified vulnerabilities in mcp-database-server
All findings verified from source code on the
mainbranch (2026-04-10).Finding A (CRITICAL): SQL Injection via ReadResource URI — PostgreSQL and SQLite adapters
Files:
src/handlers/resourceHandlers.ts,src/db/postgresql-adapter.ts,src/db/sqlite-adapter.tshandleReadResource()extractstableNamefrom the URI path with no sanitization:PostgreSQL interpolates directly:
SQLite — no quoting at all:
PoC (PostgreSQL): URI
db:///database/users' UNION SELECT usename,passwd,NULL,NULL,NULL FROM pg_shadow--/schema→ returns password hashes.PoC (SQLite): URI
sqlite:///app.db/x);ATTACH DATABASE '/etc/passwd' AS s--/schemaFix: Validate
tableNameagainst existing table list before constructing the query. Use parameterized queries.Finding B (CRITICAL): MySQL
multipleStatements: trueenables stacked-query injectionFile:
src/db/mysql-adapter.tsThe constructor unconditionally sets
multipleStatements: true(default in mysql2 isfalse). Theexec()method usesconnection.query()(notexecute()), which executes all stacked statements. Tool validation is prefix-only:PoC:
create_tablewith"CREATE TABLE x(id int); DROP TABLE users;"— prefix check passes, all statements execute.Fix: Remove
multipleStatements: true. It defaults tofalsein mysql2.Finding C (HIGH): SQLite
ATTACH DATABASE/ arbitrary file read via multi-statementdbExec()createTableandalterTableapply prefix-only checks then calldbExec()with the full input.dbExecroutes tosqlite3.Database.exec(), a multi-statement executor by design.PoC:
create_tablewith"CREATE TABLE x(id int); ATTACH DATABASE '/etc/passwd' AS shadow;"→ SQLite opens/etc/passwd. Follow-upread_queryreads the file. Can escalate to RCE viaload_extension.Fix: Use
db.run()(single-statement) for DDL tools. BlocklistATTACH,DETACH,.loadpatterns.Summary
All published versions are affected. No patched version exists.
If you are using this package, consider:
Private advisory: GHSA-fvvm-2jhg-5jgj
Reporter: @piiiico