@yanzhang123/readonly-db-mcp 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,7 +59,10 @@ allowed_databases = ["application_database"]
59
59
  # Unrecognized stored functions/UDFs are blocked. This list should normally
60
60
  # remain empty. Add only reviewed deterministic functions with no side effects.
61
61
  allowed_functions = []
62
- mask_columns = ["password", "*password*", "*passwd*", "*pwd*", "*secret*", "token", "*token", "*_token", "token_*", "*tokenvalue*", "*accesstoken*", "*refreshtoken*", "*idtoken*", "*appsec*", "*api_key*", "*private_key*", "*ssn*", "*id_card*", "*phone*", "*mobile*", "*email*"]
62
+ # No result masking by default; omitting this setting has the same effect.
63
+ mask_columns = []
64
+ # To opt in, replace the empty list with your schema-specific rules, for example:
65
+ # mask_columns = ["password", "access_token", "users.phone"]
63
66
 
64
67
  ssl_mode = "VERIFY_IDENTITY"
65
68
  ssl_ca = "/etc/mysql-mcp/company-ca.pem"
@@ -155,10 +158,15 @@ In fail-closed mode, the server first persists and fsyncs a `started` event
155
158
  before opening the database connection, then writes a terminal outcome. Alert
156
159
  on unmatched started events and preserve them during incident review.
157
160
 
158
- Result masking is a final egress control, not an authorization boundary. Output
159
- names matching `mask_columns` are redacted, and any sensitive source column
160
- found through an alias, expression, subquery, or CTE causes conservative
161
- redaction. Validate patterns with representative queries and prefer restricted
161
+ Result masking is a final egress control, not an authorization boundary. Source
162
+ columns matching `mask_columns` are redacted through aliases, expressions,
163
+ subqueries and CTEs; unresolved outputs fall back to their result names. Bare
164
+ patterns match only field names, and qualified patterns retain their scope.
165
+ COUNT statistics remain available without exposing their input values. Masking
166
+ is disabled by default: omitted or empty `mask_columns` returns all field values
167
+ without redaction. Configure the fields to mask per connection; there is no
168
+ built-in sensitive-field list. Existing explicit rules are not rewritten during
169
+ upgrades. Validate patterns with representative queries and prefer restricted
162
170
  views or column grants for fields that must never be exposed.
163
171
 
164
172
  When HMAC signing is enabled, verify a line by removing `signature`, serializing
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  - 整库表/列元数据可分页;`inspect_catalog` / `get_schema_info` / `dump_schema` 返回 `truncated` 与 `next_offset`,禁止把单页当成全集
25
25
  - 查询分页下推到 MySQL,避免客户端截断结果引发 Connector/Python `errno=-1`
26
26
  - 内置结果行数和单元格长度限制,最大返回 1000 行
27
- - 按源字段精确脱敏,支持别名、CTE 以及 JSON 内敏感键
27
+ - 结果默认不脱敏;按连接配置需要隐藏的字段,支持别名、CTE 和 JSON 内键
28
28
  - 支持 SSL/TLS、SSH 隧道、Streamable HTTP 和旧版 SSE 传输
29
29
  - 密码可保存在环境变量、操作系统凭据库或受控的密钥命令中
30
30
  - 保留旧版单连接 `MYSQL_*` 环境变量和 `query` 工具别名
@@ -82,6 +82,26 @@ Python 包仍适用于源码部署,但本分支的主要分发方式是上述
82
82
 
83
83
  ## 配置
84
84
 
