shellward 0.5.16 → 0.6.1

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 +95 -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 +255 -33
  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 +4 -2
  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 +273 -34
  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,7 +54,7 @@ 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 |
@@ -70,8 +70,10 @@ Your AI agent has full access to tools — shell, email, HTTP, file system. One
70
70
 
71
71
  - **8 defense layers**: prompt guard, input auditor, tool blocker, output scanner, security gate, outbound guard, data flow guard, session guard
72
72
  - **DLP model**: data returns in full (no redaction), outbound sends are blocked when PII was recently accessed
73
- - **PII detection**: SSN, credit cards, API keys (OpenAI/GitHub/AWS), JWT, passwords — plus Chinese ID card (GB 11643 checksum), phone, bank card (Luhn)
74
- - **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
75
77
  - **Data exfiltration chain**: read sensitive data → send email / HTTP POST / curl = blocked
76
78
  - **Bash bypass detection**: catches `curl -X POST`, `wget --post`, `nc`, Python/Node network exfil
77
79
  - **Zero dependencies**, zero config, Apache-2.0
@@ -84,42 +86,32 @@ ShellWard runs as a standalone MCP server over stdio — zero dependencies, no `
84
86
 
85
87
  **Claude Desktop / Cursor / any MCP client:**
86
88
 
87
- 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:
88
90
 
89
91
  ```json
90
92
  {
91
93
  "mcpServers": {
92
94
  "shellward": {
93
95
  "command": "npx",
94
- "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
96
+ "args": ["-y", "-p", "shellward", "shellward-mcp"]
95
97
  }
96
98
  }
97
99
  }
98
100
  ```
99
101
 
100
- **OpenClaw:**
102
+ If installed globally (`npm i -g shellward`), simply use `"command": "shellward-mcp"`.
101
103
 
102
- ```json
103
- {
104
- "mcpServers": {
105
- "shellward": {
106
- "command": "npx",
107
- "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
108
- }
109
- }
110
- }
111
- ```
112
-
113
- **7 MCP tools available:**
104
+ **8 MCP tools available:**
114
105
 
115
106
  | Tool | Description |
116
107
  |------|-------------|
117
108
  | `check_command` | Check if a shell command is safe (rm -rf, reverse shell, fork bomb...) |
118
- | `check_injection` | Detect prompt injection in text (32+ rules, zh+en) |
109
+ | `check_injection` | Detect prompt injection in text (37+ rules, zh+en) |
119
110
  | `scan_data` | Scan for PII & sensitive data (CN ID/phone/bank, API keys, SSN...) |
120
111
  | `check_path` | Check if file path operation is safe (.env, .ssh, credentials...) |
121
112
  | `check_tool` | Check if tool name is allowed (blocks payment/transfer tools) |
122
113
  | `check_response` | Audit AI response for canary leaks & PII exposure |
114
+ | `scan_mcp_tool` | Scan an MCP tool definition for poisoning + rug-pull |
123
115
  | `security_status` | Get current security config & active layers |
124
116
 
125
117
  **Environment variables:**
@@ -128,7 +120,8 @@ Add to your MCP config (`claude_desktop_config.json`, `.cursor/mcp.json`, etc.):
128
120
  |----------|--------|---------|
129
121
  | `SHELLWARD_MODE` | `enforce` / `audit` | `enforce` |
130
122
  | `SHELLWARD_LOCALE` | `auto` / `zh` / `en` | `auto` |
131
- | `SHELLWARD_THRESHOLD` | `0`-`100` | `60` |
123
+ | `SHELLWARD_THRESHOLD` | `0`-`100` | `40` |
124
+ | `SHELLWARD_BASELINE_PATH` | file path | `~/.openclaw/shellward/mcp-baseline.json` |
132
125
 
133
126
  ### As SDK (any AI agent platform):
134
127
 
@@ -174,7 +167,7 @@ User Input
174
167
 
175
168
 
176
169
  ┌───────────────────┐
177
- │ 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
178
171
  └───────────────────┘
179
172
 
180
173
 
@@ -240,6 +233,32 @@ password: "MyP@ssw0rd!" → Detected (Password)
240
233
  330102199001011234 → Detected (Chinese ID Card, checksum validated)
241
234
  ```
242
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
+
243
262
  ## Configuration
244
263
 
245
264
  ```json
@@ -250,7 +269,30 @@ password: "MyP@ssw0rd!" → Detected (Password)
250
269
  |--------|--------|---------|-------------|
251
270
  | `mode` | `enforce` / `audit` | `enforce` | Block + log, or log only |
252
271
  | `locale` | `auto` / `zh` / `en` | `auto` | Auto-detects from system LANG |
253
- | `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.
254
296
 
255
297
  ## Commands (OpenClaw)
256
298
 
@@ -260,6 +302,7 @@ password: "MyP@ssw0rd!" → Detected (Password)
260
302
  | `/audit [n] [filter]` | View audit log (filter: block, audit, critical, high) |
261
303
  | `/harden` | Scan & fix security issues |
262
304
  | `/scan-plugins` | Scan installed plugins for malicious code |
305
+ | `/scan-mcp` | Scan configured MCP servers (stdio + remote HTTP) for tool poisoning + rug-pull |
263
306
  | `/check-updates` | Check versions & known CVEs (17 built-in) |
264
307
 
265
308
  ## Performance
@@ -270,7 +313,24 @@ password: "MyP@ssw0rd!" → Detected (Password)
270
313
  | Command check throughput | 125,000/sec |
271
314
  | Injection detection throughput | ~7,700/sec |
272
315
  | Dependencies | 0 |
273
- | 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).
274
334
 
