dsh-db-mcp 0.6.8 → 0.6.9

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.
package/README.md CHANGED
@@ -310,6 +310,21 @@ driver itself where the engine allows it (`mode=ro` for SQLite,
310
310
  `default_transaction_read_only` for PostgreSQL, `SET SESSION TRANSACTION READ ONLY`
311
311
  for MySQL).
312
312
 
313
+ ### What the plugin runs, and with what
314
+
315
+ - The Python server is started with a fixed interpreter path, a fixed script and a
316
+ fixed argument list — never through a shell, so no string is ever re-parsed by
317
+ `cmd.exe` or `/bin/sh`.
318
+ - The child does **not** inherit the host environment. It gets a short allowlist
319
+ (`PATH`, `TEMP`, `HOME`, `PYTHON*`, and similar) plus the three `DB_MCP_*` flags
320
+ the plugin sets. Tokens or cloud credentials in the DSH process are not passed
321
+ down.
322
+ - Connections to PostgreSQL, MySQL and Oracle are opened by that child; only SQLite
323
+ is read in-process. The raw TCP probe is a fallback used when the child cannot
324
+ start, and it never sends credentials.
325
+ - Passwords live in `secrets.json` (mode `0600`) and are never returned by any HTTP
326
+ route; the panel only learns `hasSecret`.
327
+
313
328
  ## Audit log
314
329
 
315
330
  Every MCP tool call appends one JSON line to `<runtime>/logs/audit.jsonl` (0600):
package/README.zh.md CHANGED
@@ -209,6 +209,14 @@ node dev/mcp.test.mjs # 19 项:真实 Python 服务走 stdio,真实 SQLi
209
209
 
210
210
  Python 服务启动时从环境变量 `DB_MCP_ALLOW_WRITE` 读取全局标记;开关一变,Host 就会重启它。只要引擎支持,只读还会在驱动层再兜一次(SQLite 的 `mode=ro`、PostgreSQL 的 `default_transaction_read_only`、MySQL 的 `SET SESSION TRANSACTION READ ONLY`)。
211
211
 
212
+ ### 插件会启动什么,以及带着什么
213
+
214
+ - Python 服务用固定的解释器路径、固定脚本、固定参数启动,**从不经过 shell**,所以任何字符串都不会被 `cmd.exe` 或 `/bin/sh` 再解析一次。
215
+ - 子进程**不继承** Host 的环境变量。它只拿到一张很短的允许清单(`PATH`、`TEMP`、`HOME`、`PYTHON*` 之类)再加上插件自己设置的 `DB_MCP_*`。DSH 进程里的 token、云凭据不会被带下去。
216
+ - PostgreSQL / MySQL / Oracle 都由这个子进程连接,只有 SQLite 是在进程内读的。裸 TCP 探测只是子进程起不来时的兜底,且不发任何凭据。
217
+ - 口令存在 `secrets.json`(`0600`),任何 HTTP 接口都不会返回它;面板只知道 `hasSecret`。
218
+
219
+
212
220
  ## 审计日志
213
221
 
214
222
  每次 MCP 工具调用都会向 `<运行时目录>/logs/audit.jsonl`(0600)追加一行 JSON:时间、工具、数据源、耗时、行数、截断到 2000 字符的 SQL,以及失败时的错误。结果行永不记录。文件超过 512 KiB 轮转为 `audit.1.jsonl`,也可以在 `settings.json` 里用 `auditLog: false` 关掉。面板显示最近十几条,时间按**本机时区**换算——文件里存的是 UTC,这样换机器也仍然可比;卡片会把每条时间戳转成浏览者的时区,并标出所用偏移。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-mcp",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "DSH database plugin: a data-source management panel plus a Python MCP server that exposes those sources to the agent.",
5
5
  "keywords": [
6
6
  "dsh",
package/src/host.js CHANGED
@@ -33,7 +33,7 @@ const { createBridge } = await import(new URL(`./tools.js?v=${GENERATION}`, impo
33
33
 
34
34
  const API_PREFIX = '/plugins/db-mcp/api'
35
35
  const PLUGIN_NAME = 'dsh-db-mcp'
36
- const PLUGIN_VERSION = '0.6.7'
36
+ const PLUGIN_VERSION = '0.6.9'
37
37
  const SCHEMA_VERSION = 1
38
38
  const REGISTRY_FILE = 'datasources.json'
39
39
  const SECRETS_FILE = 'secrets.json'
package/src/mcp.js CHANGED
@@ -16,6 +16,50 @@ import { spawn } from 'node:child_process';
16
16
  const DEFAULT_TIMEOUT_MS = 30_000;
17
17
  const INIT_TIMEOUT_MS = 60_000;
18
18
 
19
+ /**
20
+ * The Python child gets a small, explicit environment instead of a copy of ours.
21
+ * Spreading `process.env` would hand every token and cloud credential in the
22
+ * DSH process to a driver stack that only ever needs to find its interpreter.
23
+ */
24
+ const ENV_ALLOW = [
25
+ 'PATH',
26
+ 'Path',
27
+ 'PATHEXT',
28
+ 'SystemRoot',
29
+ 'SYSTEMROOT',
30
+ 'windir',
31
+ 'ComSpec',
32
+ 'COMSPEC',
33
+ 'TEMP',
34
+ 'TMP',
35
+ 'TMPDIR',
36
+ 'HOME',
37
+ 'USERPROFILE',
38
+ 'HOMEDRIVE',
39
+ 'HOMEPATH',
40
+ 'APPDATA',
41
+ 'LOCALAPPDATA',
42
+ 'PROGRAMDATA',
43
+ 'PROGRAMFILES',
44
+ 'LANG',
45
+ 'LC_ALL',
46
+ 'TZ',
47
+ 'PWD',
48
+ 'PYTHONPATH',
49
+ 'PYTHONHOME',
50
+ 'PYTHONIOENCODING',
51
+ 'VIRTUAL_ENV',
52
+ ];
53
+
54
+ function childEnvironment(extra) {
55
+ const env = {};
56
+ for (const key of ENV_ALLOW) {
57
+ const value = process.env[key];
58
+ if (typeof value === 'string' && value !== '') env[key] = value;
59
+ }
60
+ return Object.assign(env, extra || {});
61
+ }
62
+
19
63
  export class McpError extends Error {
20
64
  constructor(message, { code = 'mcp_error', data } = {}) {
21
65
  super(message);
@@ -61,7 +105,7 @@ export class McpClient {
61
105
  if (this.#child) return this;
62
106
  const child = spawn(this.command, this.args, {
63
107
  cwd: this.cwd,
64
- env: { ...process.env, ...this.env },
108
+ env: childEnvironment(this.env),
65
109
  stdio: ['pipe', 'pipe', 'pipe'],
66
110
  windowsHide: true,
67
111
  });