85
+ ### 结果脱敏配置
86
+
87
+ **默认不脱敏。** 代码不再内置敏感字段名单;省略配置或配置为空时,所有结果字段(包括密码、Token、联系方式及 JSON 内键)均按查询结果返回,`masked_columns` 为空。
88
+
89
+ 命名连接通过 `mask_columns` 指定要隐藏的字段。配置属于各自连接,支持大小写不敏感的字段名和通配符:
90
+
91
+ ```toml
92
+ [connections.dev]
93
+ # 其他连接参数见下方完整示例。
94
+ mask_columns = [] # 默认全部放开;省略此项效果相同。
95
+ # 如需脱敏,将上面一行改为自己的字段列表,例如:
96
+ # mask_columns = ["password", "access_token", "users.phone"]
97
+ ```
98
+
99
+ 旧单连接模式通过 `MYSQL_MASK_COLUMNS` 配置,以逗号分隔;未设置或设为空字符串均不脱敏。例如 `MYSQL_MASK_COLUMNS=password,access_token` 只按这两个规则处理。使用命名连接文件时,以各连接自己的 `mask_columns` 为准。
100
+
101
+ 显式配置会追踪别名、表达式和 CTE 的字段来源,并对 JSON 内匹配的键递归处理。不带点的模式只匹配字段名,例如 `password`;带点的模式按限定路径匹配,例如 `users.phone`。`COUNT` / `COUNT(DISTINCT ...)` 统计值可以正常读取,同一查询中匹配规则的原始值和拼接值仍会隐藏。
102
+
103
+ 脱敏只替换返回值,不改变 MySQL 的 `WHERE` / `JOIN` 条件或删除结果行。`[REDACTED]` 表示值被隐藏,JSON/compact 的 `masked_columns` 可定位受影响列。已有的非空配置仍会生效;需要全部放开时,把它改成空列表(单连接模式清空环境变量),或删除该配置项。
104
+
85
105
  ### 推荐:命名连接
86
106
 
87
107
  将 [`mysql-connections.example.toml`](mysql-connections.example.toml) 复制为已被 Git 忽略的 `mysql-connections.toml`,然后集中声明各环境:
@@ -101,7 +121,8 @@ query_timeout_ms = 30000
101
121
  max_rows = 500
102
122
  max_cell_length = 20000
103
123
  result_format = "json"
104
- mask_columns = ["password", "*password*", "*passwd*", "*pwd*", "*secret*", "token", "*token", "*_token", "token_*", "*tokenvalue*", "*accesstoken*", "*refreshtoken*", "*idtoken*", "*appsec*", "*api_key*", "*private_key*", "*ssn*", "*id_card*", "*phone*", "*mobile*", "*email*"]
124
+ # 默认不脱敏;需要时在列表中填写字段名或模式。
125
+ mask_columns = []
105
126
  pool_size = 3
106
127
  audit_enabled = true
107
128
 
@@ -174,6 +195,8 @@ readonly-db-mcp credentials --profiles-file C:/absolute/path/mysql-connections.t
174
195
 
175
196
  Windows 下 `keyring` 使用 Windows 凭据管理器。无桌面的服务环境可设置 `credential_provider = "command"`,并通过 `credential_command = ["executable", "arg1"]` 调用经过批准的密钥管理 CLI。程序不会通过 shell 执行命令;stderr 会被丢弃,输出长度受限,且只接受一行非空 UTF-8 文本。失败信息不会包含密钥、命令参数或凭据引用。
176
197
 
198
+ 每次请求最多读取一次凭据(含连接重试)。`keyring` 或 `command` 较慢时,可设置 `credential_cache_seconds = 30`(0–300,默认 0 即不缓存)在进程内存中短期复用密码:轮换后的新密码最晚在该时长后生效;服务端返回认证失败(1045/1698)、调用 `validate_connections` 或进程退出时立即丢弃缓存。该项只对 `keyring` 和 `command` 有效。
199
+
177
200
  ### 逻辑环境路由
178
201
 
179
202
  一个逻辑环境分布在多个物理实例时,可声明精确别名:
@@ -339,7 +362,7 @@ Claude Desktop、Codex 等宿主通常从自己的目录启动 MCP 服务,未
339
362
  - 必填参数:`query`
340
363
  - 可选参数:`connection`、`database`、`max_rows`、`offset`、`timeout_ms`、`max_response_bytes`、`result_format`、`audit_context`