275
335
  ## Vulnerability Database
276
336
 
@@ -298,7 +358,7 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
298
358
  | **Zero dependencies** | ✅ (npm) | ✅ | Go binary | Cloud API | Python |
299
359
  | **Runtime blocking** | ✅ | ✅ | ✅ (proxy) | ✅ | ❌ (scanner) |
300
360
  | **Architecture** | In-process middleware | Hook-based guard | HTTP proxy | Hook + cloud | Scan + monitor |
301
- | **Detection rules** | 32 | 24 | 36 DLP patterns | 200+ YAML | 191+ |
361
+ | **Detection rules** | 37 | 24 | 36 DLP patterns | 200+ YAML | 191+ |
302
362
 
303
363
  > ShellWard is the only tool with **DLP-style data flow tracking** + **Chinese language security** + **zero dependencies** in a single package.
304
364
  >
@@ -324,7 +384,7 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
324
384
 
325
385
  | 平台 | 集成方式 | 说明 |
326
386
  |------|---------|------|
327
- | **Claude Desktop** | MCP 服务器 | 添加到 `claude_desktop_config.json`,7 个安全工具 |
387
+ | **Claude Desktop** | MCP 服务器 | 添加到 `claude_desktop_config.json`,8 个安全工具 |
328
388
  | **Cursor** | MCP 服务器 | 添加到 `.cursor/mcp.json` |
329
389
  | **OpenClaw** | MCP + 插件 + SDK | `openclaw plugins install shellward`,开箱即用 |
330
390
  | **Claude Code** | MCP + SDK | Anthropic 官方 CLI Agent |
@@ -340,20 +400,22 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
340
400
 
341
401
  **MCP 服务器模式(推荐):**
342
402
 
343
- 在 MCP 配置中添加(适用于 Claude Desktop、Cursor、OpenClaw 等):
403
+ 在 MCP 配置中添加(适用于 Claude Desktop、Cursor、OpenClaw 等)。无需本地路径,`npx` 会拉取已发布的 `shellward-mcp`:
344
404
 
345
405
  ```json
346
406
  {
347
407
  "mcpServers": {
348
408
  "shellward": {
349
409
  "command": "npx",
350
- "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
410
+ "args": ["-y", "-p", "shellward", "shellward-mcp"]
351
411
  }
352
412
  }
353
413
  }
354
414
  ```
355
415
 
356
- 零依赖,原生实现 MCP 协议。提供 7 个安全工具:命令检查、注入检测、敏感数据扫描、路径保护、工具策略、响应审计、安全状态。
416
+ 若已全局安装(`npm i -g shellward`),直接用 `"command": "shellward-mcp"` 即可。
417
+
418
+ 零依赖,原生实现 MCP 协议。提供 8 个安全工具:命令检查、注入检测、敏感数据扫描、路径保护、工具策略、响应审计、**MCP 工具投毒/rug-pull 扫描**、安全状态。
357
419
 
358
420
  **OpenClaw 插件模式:**
359
421
 
@@ -382,6 +444,8 @@ guard.checkOutbound('send_email', {...}) // → { allowed: false } (读过敏
382
444
  - **DLP 模型**:数据完整返回(不脱敏),外部发送才拦截 — 用户体验零影响
383
445
  - **中文 PII**:身份证号(GB 11643 校验位)、手机号(全运营商)、银行卡号(Luhn 校验)
384
446
  - **中文注入检测**:18 条中文规则 + 14 条英文规则,支持中英混合攻击检测
447
+ - **MCP 工具投毒扫描**:检测工具描述/参数里的隐藏指令、不可见字符、"对用户隐瞒" 类隐蔽指令、敏感文件访问与外泄提示
448
+ - **MCP rug-pull 检测**:首次见到工具时记录描述指纹,后续被偷改即告警(`/scan-mcp` 一键扫描已配置 MCP 服务器)
385
449
  - **数据外泄链**:读敏感数据 → send_email / HTTP POST / curl 外发 = 拦截
386
450
  - **零依赖**、零配置、Apache-2.0
387
451
 
@@ -396,7 +460,7 @@ guard.checkOutbound('send_email', {...}) // → { allowed: false } (读过敏
396
460
  | **零依赖** | ✅ (npm) | ✅ | Go 二进制 | 需云 API | 需 Python |
397
461
  | **运行时拦截** | ✅ | ✅ | ✅ (proxy) | ✅ | ❌ (扫描器) |
398
462
  | **架构** | 进程内中间件 | Hook 守护 | HTTP 代理 | Hook + 云端 | 扫描 + 监控 |
399
- | **检测规则数** | 32 | 24 | 36 DLP 模式 | 200+ YAML | 191+ |
463
+ | **检测规则数** | 37 | 24 | 36 DLP 模式 | 200+ YAML | 191+ |
400
464
 
401
465
  > ShellWard 是唯一同时具备 **DLP 数据流追踪** + **中文语言安全** + **零依赖** 的 AI Agent 安全工具。
402
466
  >
@@ -419,6 +483,7 @@ guard.checkOutbound('send_email', {...}) // → { allowed: false } (读过敏
419
483
  | [agency-agents-zh](https://github.com/jnMetaCode/agency-agents-zh) | 187 个专业角色,让 AI 变成安全工程师、DBA、产品经理等 |
420
484
  | [agency-orchestrator](https://github.com/jnMetaCode/agency-orchestrator) | 多智能体编排引擎 — 用 YAML 编排 187 个角色协作,支持 DeepSeek/Claude/OpenAI/Ollama,零代码 |
421
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 / 可灵 / 即梦通用 |
422
487
 
423
488
  ### 作者
424
489
 
@@ -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;