shellward 0.5.15 → 0.6.0

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.
Files changed (41) hide show
  1. package/README.md +115 -30
  2. package/dist/auto-check.d.ts +1 -0
  3. package/dist/auto-check.js +12 -1
  4. package/dist/commands/index.d.ts +2 -1
  5. package/dist/commands/index.js +7 -0
  6. package/dist/commands/scan-mcp.d.ts +2 -0
  7. package/dist/commands/scan-mcp.js +105 -0
  8. package/dist/core/engine.d.ts +35 -0
  9. package/dist/core/engine.js +225 -30
  10. package/dist/index.d.ts +4 -2
  11. package/dist/index.js +18 -3
  12. package/dist/mcp-baseline.d.ts +27 -0
  13. package/dist/mcp-baseline.js +73 -0
  14. package/dist/mcp-client.d.ts +29 -0
  15. package/dist/mcp-client.js +264 -0
  16. package/dist/mcp-server.js +64 -9
  17. package/dist/rules/dangerous-commands.js +6 -2
  18. package/dist/rules/injection-en.js +27 -2
  19. package/dist/rules/injection-zh.js +27 -4
  20. package/dist/rules/sensitive-patterns.d.ts +13 -1
  21. package/dist/rules/sensitive-patterns.js +32 -5
  22. package/dist/rules/tool-poisoning.d.ts +8 -0
  23. package/dist/rules/tool-poisoning.js +96 -0
  24. package/dist/types.d.ts +32 -0
  25. package/dist/types.js +3 -1
  26. package/package.json +5 -3
  27. package/server.json +2 -2
  28. package/src/auto-check.ts +11 -1
  29. package/src/commands/index.ts +9 -1
  30. package/src/commands/scan-mcp.ts +118 -0
  31. package/src/core/engine.ts +250 -31
  32. package/src/index.ts +25 -5
  33. package/src/mcp-baseline.ts +97 -0
  34. package/src/mcp-client.ts +268 -0
  35. package/src/mcp-server.ts +71 -9
  36. package/src/rules/dangerous-commands.ts +6 -2
  37. package/src/rules/injection-en.ts +27 -2
  38. package/src/rules/injection-zh.ts +27 -4
  39. package/src/rules/sensitive-patterns.ts +37 -5
  40. package/src/rules/tool-poisoning.ts +108 -0
  41. package/src/types.ts +38 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  [![npm](https://img.shields.io/npm/v/shellward?color=cb0000&label=npm)](https://www.npmjs.com/package/shellward)
12
12
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE)
13
- [![tests](https://img.shields.io/badge/tests-123%20passing-brightgreen)](#performance)
13
+ [![tests](https://img.shields.io/badge/tests-183%20passing-brightgreen)](#performance)
14
14
  [![deps](https://img.shields.io/badge/dependencies-0-brightgreen)](#performance)
15
15
 
16
16
  [English](#demo) | [中文](#中文)
@@ -54,13 +54,14 @@ Your AI agent has full access to tools — shell, email, HTTP, file system. One
54
54
 
55
55
  | Platform | Integration | Note |
56
56
  |----------|------------|------|
57
- | **Claude Desktop** | MCP Server | Add to `claude_desktop_config.json` — 7 security tools |
57
+ | **Claude Desktop** | MCP Server | Add to `claude_desktop_config.json` — 8 security tools |
58
58
  | **Cursor** | MCP Server | Add to `.cursor/mcp.json` |
59
59
  | **OpenClaw** | MCP + Plugin + SDK | `openclaw plugins install shellward` — adapts to available hooks |
60
60
  | **Claude Code** | MCP + SDK | Anthropic's official CLI agent |
61
61
  | **LangChain** | SDK | LLM application framework |
62
62
  | **AutoGPT** | SDK | Autonomous AI agents |
63
63
  | **OpenAI Agents** | SDK | GPT agent platform |
64
+ | **Hermes Agent** | MCP Server | Nous Research's self-improving agent — register via MCP Integration |
64
65
  | **Dify / Coze** | SDK | Low-code AI platforms |
65
66
  | **Any MCP Client** | MCP Server | stdio JSON-RPC, zero dependencies |
66
67
  | **Any AI Agent** | SDK | `npm install shellward` — 3 lines to integrate |
@@ -69,8 +70,10 @@ Your AI agent has full access to tools — shell, email, HTTP, file system. One
69
70
 
70
71
  - **8 defense layers**: prompt guard, input auditor, tool blocker, output scanner, security gate, outbound guard, data flow guard, session guard
71
72
  - **DLP model**: data returns in full (no redaction), outbound sends are blocked when PII was recently accessed
72
- - **PII detection**: SSN, credit cards, API keys (OpenAI/GitHub/AWS), JWT, passwords — plus Chinese ID card (GB 11643 checksum), phone, bank card (Luhn)
73
- - **32 injection rules**: 18 Chinese + 14 English, risk scoring, mixed-language detection
73
+ - **PII detection**: SSN, credit cards, API keys (OpenAI/GitHub/AWS), JWT, passwords — plus Chinese ID card (GB 11643 checksum), carrier-validated mobile, UnionPay bank card (Luhn) — precision-tuned to cut false positives
74
+ - **37 injection rules**: 20 Chinese + 17 English, risk scoring, mixed-language detection
75
+ - **MCP tool-poisoning scan**: detects hidden instructions, invisible characters, concealment ("hide from user"), secret-file access & exfiltration hints in a tool's description/parameters
76
+ - **MCP rug-pull detection**: fingerprints each tool's description on first sight, flags silent changes across runs
74
77
  - **Data exfiltration chain**: read sensitive data → send email / HTTP POST / curl = blocked
75
78
  - **Bash bypass detection**: catches `curl -X POST`, `wget --post`, `nc`, Python/Node network exfil
76
79
  - **Zero dependencies**, zero config, Apache-2.0
@@ -83,42 +86,32 @@ ShellWard runs as a standalone MCP server over stdio — zero dependencies, no `
83
86
 
84
87
  **Claude Desktop / Cursor / any MCP client:**
85
88
 
86
- Add to your MCP config (`claude_desktop_config.json`, `.cursor/mcp.json`, etc.):
89
+ Add to your MCP config (`claude_desktop_config.json`, `.cursor/mcp.json`, OpenClaw, etc.) — no install path needed, `npx` fetches the published `shellward-mcp` bin:
87
90
 
88
91
  ```json
89
92
  {
90
93
  "mcpServers": {
91
94
  "shellward": {
92
95
  "command": "npx",
93
- "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
96
+ "args": ["-y", "-p", "shellward", "shellward-mcp"]
94
97
  }
95
98
  }
96
99
  }
97
100
  ```
98
101
 
99
- **OpenClaw:**
102
+ If installed globally (`npm i -g shellward`), simply use `"command": "shellward-mcp"`.
100
103
 
101
- ```json
102
- {
103
- "mcpServers": {
104
- "shellward": {
105
- "command": "npx",
106
- "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
107
- }
108
- }
109
- }
110
- ```
111
-
112
- **7 MCP tools available:**
104
+ **8 MCP tools available:**
113
105
 
114
106
  | Tool | Description |
115
107
  |------|-------------|
116
108
  | `check_command` | Check if a shell command is safe (rm -rf, reverse shell, fork bomb...) |
117
- | `check_injection` | Detect prompt injection in text (32+ rules, zh+en) |
109
+ | `check_injection` | Detect prompt injection in text (37+ rules, zh+en) |
118
110
  | `scan_data` | Scan for PII & sensitive data (CN ID/phone/bank, API keys, SSN...) |
119
111
  | `check_path` | Check if file path operation is safe (.env, .ssh, credentials...) |
120
112
  | `check_tool` | Check if tool name is allowed (blocks payment/transfer tools) |
121
113
  | `check_response` | Audit AI response for canary leaks & PII exposure |
114
+ | `scan_mcp_tool` | Scan an MCP tool definition for poisoning + rug-pull |
122
115
  | `security_status` | Get current security config & active layers |
123
116
 
124
117
  **Environment variables:**
@@ -127,7 +120,8 @@ Add to your MCP config (`claude_desktop_config.json`, `.cursor/mcp.json`, etc.):
127
120
  |----------|--------|---------|
128
121
  | `SHELLWARD_MODE` | `enforce` / `audit` | `enforce` |
129
122
  | `SHELLWARD_LOCALE` | `auto` / `zh` / `en` | `auto` |
130
- | `SHELLWARD_THRESHOLD` | `0`-`100` | `60` |
123
+ | `SHELLWARD_THRESHOLD` | `0`-`100` | `40` |
124
+ | `SHELLWARD_BASELINE_PATH` | file path | `~/.openclaw/shellward/mcp-baseline.json` |
131
125
 
132
126
  ### As SDK (any AI agent platform):
133
127
 
@@ -173,7 +167,7 @@ User Input
173
167
 
174
168
 
175
169
  ┌───────────────────┐
176
- │ L4 Input Auditor │ 32 injection rules (18 ZH + 14 EN), risk scoring
170
+ │ L4 Input Auditor │ 37 injection rules (20 ZH + 17 EN), risk scoring
177
171
  └───────────────────┘
178
172
 
179
173
 
@@ -239,6 +233,32 @@ password: "MyP@ssw0rd!" → Detected (Password)
239
233
  330102199001011234 → Detected (Chinese ID Card, checksum validated)
240
234
  ```
241
235
 
236
+ ## OWASP Coverage
237
+
238
+ How ShellWard maps to the **OWASP Top 10 for LLM Applications (2025)** and common **MCP** risks. Honest scope — `✅` covered, `◐` partial, `✗` out of scope.
239
+
240
+ | OWASP LLM Top 10 (2025) | ShellWard | How |
241
+ |---|:--:|---|
242
+ | LLM01 Prompt Injection | ✅ | L1 prompt guard + L4 injection engine (32 rules, hidden-char/tag detection) |
243
+ | LLM02 Sensitive Information Disclosure | ✅ | L2/L6 PII scan + L7 DLP exfiltration blocking |
244
+ | LLM03 Supply Chain | ✅ | `/scan-plugins`, package-install detection, `/check-updates` CVE DB |
245
+ | LLM04 Data & Model Poisoning | ◐ | **MCP tool-poisoning scan + rug-pull detection** (tool-definition layer) |
246
+ | LLM05 Improper Output Handling | ✅ | L6 output scanner + canary-leak detection |
247
+ | LLM06 Excessive Agency | ✅ | L3 tool blocker (payment/transfer), L5 security gate |
248
+ | LLM07 System Prompt Leakage | ✅ | L1 canary token tripwire in responses |
249
+ | LLM08 Vector & Embedding Weaknesses | ✗ | Out of scope (not a RAG/vector tool) |
250
+ | LLM09 Misinformation | ✗ | Out of scope |
251
+ | LLM10 Unbounded Consumption | ◐ | Fork-bomb / resource-exhaustion command blocking |
252
+
253
+ | Common MCP risk | ShellWard | How |
254
+ |---|:--:|---|
255
+ | Tool Poisoning (hidden instructions in tool metadata) | ✅ | `scan_mcp_tool` / `/scan-mcp` |
256
+ | Rug Pull (tool silently redefined after approval) | ✅ | description+schema fingerprint baseline |
257
+ | Data exfiltration via tools | ✅ | L7 outbound guard (email/HTTP/curl/bash) |
258
+ | Command injection via MCP | ✅ | `check_command` (17 dangerous patterns) |
259
+ | Sensitive-file access | ✅ | `check_path` + honeypot tripwires |
260
+ | Tool Shadowing / cross-server escalation | ◐ | Per-tool scan; cross-server graph analysis not yet |
261
+
242
262
  ## Configuration
243
263
 
244
264
  ```json
@@ -249,7 +269,30 @@ password: "MyP@ssw0rd!" → Detected (Password)
249
269
  |--------|--------|---------|-------------|
250
270
  | `mode` | `enforce` / `audit` | `enforce` | Block + log, or log only |
251
271
  | `locale` | `auto` / `zh` / `en` | `auto` | Auto-detects from system LANG |
252
- | `injectionThreshold` | `0`-`100` | `60` | Risk score threshold for injection detection |
272
+ | `injectionThreshold` | `0`-`100` | `40` | Risk score threshold (lower = stricter; calibrated via bench/) |
273
+
274
+ ### Custom Rules (SDK)
275
+
276
+ Extend the built-in rules without forking — every field is additive, except `allowedTools` which always wins:
277
+
278
+ ```typescript
279
+ const guard = new ShellWard({
280
+ customRules: {
281
+ blockedTools: ['internal_payout', 'wire_transfer'], // add to the block policy
282
+ allowedTools: ['payment'], // trust a tool (overrides built-in block)
283
+ sensitivePatterns: [ // org-specific PII / secrets
284
+ { id: 'emp_id', name: 'Employee ID', pattern: 'EMP-\\d{6}' },
285
+ ],
286
+ dangerousCommands: [ // extra command blocklist
287
+ { id: 'no_shutdown', pattern: 'shutdown\\s+-h', description: 'Power-off' },
288
+ ],
289
+ honeypotPaths: ['secret_vault\\.dat$'], // extra honeypot tripwires
290
+ injectionRules: [/* custom InjectionRule[] */],
291
+ },
292
+ })
293
+ ```
294
+
295
+ Invalid regexes are skipped (never throws), so user input can't break the guard.
253
296
 
254
297
  ## Commands (OpenClaw)
255
298
 
@@ -259,6 +302,7 @@ password: "MyP@ssw0rd!" → Detected (Password)
259
302
  | `/audit [n] [filter]` | View audit log (filter: block, audit, critical, high) |
260
303
  | `/harden` | Scan & fix security issues |
261
304
  | `/scan-plugins` | Scan installed plugins for malicious code |
305
+ | `/scan-mcp` | Scan configured MCP servers (stdio + remote HTTP) for tool poisoning + rug-pull |
262
306
  | `/check-updates` | Check versions & known CVEs (17 built-in) |
263
307
 
264
308
  ## Performance
@@ -269,7 +313,24 @@ password: "MyP@ssw0rd!" → Detected (Password)
269
313
  | Command check throughput | 125,000/sec |
270
314
  | Injection detection throughput | ~7,700/sec |
271
315
  | Dependencies | 0 |
272
- | Tests | 123 passing (incl. 11 MCP) |
316
+ | Tests | 183 passing (incl. 15 MCP + 12 ReDoS + live tool-poisoning scan) |
317
+
318
+ ## Detection Benchmark
319
+
320
+ Effectiveness is measured, not asserted. `npm run bench` runs every detector over a labeled corpus (attacks **and** hard negatives — benign text that looks suspicious) and reports precision/recall/F1. The corpus and harness live in [`bench/`](./bench); CI fails on regression.
321
+
322
+ | Category | Precision | Recall | F1 |
323
+ |----------|:---------:|:------:|:--:|
324
+ | Prompt injection | 100% | 100% | 100% |
325
+ | Dangerous commands | 100% | 100% | 100% |
326
+ | PII / secrets | 100% | 100% | 100% |
327
+ | MCP tool poisoning | 100% | 100% | 100% |
328
+
329
+ 83 gated samples (attacks + hard negatives). Zero-width-interleaved and empty-quote (`r''m`) obfuscation are normalized before matching. The corpus also tracks **5 documented bypasses** (leetspeak, base64, non-zh/en languages, shell variable indirection) that regex/heuristics are not expected to catch — listed explicitly and excluded from the gate rather than hidden.
330
+
331
+ > Numbers are on the current in-repo corpus — a floor, not a universal guarantee. Found a bypass? Add it to `bench/corpus.ts` as a labeled row and the gap becomes measurable (and CI-enforced).
332
+ >
333
+ > **Conservative by design:** in enforce mode ShellWard fails safe — e.g. `echo "rm -rf /"` (printing a literal) is flagged, since regex can't distinguish it from `echo "$(rm -rf /)"` (which executes).
273
334
 
274
335
  ## Vulnerability Database
275
336
 
@@ -297,7 +358,7 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
297
358
  | **Zero dependencies** | ✅ (npm) | ✅ | Go binary | Cloud API | Python |
298
359
  | **Runtime blocking** | ✅ | ✅ | ✅ (proxy) | ✅ | ❌ (scanner) |
299
360
  | **Architecture** | In-process middleware | Hook-based guard | HTTP proxy | Hook + cloud | Scan + monitor |
300
- | **Detection rules** | 32 | 24 | 36 DLP patterns | 200+ YAML | 191+ |
361
+ | **Detection rules** | 37 | 24 | 36 DLP patterns | 200+ YAML | 191+ |
301
362
 
302
363
  > ShellWard is the only tool with **DLP-style data flow tracking** + **Chinese language security** + **zero dependencies** in a single package.
303
364
  >
@@ -323,13 +384,14 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
323
384
 
324
385
  | 平台 | 集成方式 | 说明 |
325
386
  |------|---------|------|
326
- | **Claude Desktop** | MCP 服务器 | 添加到 `claude_desktop_config.json`,7 个安全工具 |
387
+ | **Claude Desktop** | MCP 服务器 | 添加到 `claude_desktop_config.json`,8 个安全工具 |
327
388
  | **Cursor** | MCP 服务器 | 添加到 `.cursor/mcp.json` |
328
389
  | **OpenClaw** | MCP + 插件 + SDK | `openclaw plugins install shellward`,开箱即用 |
329
390
  | **Claude Code** | MCP + SDK | Anthropic 官方 CLI Agent |
330
391
  | **LangChain** | SDK | LLM 应用开发框架 |
331
392
  | **AutoGPT** | SDK | 自主 AI Agent |
332
393
  | **OpenAI Agents** | SDK | GPT Agent 平台 |
394
+ | **Hermes Agent** | MCP 服务器 | Nous Research 自改进 Agent — 通过 MCP Integration 接入 |
333
395
  | **Dify / Coze** | SDK | 低代码 AI 平台 |
334
396
  | **任意 MCP 客户端** | MCP 服务器 | stdio JSON-RPC,零依赖 |
335
397
  | **任意 AI Agent** | SDK | `npm install shellward`,3 行代码接入 |
@@ -338,20 +400,22 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
338
400
 
339
401
  **MCP 服务器模式(推荐):**
340
402
 
341
- 在 MCP 配置中添加(适用于 Claude Desktop、Cursor、OpenClaw 等):
403
+ 在 MCP 配置中添加(适用于 Claude Desktop、Cursor、OpenClaw 等)。无需本地路径,`npx` 会拉取已发布的 `shellward-mcp`:
342
404
 
343
405
  ```json
344
406
  {
345
407
  "mcpServers": {
346
408
  "shellward": {
347
409
  "command": "npx",
348
- "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
410
+ "args": ["-y", "-p", "shellward", "shellward-mcp"]
349
411
  }
350
412
  }
351
413
  }
352
414
  ```
353
415
 
354
- 零依赖,原生实现 MCP 协议。提供 7 个安全工具:命令检查、注入检测、敏感数据扫描、路径保护、工具策略、响应审计、安全状态。
416
+ 若已全局安装(`npm i -g shellward`),直接用 `"command": "shellward-mcp"` 即可。
417
+
418
+ 零依赖,原生实现 MCP 协议。提供 8 个安全工具:命令检查、注入检测、敏感数据扫描、路径保护、工具策略、响应审计、**MCP 工具投毒/rug-pull 扫描**、安全状态。
355
419
 
356
420
  **OpenClaw 插件模式:**
357
421
 
@@ -380,6 +444,8 @@ guard.checkOutbound('send_email', {...}) // → { allowed: false } (读过敏
380
444
  - **DLP 模型**:数据完整返回(不脱敏),外部发送才拦截 — 用户体验零影响
381
445
  - **中文 PII**:身份证号(GB 11643 校验位)、手机号(全运营商)、银行卡号(Luhn 校验)
382
446
  - **中文注入检测**:18 条中文规则 + 14 条英文规则,支持中英混合攻击检测
447
+ - **MCP 工具投毒扫描**:检测工具描述/参数里的隐藏指令、不可见字符、"对用户隐瞒" 类隐蔽指令、敏感文件访问与外泄提示
448
+ - **MCP rug-pull 检测**:首次见到工具时记录描述指纹,后续被偷改即告警(`/scan-mcp` 一键扫描已配置 MCP 服务器)
383
449
  - **数据外泄链**:读敏感数据 → send_email / HTTP POST / curl 外发 = 拦截
384
450
  - **零依赖**、零配置、Apache-2.0
385
451
 
@@ -394,12 +460,31 @@ guard.checkOutbound('send_email', {...}) // → { allowed: false } (读过敏
394
460
  | **零依赖** | ✅ (npm) | ✅ | Go 二进制 | 需云 API | 需 Python |
395
461
  | **运行时拦截** | ✅ | ✅ | ✅ (proxy) | ✅ | ❌ (扫描器) |
396
462
  | **架构** | 进程内中间件 | Hook 守护 | HTTP 代理 | Hook + 云端 | 扫描 + 监控 |
397
- | **检测规则数** | 32 | 24 | 36 DLP 模式 | 200+ YAML | 191+ |
463
+ | **检测规则数** | 37 | 24 | 36 DLP 模式 | 200+ YAML | 191+ |
398
464
 
399
465
  > ShellWard 是唯一同时具备 **DLP 数据流追踪** + **中文语言安全** + **零依赖** 的 AI Agent 安全工具。
400
466
  >
401
467
  > 最新研究 ([arXiv:2603.08665](https://arxiv.org/abs/2603.08665)) 显示 GenAI 在 7 小时内发现 38 个真实漏洞 — AI 驱动的攻击正在规模化,防御必须内建到 Agent 层。
402
468
 
469
+ ### 交流 · Community
470
+
471
+ 微信公众号 **「AI不止语」**(微信搜索 `AI_BuZhiYu`)— 技术问答 · 项目更新 · 实战文章
472
+
473
+ | 渠道 | 加入方式 |
474
+ |------|---------|
475
+ | QQ 群 | [点击加入](https://qm.qq.com/q/EeNQA9xCxy)(群号 1071280067) |
476
+ | 微信群 | 关注公众号后回复「群」获取入群方式 |
477
+
478
+ ### 姊妹项目
479
+
480
+ | 项目 | 说明 |
481
+ |------|------|
482
+ | [ai-coding-guide](https://github.com/jnMetaCode/ai-coding-guide) | AI 编程工具实战指南 — 66 个 Claude Code 技巧 + 9 款工具最佳实践 + 可复制配置模板 |
483
+ | [agency-agents-zh](https://github.com/jnMetaCode/agency-agents-zh) | 187 个专业角色,让 AI 变成安全工程师、DBA、产品经理等 |
484
+ | [agency-orchestrator](https://github.com/jnMetaCode/agency-orchestrator) | 多智能体编排引擎 — 用 YAML 编排 187 个角色协作,支持 DeepSeek/Claude/OpenAI/Ollama,零代码 |
485
+ | [superpowers-zh](https://github.com/jnMetaCode/superpowers-zh) | AI 编程超能力 · 中文版 — 20 个 skills,让你的 AI 编程助手真正会干活 |
486
+ | 🆕 [ai-shortfilm-prompts](https://github.com/jnMetaCode/ai-shortfilm-prompts) | AI 短片提示词方法论 — Mx-Shell《丧尸清道夫》5 段式拆解 + Skill,Seedance / 小云雀 / Sora / 可灵 / 即梦通用 |
487
+
403
488
  ### 作者
404
489
 
405
490
  [jnMetaCode](https://github.com/jnMetaCode) · Apache-2.0
@@ -12,6 +12,7 @@ export interface AutoCheckResult {
12
12
  config: string;
13
13
  risk: string;
14
14
  }[];
15
+ mcpServerCount: number;
15
16
  rootWarning: boolean;
16
17
  }
17
18
  /**
@@ -5,6 +5,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs';
5
5
  import { join } from 'path';
6
6
  import { getHomeDir } from './utils.js';
7
7
  import { fetchVulnDB, compareVersions } from './update-check.js';
8
+ import { discoverMcpServers } from './mcp-client.js';
8
9
  const OPENCLAW_DIR = join(getHomeDir(), '.openclaw');
9
10
  const SUSPICIOUS_PATTERNS = [
10
11
  { pattern: /eval\s*\(/, name: 'eval()' },
@@ -125,7 +126,12 @@ export async function runAutoCheck(locale = 'en') {
125
126
  Promise.resolve(scanMcpConfig()),
126
127
  ]);
127
128
  const rootWarning = typeof process.getuid === 'function' && process.getuid() === 0;
128
- return { openclawVulns, pluginRisks, mcpRisks, rootWarning };
129
+ let mcpServerCount = 0;
130
+ try {
131
+ mcpServerCount = discoverMcpServers().filter(s => s.transport === 'stdio').length;
132
+ }
133
+ catch { /* ignore */ }
134
+ return { openclawVulns, pluginRisks, mcpRisks, mcpServerCount, rootWarning };
129
135
  }
130
136
  /**
131
137
  * 启动时执行检查,发现问题时通过 logger 告警
@@ -160,6 +166,11 @@ export function runAutoCheckOnStartup(logger, locale) {
160
166
  if (result.rootWarning) {
161
167
  lines.push(zh ? '⚠️ 正在以 root 运行,建议使用普通用户' : '⚠️ Running as root, consider using non-root user');
162
168
  }
169
+ if (result.mcpServerCount > 0) {
170
+ lines.push(zh
171
+ ? `🔌 检测到 ${result.mcpServerCount} 个 MCP 服务器 — 运行 /scan-mcp 检测工具投毒与 rug-pull`
172
+ : `🔌 ${result.mcpServerCount} MCP server(s) configured — run /scan-mcp to check for tool poisoning & rug-pulls`);
173
+ }
163
174
  if (lines.length > 0) {
164
175
  logger.warn((zh ? '[ShellWard] 自动安全检查:\n' : '[ShellWard] Auto security check:\n') + lines.join('\n'));
165
176
  }
@@ -1,2 +1,3 @@
1
1
  import type { ShellWardConfig } from '../types.js';
2
- export declare function registerAllCommands(api: any, config: ShellWardConfig): void;
2
+ /** @returns number of registered commands (for the startup log). */
3
+ export declare function registerAllCommands(api: any, config: ShellWardConfig): number;
@@ -4,8 +4,10 @@ import { registerSecurityCommand } from './security.js';
4
4
  import { registerAuditCommand } from './audit.js';
5
5
  import { registerHardenCommand } from './harden.js';
6
6
  import { registerScanPluginsCommand } from './scan-plugins.js';
7
+ import { registerScanMcpCommand } from './scan-mcp.js';
7
8
  import { registerCheckUpdatesCommand } from './check-updates.js';
8
9
  import { registerUpgradeOpenClawCommand } from './upgrade-openclaw.js';
10
+ /** @returns number of registered commands (for the startup log). */
9
11
  export function registerAllCommands(api, config) {
10
12
  const locale = resolveLocale(config);
11
13
  // Register individual commands
@@ -13,6 +15,7 @@ export function registerAllCommands(api, config) {
13
15
  registerAuditCommand(api, config);
14
16
  registerHardenCommand(api, config);
15
17
  registerScanPluginsCommand(api, config);
18
+ registerScanMcpCommand(api, config);
16
19
  registerCheckUpdatesCommand(api, config);
17
20
  registerUpgradeOpenClawCommand(api, config);
18
21
  // Register /cg shortcut with help
@@ -31,6 +34,7 @@ export function registerAllCommands(api, config) {
31
34
  | \`/audit [数量] [过滤]\` | 查看审计日志 (过滤: block/audit/critical/high) |
32
35
  | \`/harden\` | 安全扫描 · \`/harden fix\` 自动修复权限 |
33
36
  | \`/scan-plugins\` | 扫描已安装插件的安全风险 |
37
+ | \`/scan-mcp\` | 扫描已配置 MCP 服务器(工具投毒 + rug-pull) |
34
38
  | \`/check-updates\` | 检查 OpenClaw 版本和已知漏洞 |
35
39
  | \`/upgrade-openclaw\` | 一键升级 OpenClaw · \`/upgrade-openclaw yes\` 直接执行 |
36
40
 
@@ -45,6 +49,7 @@ L5 安全门 · L6 回复审计 · L7 数据流监控 · L8 会话安全`
45
49
  | \`/audit [count] [filter]\` | View audit log (filter: block/audit/critical/high) |
46
50
  | \`/harden\` | Security scan · \`/harden fix\` to auto-fix permissions |
47
51
  | \`/scan-plugins\` | Scan installed plugins for security risks |
52
+ | \`/scan-mcp\` | Scan configured MCP servers (tool poisoning + rug-pull) |
48
53
  | \`/check-updates\` | Check OpenClaw version and known vulnerabilities |
49
54
  | \`/upgrade-openclaw\` | Upgrade OpenClaw · \`/upgrade-openclaw yes\` to execute |
50
55
 
@@ -53,4 +58,6 @@ L1 Prompt Guard · L2 Output Scanner · L3 Tool Blocker · L4 Input Auditor
53
58
  L5 Security Gate · L6 Outbound Guard · L7 Data Flow Guard · L8 Session Guard`,
54
59
  }),
55
60
  });
61
+ // 7 individual commands + /cg help
62
+ return 8;
56
63
  }
@@ -0,0 +1,2 @@
1
+ import type { ShellWardConfig } from '../types.js';
2
+ export declare function registerScanMcpCommand(api: any, config: ShellWardConfig): void;
@@ -0,0 +1,105 @@
1
+ // src/commands/scan-mcp.ts — /scan-mcp: connect to configured MCP servers and
2
+ // scan their tool definitions for poisoning + rug-pulls.
3
+ //
4
+ // Safety model (mirrors Snyk agent-scan): scanning spawns the configured stdio
5
+ // servers, so it is an explicit user action — never auto-run at startup.
6
+ import { ShellWard } from '../core/engine.js';
7
+ import { McpBaseline } from '../mcp-baseline.js';
8
+ import { discoverMcpServers, listToolsStdio, listToolsHttp } from '../mcp-client.js';
9
+ import { resolveLocale } from '../types.js';
10
+ export function registerScanMcpCommand(api, config) {
11
+ const locale = resolveLocale(config);
12
+ api.registerCommand({
13
+ name: 'scan-mcp',
14
+ description: locale === 'zh'
15
+ ? '🔌 扫描已配置的 MCP 服务器(工具投毒 + rug-pull 检测)'
16
+ : '🔌 Scan configured MCP servers (tool poisoning + rug-pull)',
17
+ acceptsArgs: false,
18
+ handler: async () => {
19
+ const zh = locale === 'zh';
20
+ const guard = new ShellWard(config);
21
+ const baseline = new McpBaseline();
22
+ const lines = [];
23
+ lines.push(zh ? '🔌 **MCP 服务器安全扫描**' : '🔌 **MCP Server Security Scan**');
24
+ lines.push('');
25
+ const servers = discoverMcpServers();
26
+ if (servers.length === 0) {
27
+ lines.push(zh
28
+ ? 'ℹ️ 未发现已配置的 MCP 服务器(检查 ~/.openclaw/mcp.json 等)。'
29
+ : 'ℹ️ No configured MCP servers found (checked ~/.openclaw/mcp.json etc).');
30
+ return { text: lines.join('\n') };
31
+ }
32
+ const stdioServers = servers.filter(s => s.transport === 'stdio');
33
+ const remoteServers = servers.filter(s => s.transport === 'remote');
34
+ lines.push(zh
35
+ ? `发现 ${servers.length} 个服务器(${stdioServers.length} 个 stdio,${remoteServers.length} 个远程)`
36
+ : `Found ${servers.length} servers (${stdioServers.length} stdio, ${remoteServers.length} remote)`);
37
+ lines.push('');
38
+ let totalTools = 0;
39
+ let poisoned = 0;
40
+ let rugPulls = 0;
41
+ let unreachable = 0;
42
+ for (const server of servers) {
43
+ let tools;
44
+ try {
45
+ tools = server.transport === 'remote'
46
+ ? await listToolsHttp(server)
47
+ : await listToolsStdio(server);
48
+ }
49
+ catch (e) {
50
+ unreachable++;
51
+ const where = server.transport === 'remote' ? server.url || 'remote' : 'stdio';
52
+ lines.push(zh
53
+ ? `### ⚠️ ${server.name} (${where}) — 无法连接 (${e?.message || 'error'})`
54
+ : `### ⚠️ ${server.name} (${where}) — unreachable (${e?.message || 'error'})`);
55
+ lines.push('');
56
+ continue;
57
+ }
58
+ const serverIssues = [];
59
+ for (const tool of tools) {
60
+ totalTools++;
61
+ const scan = guard.scanToolDefinition(tool);
62
+ const rp = baseline.record(McpBaseline.keyFor(server.name, tool.name), tool);
63
+ if (!scan.safe) {
64
+ poisoned++;
65
+ serverIssues.push(zh
66
+ ? ` 🔴 \`${tool.name}\` 工具投毒 (评分 ${scan.score}): ${scan.findings.map(f => f.name).join('; ')}`
67
+ : ` 🔴 \`${tool.name}\` poisoned (score ${scan.score}): ${scan.findings.map(f => f.name).join('; ')}`);
68
+ }
69
+ if (rp.status === 'changed') {
70
+ rugPulls++;
71
+ serverIssues.push(zh
72
+ ? ` 🟠 \`${tool.name}\` 描述自上次以来被修改 (rug-pull 嫌疑)`
73
+ : ` 🟠 \`${tool.name}\` description changed since last seen (possible rug-pull)`);
74
+ }
75
+ }
76
+ const icon = serverIssues.length > 0 ? '🔴' : '✅';
77
+ const tag = server.transport === 'remote' ? ' (remote)' : '';
78
+ lines.push(`### ${icon} ${server.name}${tag}`);
79
+ lines.push(zh ? ` ${tools.length} 个工具` : ` ${tools.length} tools`);
80
+ if (serverIssues.length === 0) {
81
+ lines.push(zh ? ' ✅ 未发现问题' : ' ✅ No issues found');
82
+ }
83
+ else {
84
+ lines.push(...serverIssues);
85
+ }
86
+ lines.push('');
87
+ }
88
+ baseline.save();
89
+ // Summary
90
+ lines.push('---');
91
+ lines.push(zh
92
+ ? `扫描了 ${totalTools} 个工具 · 🔴 投毒 ${poisoned} · 🟠 rug-pull ${rugPulls} · ⚠️ 无法连接 ${unreachable}`
93
+ : `Scanned ${totalTools} tools · 🔴 poisoned ${poisoned} · 🟠 rug-pull ${rugPulls} · ⚠️ unreachable ${unreachable}`);
94
+ if (poisoned === 0 && rugPulls === 0) {
95
+ lines.push(zh ? '✅ **所有 MCP 工具通过扫描**' : '✅ **All MCP tools passed**');
96
+ }
97
+ else {
98
+ lines.push(zh
99
+ ? '⚠️ **发现可疑 MCP 工具 — 请审查或移除对应服务器**'
100
+ : '⚠️ **Suspicious MCP tools found — review or remove the server**');
101
+ }
102
+ return { text: lines.join('\n') };
103
+ },
104
+ });
105
+ }
@@ -30,12 +30,40 @@ export interface ResponseCheckResult {
30
30
  canaryLeak: boolean;
31
31
  sensitiveData: ScanResult;
32
32
  }
33
+ /** Shape of an MCP tool definition (subset of the spec we inspect). */
34
+ export interface McpToolDefinition {
35
+ name: string;
36
+ description?: string;
37
+ inputSchema?: Record<string, any>;
38
+ }
39
+ export interface ToolPoisoningFinding {
40
+ id: string;
41
+ name: string;
42
+ category: string;
43
+ score: number;
44
+ source: 'description' | 'parameter' | 'hidden_chars';
45
+ }
46
+ export interface ToolPoisoningResult {
47
+ toolName: string;
48
+ safe: boolean;
49
+ score: number;
50
+ threshold: number;
51
+ findings: ToolPoisoningFinding[];
52
+ hiddenChars: number;
53
+ }
33
54
  export declare class ShellWard {
34
55
  readonly config: ShellWardConfig;
35
56
  readonly locale: ResolvedLocale;
36
57
  readonly log: AuditLog;
37
58
  private _canaryToken;
38
59
  private compiledRules;
60
+ private readonly blockedTools;
61
+ private readonly allowedTools;
62
+ private readonly sensitiveTools;
63
+ private readonly outboundTools;
64
+ private readonly honeypots;
65
+ private readonly customSensitive;
66
+ private readonly customDangerous;
39
67
  private sensitiveReads;
40
68
  private readonly TRACKING_WINDOW_MS;
41
69
  private readonly MAX_TRACKED_READS;
@@ -51,6 +79,13 @@ export declare class ShellWard {
51
79
  threshold?: number;
52
80
  }): InjectionResult;
53
81
  getInjectionThreshold(toolName?: string): number;
82
+ scanToolDefinition(tool: McpToolDefinition, options?: {
83
+ threshold?: number;
84
+ }): ToolPoisoningResult;
85
+ /** Scan a list of MCP tool definitions; returns only the unsafe ones. */
86
+ scanToolDefinitions(tools: McpToolDefinition[], options?: {
87
+ threshold?: number;
88
+ }): ToolPoisoningResult[];
54
89
  checkAction(action: string, details: string): CheckResult;
55
90
  checkResponse(content: string): ResponseCheckResult;
56
91
  markSensitiveData(toolName: string, summary: string): void;