341
364
  - 允许:`SELECT`、`WITH`、受数据库范围约束的 `SHOW`(包括 `SHOW CREATE TABLE/VIEW`)、`DESCRIBE`、`DESC`、`EXPLAIN`、`TABLE`
342
- - 始终拦截:DML、DDL、`USE`、事务控制、锁、`SELECT ... INTO`、会话变量赋值、MySQL 可执行注释和多语句
365
+ - 始终拦截:DML、DDL、`USE`、事务控制、锁、`SELECT ... INTO`、会话变量赋值、MySQL 可执行注释和多语句;禁止 `SET_VAR`、`MAX_EXECUTION_TIME`、`RESOURCE_GROUP` 优化器提示覆盖连接策略,需要缩短超时时使用 `timeout_ms`
343
366
  - 函数策略:默认拦截无法识别的存储函数/UDF;只有 `allowed_functions` 中经过审核的确定性函数可放行
344
367
  - 纵深防御:连接前先校验;随后在 `START TRANSACTION READ ONLY` 中执行并回滚
345
368
  - 分页:对适用的 `SELECT`/CTE/UNION 下推 `LIMIT max_rows+1 OFFSET offset`,完整消费该有界结果后再回滚;JSON 结果返回 `truncated` 和 `next_offset`
@@ -347,9 +370,25 @@ Claude Desktop、Codex 等宿主通常从自己的目录启动 MCP 服务,未
347
370
  - 超时/取消:统一限制完整操作、Connector socket 和 MySQL/MariaDB 语句时间;取消请求会关闭活动连接,无需 `KILL` 权限
348
371
  - 格式:`json` 保留数据类型和元信息;`csv` 保持原有格式,使用标准引号规则,并明确表示 `NULL`;`compact` 第一行是 JSON 元信息,后面是 CSV 数据,保留目标库、路由、脱敏字段和分页信息。需要区分 SQL NULL 与字符串 `NULL` 时使用 JSON。
349
372
  - 审计归因:`audit_context` 支持 `actor`、`purpose`、`ticket_id`,配置可要求调用前必须提供指定字段
350
- - 临时故障恢复:Connector/Python 客户端侧 `errno=-1` 会重试一次;表不存在、字段不存在、语法和权限错误不会自动重试。
373
+ - 临时故障恢复:仅执行/取数阶段的 Connector/Python `InterfaceError(errno=-1)` 保留一次兼容重试。`ReadTimeoutError` / `WriteTimeoutError` 不重放,统一返回 `QUERY_TIMEOUT` 并丢弃活动连接;其他未知 `errno=-1`、表不存在、字段不存在、语法和权限错误不会自动重试。
351
374
  - 首次查询或结构不确定时先用 `list_tables` / `get_schema_info` 确认真实名称;每次传入一致的 `connection`、`database`。收到确定性 SQL 错误后核对元数据再改 SQL,不自动换库或改表名执行。
352
375
 
376
+ ### `explain_sql`
377
+
378
+ 对原始 `SELECT` / `WITH`(含 UNION)生成普通 `EXPLAIN` 执行计划,便于先检查索引、连接顺序和预估扫描行数。
379
+
380
+ - 必填 `query`,传入原 SQL;不要添加 `EXPLAIN` 前缀。拒绝写语句、多语句和 `EXPLAIN ANALYZE`。
381
+ - 可选参数与 `execute_sql` 一致,复用数据库白名单、函数策略、只读事务、审计、脱敏、超时和响应预算。
382
+ - 保留原 SQL 中的 `LIMIT/OFFSET`;工具参数 `max_rows/offset` 只分页执行计划,避免为诊断修改原查询。
383
+ - 默认 `plan_output=rows` 返回数据库的计划结果,格式取决于服务器设置。保留完整计划单元格,不再受普通数据的 `max_cell_length` 截断;首行超响应预算时明确返回 `RESULT_TOO_LARGE` 并提示切换分片,不交付残缺计划。
384
+ - `plan_output=chunks` 显式请求 `EXPLAIN FORMAT=JSON`;先按原规则脱敏,再把计划文本拆成固定长度的 `part/text` 行。此时 `offset/max_rows` 针对分片,响应仍受 UTF-8 字节预算限制。默认使用 `json`,也可选 `compact`;不接受缺少计划元信息的 `csv`。
385
+ - 第一页保留 `plan.id`。续页传 `offset=next_offset` 和 `expected_plan_id=plan.id`,按 `part` 顺序拼接 `text`,直到 `truncated=false`。分片长度、总字符数和总片数在 `plan` 中。每页重新生成计划;计划、目标或分片上下文变化时返回 `PLAN_CHANGED`,需丢弃旧片段并从 0 重取,避免混合版本。
386
+ - 计划是估算信息,不代表实际执行耗时;工具不会自动建索引、更新统计信息或运行 ANALYZE。分片模式需要服务器支持 `EXPLAIN FORMAT=JSON`,不会自动切换到 ANALYZE 或其他数据库。
387
+
388
+ ```json
389
+ {"query":"SELECT id FROM orders WHERE status = 'open' ORDER BY id LIMIT 20","connection":"dev","database":"app_dev","result_format":"json"}
390
+ ```
391
+
353
392
  ### `query`
354
393
 
355
394
  `execute_sql` 的兼容别名,使用相同的只读、白名单、脱敏、超时、分页和审计策略。不会恢复不安全的进程级 `use_connection` 或 `use_database`。
@@ -413,12 +452,19 @@ Claude Desktop、Codex 等宿主通常从自己的目录启动 MCP 服务,未
413
452
 
414
453
  数据库错误通过 MCP `isError=true` 返回,文本为 JSON,同时提供 `structuredContent`。字段包含 `code`、`message`、`retryable`、`next_action`、安全的错误码/阶段、实际连接/库、请求的连接/库、`route_applied` 和 `query_id`。不透传驱动原始错误、SQL 文本、密码或主机连接信息。
415
454
 
455
+ - `SQL_PARSE_ERROR`:连接前解析失败;只返回固定消息、可用的数字行列位置和查询指纹,不回显 SQL、字面量、解析器上下文或 ANSI 高亮。
416
456
  - `TABLE_NOT_FOUND`:1146/1109,先核对目标库并发现真实表名。
417
457
  - `COLUMN_NOT_FOUND`:1054,检查字段和别名。
418
458
  - `SQL_SYNTAX_ERROR`:1064,核对 MySQL 语法。
419
459
  - `DATABASE_NOT_FOUND`、`AUTHENTICATION_FAILED`、`ACCESS_DENIED`:核对配置和只读权限。
420
- - `CONNECTION_BUSY`:可稍后重试;不会自动切换环境。
421
- - `RESULT_TOO_LARGE`:缩小字段投影后从原 offset 重查。
460
+ - `QUERY_TIMEOUT`:MySQL 3024、MariaDB 1969、驱动读写超时或本地完整调用截止时间;先缩小查询范围或用 `explain_sql` 检查计划,不直接重复昂贵查询。本地截止时间使用 `phase=deadline` 并携带 `timeout_ms`。
461
+ - `LOCK_WAIT_TIMEOUT` / `DEADLOCK`:1205 / 1213,检查并发事务或 DDL,在阻塞缓解后有限退避重试。
462
+ - `CONNECTION_FAILED` / `CONNECTION_LOST`:连接建立失败或结果接收中断;先核对网络和目标连接,中断结果不得当作完整结果。
463
+ - `QUERY_INTERRUPTED`:1317,先确认取消原因,不自动重新执行。
464
+ - `CONNECTION_BUSY`:本地队列拥塞,或 MySQL 1040/1203 连接数超限;降低并发后有限退避重试,不会自动切换环境。
465
+ - `retryable=true` 表示恢复后可由调用方有限重试,不会触发本服务新增自动重放;未知主机 2005、查询超时及主动中断均为 `false`。
466
+ - `RESULT_TOO_LARGE`:普通查询缩小字段投影后从原 offset 重查;执行计划改用 `plan_output=chunks` 从 0 获取。
467
+ - `PLAN_CHANGED`:计划续页指纹不一致,丢弃已收集分片并从 0 重取;`PLAN_FORMAT_UNSUPPORTED` 表示服务器没有返回预期的单行 JSON 计划。
422
468
 
423
469
  示例调用:
424
470
 
@@ -549,8 +595,7 @@ npm test
549
595
  - **受控元数据**:用户 SQL 默认不能直接查询 `information_schema`;使用 `list_tables`、`get_schema_info`、`inspect_catalog` 获取限定范围的元数据
550
596
  - **凭据隔离**:密码可来自环境变量、操作系统凭据库或无 shell 的受控命令;发现接口、日志和错误均不返回凭据值或引用
551
597
  - **资源限制**:整次调用、socket 和服务端语句均有超时;最多返回 1000 行,超长单元格会截断
552
- - **精确脱敏**:依据表达式源字段脱敏,别名不能绕过规则,也不会连带遮罩无关列;JSON 中 `clientSecret`、`AppSec` 等敏感键递归脱敏
553
- - **安全默认规则**:覆盖常见 password/PWD、mobile/phone、secret、凭据 token、API key、private key 命名;不会用宽泛的 `*token*` 误伤通道代码或 token 有效时长
598
+ - **可配置脱敏**:默认全部放开;通过 `mask_columns` / `MYSQL_MASK_COLUMNS` 指定字段后才脱敏,追踪别名和表达式来源,并递归处理 JSON 内匹配的键
554
599
  - **企业审计**:UTC JSONL 事件包含事件 ID、MCP 请求 ID、操作、调用方归因、策略决定、目标库、无字面量查询指纹、耗时、结果大小和结果状态;不记录 SQL 原文和结果数据
555
600
  - **两阶段审计**:失败关闭模式下,建立连接前先 fsync 写入 `started` 事件,随后记录终态事件
556
601
  - **默认加密**:数据库 TLS 默认 `REQUIRED`;生产配置建议使用 `VERIFY_CA` 或 `VERIFY_IDENTITY`
package/SECURITY.md CHANGED
@@ -153,10 +153,16 @@ The MCP server includes built-in protection against SQL injection for administra
153
153
  - Audit logs never contain SQL text, result data, credentials, hosts, usernames,
154
154
  or SSH key paths. They contain a literal-free fingerprint plus the policy and
155
155
  execution metadata needed for investigation.
156
- - Result cells whose output or source columns match `mask_columns` are replaced
157
- with `[REDACTED]`. Source-column detection traverses aliases, expressions and
158
- CTEs; review the patterns for your schema because name-based masking cannot
159
- replace database views or column-level permissions.
156
+ - Result masking is opt-in: `mask_columns` defaults to an empty list, and legacy
157
+ `MYSQL_MASK_COLUMNS` defaults to empty. There is no built-in sensitive-field list;
158
+ omitted or empty rules return all field values without redaction, including JSON
159
+ keys. When rules are configured, matching source columns (or output names when
160
+ unresolved) and JSON keys are replaced with `[REDACTED]`. Unqualified patterns
161
+ match only field names; qualified patterns retain table/database scope. Aliases,
162
+ expressions and CTEs retain tracing. COUNT statistics do not inherit masking
163
+ from their inputs, while matching raw values and combined expressions remain
164
+ masked. Configure rules against your schema; name-based masking cannot replace
165
+ database views or column-level permissions.
160
166
 
161
167
  These controls enforce the MCP read-only contract even when the supplied MySQL
162
168
  account has write privileges. A `SELECT`-only account is still recommended as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanzhang123/readonly-db-mcp",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Enterprise-auditable, strictly read-only MySQL MCP server with multi-environment profiles.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",