shellward 0.5.10 → 0.5.12
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 +99 -14
- package/dist/audit-log.d.ts +8 -0
- package/dist/audit-log.js +72 -0
- package/dist/auto-check.d.ts +26 -0
- package/dist/auto-check.js +167 -0
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +75 -0
- package/dist/commands/check-updates.d.ts +2 -0
- package/dist/commands/check-updates.js +166 -0
- package/dist/commands/harden.d.ts +2 -0
- package/dist/commands/harden.js +218 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +56 -0
- package/dist/commands/scan-plugins.d.ts +2 -0
- package/dist/commands/scan-plugins.js +186 -0
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/upgrade-openclaw.d.ts +2 -0
- package/dist/commands/upgrade-openclaw.js +54 -0
- package/dist/core/engine.d.ts +66 -0
- package/dist/core/engine.js +572 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +137 -0
- package/dist/layers/data-flow-guard.d.ts +2 -0
- package/dist/layers/data-flow-guard.js +23 -0
- package/dist/layers/input-auditor.d.ts +2 -0
- package/dist/layers/input-auditor.js +33 -0
- package/dist/layers/outbound-guard.d.ts +2 -0
- package/dist/layers/outbound-guard.js +22 -0
- package/dist/layers/output-scanner.d.ts +2 -0
- package/dist/layers/output-scanner.js +16 -0
- package/dist/layers/prompt-guard.d.ts +2 -0
- package/dist/layers/prompt-guard.js +14 -0
- package/dist/layers/security-gate.d.ts +2 -0
- package/dist/layers/security-gate.js +49 -0
- package/dist/layers/session-guard.d.ts +2 -0
- package/dist/layers/session-guard.js +34 -0
- package/dist/layers/tool-blocker.d.ts +2 -0
- package/dist/layers/tool-blocker.js +28 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +337 -0
- package/dist/rules/dangerous-commands.d.ts +8 -0
- package/dist/rules/dangerous-commands.js +113 -0
- package/dist/rules/injection-en.d.ts +2 -0
- package/dist/rules/injection-en.js +115 -0
- package/dist/rules/injection-zh.d.ts +2 -0
- package/dist/rules/injection-zh.js +132 -0
- package/dist/rules/protected-paths.d.ts +2 -0
- package/dist/rules/protected-paths.js +75 -0
- package/dist/rules/sensitive-patterns.d.ts +21 -0
- package/dist/rules/sensitive-patterns.js +192 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.js +30 -0
- package/dist/update-check.d.ts +40 -0
- package/dist/update-check.js +147 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +8 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +22 -6
- package/server.json +44 -0
- package/src/audit-log.ts +8 -4
- package/src/auto-check.ts +2 -2
- package/src/commands/audit.ts +3 -3
- package/src/commands/check-updates.ts +4 -4
- package/src/commands/harden.ts +3 -3
- package/src/commands/index.ts +8 -8
- package/src/commands/scan-plugins.ts +3 -3
- package/src/commands/security.ts +3 -3
- package/src/commands/upgrade-openclaw.ts +2 -2
- package/src/core/engine.ts +8 -8
- package/src/index.ts +15 -15
- package/src/layers/data-flow-guard.ts +1 -1
- package/src/layers/input-auditor.ts +1 -1
- package/src/layers/outbound-guard.ts +1 -1
- package/src/layers/output-scanner.ts +1 -1
- package/src/layers/prompt-guard.ts +1 -1
- package/src/layers/security-gate.ts +1 -1
- package/src/layers/session-guard.ts +1 -1
- package/src/layers/tool-blocker.ts +1 -1
- package/src/mcp-server.ts +386 -0
- package/src/rules/dangerous-commands.ts +1 -1
- package/src/rules/injection-en.ts +1 -1
- package/src/rules/injection-zh.ts +1 -1
- package/src/rules/protected-paths.ts +1 -1
- package/src/rules/sensitive-patterns.ts +1 -1
- package/src/update-check.ts +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.svg" alt="ShellWard Logo" width="160" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# ShellWard
|
|
2
6
|
|
|
3
|
-
**AI Agent Security Middleware** — Protect AI agents from prompt injection, data exfiltration, and dangerous command execution.
|
|
7
|
+
**AI Agent Security Middleware** — Protect AI agents from prompt injection, data exfiltration, and dangerous command execution. ShellWard acts as an LLM security middleware and AI agent firewall, intercepting tool calls at runtime to enforce agent guardrails before damage is done.
|
|
4
8
|
|
|
5
9
|
8-layer defense-in-depth, DLP-style data flow control, zero dependencies. Works as **standalone SDK** or **OpenClaw plugin**.
|
|
6
10
|
|
|
7
11
|
[](https://www.npmjs.com/package/shellward)
|
|
8
12
|
[](./LICENSE)
|
|
9
|
-
[](#performance)
|
|
10
14
|
[](#performance)
|
|
11
15
|
|
|
12
16
|
[English](#demo) | [中文](#中文)
|
|
13
17
|
|
|
14
18
|
## Demo
|
|
15
19
|
|
|
16
|
-

|
|
17
21
|
|
|
18
22
|
> 7 real-world scenarios: server wipe → reverse shell → prompt injection → DLP audit → data exfiltration chain → credential theft → APT attack chain
|
|
19
23
|
|
|
@@ -50,13 +54,15 @@ Your AI agent has full access to tools — shell, email, HTTP, file system. One
|
|
|
50
54
|
|
|
51
55
|
| Platform | Integration | Note |
|
|
52
56
|
|----------|------------|------|
|
|
53
|
-
| **
|
|
54
|
-
| **
|
|
55
|
-
| **
|
|
57
|
+
| **Claude Desktop** | MCP Server | Add to `claude_desktop_config.json` — 7 security tools |
|
|
58
|
+
| **Cursor** | MCP Server | Add to `.cursor/mcp.json` |
|
|
59
|
+
| **OpenClaw** | MCP + Plugin + SDK | `openclaw plugins install shellward` — adapts to available hooks |
|
|
60
|
+
| **Claude Code** | MCP + SDK | Anthropic's official CLI agent |
|
|
56
61
|
| **LangChain** | SDK | LLM application framework |
|
|
57
62
|
| **AutoGPT** | SDK | Autonomous AI agents |
|
|
58
63
|
| **OpenAI Agents** | SDK | GPT agent platform |
|
|
59
64
|
| **Dify / Coze** | SDK | Low-code AI platforms |
|
|
65
|
+
| **Any MCP Client** | MCP Server | stdio JSON-RPC, zero dependencies |
|
|
60
66
|
| **Any AI Agent** | SDK | `npm install shellward` — 3 lines to integrate |
|
|
61
67
|
|
|
62
68
|
## Features
|
|
@@ -71,7 +77,59 @@ Your AI agent has full access to tools — shell, email, HTTP, file system. One
|
|
|
71
77
|
|
|
72
78
|
## Quick Start
|
|
73
79
|
|
|
74
|
-
|
|
80
|
+
### As MCP Server
|
|
81
|
+
|
|
82
|
+
ShellWard runs as a standalone MCP server over stdio — zero dependencies, no `@modelcontextprotocol/sdk` needed.
|
|
83
|
+
|
|
84
|
+
**Claude Desktop / Cursor / any MCP client:**
|
|
85
|
+
|
|
86
|
+
Add to your MCP config (`claude_desktop_config.json`, `.cursor/mcp.json`, etc.):
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"shellward": {
|
|
92
|
+
"command": "npx",
|
|
93
|
+
"args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**OpenClaw:**
|
|
100
|
+
|
|
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:**
|
|
113
|
+
|
|
114
|
+
| Tool | Description |
|
|
115
|
+
|------|-------------|
|
|
116
|
+
| `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) |
|
|
118
|
+
| `scan_data` | Scan for PII & sensitive data (CN ID/phone/bank, API keys, SSN...) |
|
|
119
|
+
| `check_path` | Check if file path operation is safe (.env, .ssh, credentials...) |
|
|
120
|
+
| `check_tool` | Check if tool name is allowed (blocks payment/transfer tools) |
|
|
121
|
+
| `check_response` | Audit AI response for canary leaks & PII exposure |
|
|
122
|
+
| `security_status` | Get current security config & active layers |
|
|
123
|
+
|
|
124
|
+
**Environment variables:**
|
|
125
|
+
|
|
126
|
+
| Variable | Values | Default |
|
|
127
|
+
|----------|--------|---------|
|
|
128
|
+
| `SHELLWARD_MODE` | `enforce` / `audit` | `enforce` |
|
|
129
|
+
| `SHELLWARD_LOCALE` | `auto` / `zh` / `en` | `auto` |
|
|
130
|
+
| `SHELLWARD_THRESHOLD` | `0`-`100` | `60` |
|
|
131
|
+
|
|
132
|
+
### As SDK (any AI agent platform):
|
|
75
133
|
|
|
76
134
|
```bash
|
|
77
135
|
npm install shellward
|
|
@@ -211,7 +269,7 @@ password: "MyP@ssw0rd!" → Detected (Password)
|
|
|
211
269
|
| Command check throughput | 125,000/sec |
|
|
212
270
|
| Injection detection throughput | ~7,700/sec |
|
|
213
271
|
| Dependencies | 0 |
|
|
214
|
-
| Tests |
|
|
272
|
+
| Tests | 123 passing (incl. 11 MCP) |
|
|
215
273
|
|
|
216
274
|
## Vulnerability Database
|
|
217
275
|
|
|
@@ -224,6 +282,10 @@ password: "MyP@ssw0rd!" → Detected (Password)
|
|
|
224
282
|
|
|
225
283
|
Remote vuln DB syncs every 24h, falls back to local DB when offline.
|
|
226
284
|
|
|
285
|
+
## Use Cases
|
|
286
|
+
|
|
287
|
+
ShellWard is built for teams that need runtime security for AI agents — whether you are building autonomous coding assistants, customer-facing chatbots with tool access, or internal automation powered by LLMs. Common use cases include MCP security enforcement, tool call interception and filtering, and adding agent guardrails to any LLM-powered workflow.
|
|
288
|
+
|
|
227
289
|
## Why ShellWard?
|
|
228
290
|
|
|
229
291
|
| Capability | ShellWard | [agentguard](https://github.com/GoPlusSecurity/agentguard) | [pipelock](https://github.com/luckyPipewrench/pipelock) | [Sage](https://github.com/avast/sage) | [AgentSeal](https://github.com/AgentSeal/agentseal) |
|
|
@@ -251,7 +313,7 @@ Remote vuln DB syncs every 24h, falls back to local DB when offline.
|
|
|
251
313
|
|
|
252
314
|
**AI Agent 安全中间件** — 保护 AI 代理免受提示词注入、数据泄露、危险命令执行。8 层纵深防御,零依赖。
|
|
253
315
|
|
|
254
|
-

|
|
255
317
|
|
|
256
318
|
> 7 个真实攻击场景:服务器毁灭拦截 → 反弹 Shell → 注入检测 → DLP 审计 → 数据外泄链 → 凭证窃取 → APT 攻击链
|
|
257
319
|
|
|
@@ -261,22 +323,45 @@ Remote vuln DB syncs every 24h, falls back to local DB when offline.
|
|
|
261
323
|
|
|
262
324
|
| 平台 | 集成方式 | 说明 |
|
|
263
325
|
|------|---------|------|
|
|
264
|
-
| **
|
|
265
|
-
| **
|
|
266
|
-
| **
|
|
326
|
+
| **Claude Desktop** | MCP 服务器 | 添加到 `claude_desktop_config.json`,7 个安全工具 |
|
|
327
|
+
| **Cursor** | MCP 服务器 | 添加到 `.cursor/mcp.json` |
|
|
328
|
+
| **OpenClaw** | MCP + 插件 + SDK | `openclaw plugins install shellward`,开箱即用 |
|
|
329
|
+
| **Claude Code** | MCP + SDK | Anthropic 官方 CLI Agent |
|
|
267
330
|
| **LangChain** | SDK | LLM 应用开发框架 |
|
|
268
331
|
| **AutoGPT** | SDK | 自主 AI Agent |
|
|
269
332
|
| **OpenAI Agents** | SDK | GPT Agent 平台 |
|
|
270
333
|
| **Dify / Coze** | SDK | 低代码 AI 平台 |
|
|
334
|
+
| **任意 MCP 客户端** | MCP 服务器 | stdio JSON-RPC,零依赖 |
|
|
271
335
|
| **任意 AI Agent** | SDK | `npm install shellward`,3 行代码接入 |
|
|
272
336
|
|
|
273
337
|
### 安装
|
|
274
338
|
|
|
339
|
+
**MCP 服务器模式(推荐):**
|
|
340
|
+
|
|
341
|
+
在 MCP 配置中添加(适用于 Claude Desktop、Cursor、OpenClaw 等):
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"mcpServers": {
|
|
346
|
+
"shellward": {
|
|
347
|
+
"command": "npx",
|
|
348
|
+
"args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
零依赖,原生实现 MCP 协议。提供 7 个安全工具:命令检查、注入检测、敏感数据扫描、路径保护、工具策略、响应审计、安全状态。
|
|
355
|
+
|
|
356
|
+
**OpenClaw 插件模式:**
|
|
357
|
+
|
|
275
358
|
```bash
|
|
276
|
-
# OpenClaw 插件
|
|
277
359
|
openclaw plugins install shellward
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**SDK 模式:**
|
|
278
363
|
|
|
279
|
-
|
|
364
|
+
```bash
|
|
280
365
|
npm install shellward
|
|
281
366
|
```
|
|
282
367
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AuditEntry, ShellWardConfig } from './types.js';
|
|
2
|
+
export declare class AuditLog {
|
|
3
|
+
private config;
|
|
4
|
+
private rotating;
|
|
5
|
+
constructor(config: ShellWardConfig);
|
|
6
|
+
write(entry: Pick<AuditEntry, 'level' | 'layer' | 'action' | 'detail'> & Record<string, unknown>): void;
|
|
7
|
+
private rotateIfNeeded;
|
|
8
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/audit-log.ts — JSONL audit log, zero dependencies
|
|
2
|
+
import { appendFileSync, mkdirSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getHomeDir } from './utils.js';
|
|
5
|
+
const LOG_DIR = join(getHomeDir(), '.openclaw', 'shellward');
|
|
6
|
+
const LOG_FILE = join(LOG_DIR, 'audit.jsonl');
|
|
7
|
+
const MAX_SIZE_BYTES = 100 * 1024 * 1024; // 100 MB
|
|
8
|
+
const RISK_SCORES = {
|
|
9
|
+
CRITICAL: 10,
|
|
10
|
+
HIGH: 7,
|
|
11
|
+
MEDIUM: 4,
|
|
12
|
+
LOW: 2,
|
|
13
|
+
INFO: 0,
|
|
14
|
+
};
|
|
15
|
+
export class AuditLog {
|
|
16
|
+
config;
|
|
17
|
+
rotating = false;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
try {
|
|
21
|
+
mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
|
|
22
|
+
// Ensure log file exists with restricted permissions (owner-only)
|
|
23
|
+
try {
|
|
24
|
+
statSync(LOG_FILE);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
writeFileSync(LOG_FILE, '', { mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch { /* directory may already exist */ }
|
|
31
|
+
}
|
|
32
|
+
write(entry) {
|
|
33
|
+
try {
|
|
34
|
+
const record = {
|
|
35
|
+
...entry,
|
|
36
|
+
level: entry.level,
|
|
37
|
+
layer: entry.layer,
|
|
38
|
+
action: entry.action,
|
|
39
|
+
detail: entry.detail,
|
|
40
|
+
ts: new Date().toISOString(),
|
|
41
|
+
mode: this.config.mode,
|
|
42
|
+
riskScore: RISK_SCORES[entry.level] ?? 0,
|
|
43
|
+
};
|
|
44
|
+
appendFileSync(LOG_FILE, JSON.stringify(record) + '\n', { mode: 0o600 });
|
|
45
|
+
this.rotateIfNeeded();
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
// Log failure must not break plugin, but warn via stderr
|
|
49
|
+
try {
|
|
50
|
+
process.stderr.write(`[ShellWard] audit log write failed: ${e?.message}\n`);
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
rotateIfNeeded() {
|
|
56
|
+
if (this.rotating)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
const stat = statSync(LOG_FILE);
|
|
60
|
+
if (stat.size > MAX_SIZE_BYTES) {
|
|
61
|
+
this.rotating = true;
|
|
62
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
63
|
+
renameSync(LOG_FILE, `${LOG_FILE}.${ts}.bak`);
|
|
64
|
+
writeFileSync(LOG_FILE, '', { mode: 0o600 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch { /* ignore */ }
|
|
68
|
+
finally {
|
|
69
|
+
this.rotating = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AutoCheckResult {
|
|
2
|
+
openclawVulns: {
|
|
3
|
+
id: string;
|
|
4
|
+
severity: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}[];
|
|
7
|
+
pluginRisks: {
|
|
8
|
+
plugin: string;
|
|
9
|
+
risk: string;
|
|
10
|
+
}[];
|
|
11
|
+
mcpRisks: {
|
|
12
|
+
config: string;
|
|
13
|
+
risk: string;
|
|
14
|
+
}[];
|
|
15
|
+
rootWarning: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 执行全部自动检查,返回结果(供启动时告警用)
|
|
19
|
+
*/
|
|
20
|
+
export declare function runAutoCheck(locale?: 'zh' | 'en'): Promise<AutoCheckResult>;
|
|
21
|
+
/**
|
|
22
|
+
* 启动时执行检查,发现问题时通过 logger 告警
|
|
23
|
+
*/
|
|
24
|
+
export declare function runAutoCheckOnStartup(logger: {
|
|
25
|
+
warn: (s: string) => void;
|
|
26
|
+
}, locale: 'zh' | 'en'): void;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// src/auto-check.ts — 启动时自动安全检查,减少人为操作
|
|
2
|
+
// 异步执行,不阻塞启动;发现问题时通过 logger 告警
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { getHomeDir } from './utils.js';
|
|
7
|
+
import { fetchVulnDB, compareVersions } from './update-check.js';
|
|
8
|
+
const OPENCLAW_DIR = join(getHomeDir(), '.openclaw');
|
|
9
|
+
const SUSPICIOUS_PATTERNS = [
|
|
10
|
+
{ pattern: /eval\s*\(/, name: 'eval()' },
|
|
11
|
+
{ pattern: /\/dev\/tcp|nc\s+-e|ncat/, name: 'reverse shell' },
|
|
12
|
+
{ pattern: /webhook|exfil|callback.*http/i, name: 'data exfil' },
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* 获取 OpenClaw 版本
|
|
16
|
+
*/
|
|
17
|
+
function getOpenClawVersion() {
|
|
18
|
+
try {
|
|
19
|
+
const out = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString().trim();
|
|
20
|
+
const match = out.match(/(\d{4}\.\d+\.\d+|\d+\.\d+\.\d+)/);
|
|
21
|
+
return match ? match[1] : 'unknown';
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return 'unknown';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 检查 OpenClaw 是否受已知漏洞影响
|
|
29
|
+
*/
|
|
30
|
+
async function checkOpenClawVulns(version, locale = 'en') {
|
|
31
|
+
const vulns = [];
|
|
32
|
+
try {
|
|
33
|
+
const { vulns: db } = await fetchVulnDB();
|
|
34
|
+
const list = db.length > 0 ? db : [
|
|
35
|
+
{ affectedBelow: '1.0.111', severity: 'HIGH', id: 'CVE-2025-59536', description_zh: 'RCE via Hooks/MCP', description_en: 'RCE via Hooks/MCP' },
|
|
36
|
+
{ affectedBelow: '2.0.65', severity: 'MEDIUM', id: 'CVE-2026-21852', description_zh: 'API Key exfil', description_en: 'API Key exfil' },
|
|
37
|
+
];
|
|
38
|
+
for (const v of list) {
|
|
39
|
+
if (version !== 'unknown' && compareVersions(version, v.affectedBelow) < 0) {
|
|
40
|
+
vulns.push({
|
|
41
|
+
id: v.id,
|
|
42
|
+
severity: v.severity || 'MEDIUM',
|
|
43
|
+
description: locale === 'zh'
|
|
44
|
+
? (v.description_zh || v.description_en || v.id)
|
|
45
|
+
: (v.description_en || v.description_zh || v.id),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { /* ignore */ }
|
|
51
|
+
return vulns;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 快速扫描插件中的高风险模式
|
|
55
|
+
*/
|
|
56
|
+
function scanPluginsQuick() {
|
|
57
|
+
const risks = [];
|
|
58
|
+
const dirs = [
|
|
59
|
+
join(OPENCLAW_DIR, 'extensions'),
|
|
60
|
+
join(OPENCLAW_DIR, 'plugins'),
|
|
61
|
+
];
|
|
62
|
+
for (const dir of dirs) {
|
|
63
|
+
if (!existsSync(dir))
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
for (const name of readdirSync(dir)) {
|
|
67
|
+
const p = join(dir, name);
|
|
68
|
+
if (name.startsWith('.'))
|
|
69
|
+
continue;
|
|
70
|
+
try {
|
|
71
|
+
const files = readdirSync(p);
|
|
72
|
+
for (const f of files) {
|
|
73
|
+
if (!/\.(ts|js)$/.test(f))
|
|
74
|
+
continue;
|
|
75
|
+
const content = readFileSync(join(p, f), 'utf-8').slice(0, 50000);
|
|
76
|
+
for (const { pattern, name: riskName } of SUSPICIOUS_PATTERNS) {
|
|
77
|
+
if (pattern.test(content)) {
|
|
78
|
+
risks.push({ plugin: name, risk: riskName });
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch { /* skip */ }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch { /* skip */ }
|
|
88
|
+
}
|
|
89
|
+
return risks;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 扫描 MCP 配置中的可疑项
|
|
93
|
+
*/
|
|
94
|
+
function scanMcpConfig() {
|
|
95
|
+
const risks = [];
|
|
96
|
+
const configPaths = [
|
|
97
|
+
join(OPENCLAW_DIR, 'mcp.json'),
|
|
98
|
+
join(OPENCLAW_DIR, 'config', 'mcp.json'),
|
|
99
|
+
join(OPENCLAW_DIR, 'settings.json'),
|
|
100
|
+
];
|
|
101
|
+
for (const p of configPaths) {
|
|
102
|
+
if (!existsSync(p))
|
|
103
|
+
continue;
|
|
104
|
+
try {
|
|
105
|
+
const content = readFileSync(p, 'utf-8');
|
|
106
|
+
if (/webhook|exfil|callback|pastebin|requestbin/i.test(content)) {
|
|
107
|
+
risks.push({ config: p, risk: 'suspicious URL in config' });
|
|
108
|
+
}
|
|
109
|
+
if (/command.*:.*["'](?:curl|wget|nc)\s/i.test(content)) {
|
|
110
|
+
risks.push({ config: p, risk: 'network command in MCP' });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch { /* skip */ }
|
|
114
|
+
}
|
|
115
|
+
return risks;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 执行全部自动检查,返回结果(供启动时告警用)
|
|
119
|
+
*/
|
|
120
|
+
export async function runAutoCheck(locale = 'en') {
|
|
121
|
+
const ocVersion = getOpenClawVersion();
|
|
122
|
+
const [openclawVulns, pluginRisks, mcpRisks] = await Promise.all([
|
|
123
|
+
checkOpenClawVulns(ocVersion, locale),
|
|
124
|
+
Promise.resolve(scanPluginsQuick()),
|
|
125
|
+
Promise.resolve(scanMcpConfig()),
|
|
126
|
+
]);
|
|
127
|
+
const rootWarning = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
128
|
+
return { openclawVulns, pluginRisks, mcpRisks, rootWarning };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 启动时执行检查,发现问题时通过 logger 告警
|
|
132
|
+
*/
|
|
133
|
+
export function runAutoCheckOnStartup(logger, locale) {
|
|
134
|
+
runAutoCheck(locale).then(result => {
|
|
135
|
+
const zh = locale === 'zh';
|
|
136
|
+
const lines = [];
|
|
137
|
+
if (result.openclawVulns.length > 0) {
|
|
138
|
+
lines.push(zh ? '⚠️ OpenClaw 存在已知漏洞:' : '⚠️ OpenClaw has known vulnerabilities:');
|
|
139
|
+
for (const v of result.openclawVulns) {
|
|
140
|
+
lines.push(` ${v.id} [${v.severity}]: ${v.description}`);
|
|
141
|
+
}
|
|
142
|
+
lines.push(zh ? ' 请运行 /check-updates 查看详情并升级' : ' Run /check-updates for details and upgrade');
|
|
143
|
+
}
|
|
144
|
+
if (result.pluginRisks.length > 0) {
|
|
145
|
+
lines.push(zh ? '⚠️ 插件扫描发现可疑模式:' : '⚠️ Plugin scan found suspicious patterns:');
|
|
146
|
+
for (const r of result.pluginRisks.slice(0, 3)) {
|
|
147
|
+
lines.push(` ${r.plugin}: ${r.risk}`);
|
|
148
|
+
}
|
|
149
|
+
if (result.pluginRisks.length > 3) {
|
|
150
|
+
lines.push(zh ? ` ... 共 ${result.pluginRisks.length} 项` : ` ... ${result.pluginRisks.length} total`);
|
|
151
|
+
}
|
|
152
|
+
lines.push(zh ? ' 请运行 /scan-plugins 查看详情' : ' Run /scan-plugins for details');
|
|
153
|
+
}
|
|
154
|
+
if (result.mcpRisks.length > 0) {
|
|
155
|
+
lines.push(zh ? '⚠️ MCP 配置存在可疑项:' : '⚠️ Suspicious items in MCP config:');
|
|
156
|
+
for (const r of result.mcpRisks) {
|
|
157
|
+
lines.push(` ${r.config}: ${r.risk}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (result.rootWarning) {
|
|
161
|
+
lines.push(zh ? '⚠️ 正在以 root 运行,建议使用普通用户' : '⚠️ Running as root, consider using non-root user');
|
|
162
|
+
}
|
|
163
|
+
if (lines.length > 0) {
|
|
164
|
+
logger.warn((zh ? '[ShellWard] 自动安全检查:\n' : '[ShellWard] Auto security check:\n') + lines.join('\n'));
|
|
165
|
+
}
|
|
166
|
+
}).catch(() => { });
|
|
167
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/commands/audit.ts — /audit command: view recent audit log entries
|
|
2
|
+
import { readFileSync, statSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getHomeDir } from '../utils.js';
|
|
5
|
+
import { resolveLocale } from '../types.js';
|
|
6
|
+
const LOG_FILE = join(getHomeDir(), '.openclaw', 'shellward', 'audit.jsonl');
|
|
7
|
+
export function registerAuditCommand(api, config) {
|
|
8
|
+
const locale = resolveLocale(config);
|
|
9
|
+
api.registerCommand({
|
|
10
|
+
name: 'audit',
|
|
11
|
+
description: locale === 'zh'
|
|
12
|
+
? '📋 查看 ShellWard 审计日志 (用法: /audit [数量] [block|audit|critical])'
|
|
13
|
+
: '📋 View ShellWard audit log (usage: /audit [count] [block|audit|critical])',
|
|
14
|
+
acceptsArgs: true,
|
|
15
|
+
handler: (ctx) => {
|
|
16
|
+
const zh = locale === 'zh';
|
|
17
|
+
const args = (ctx.args || '').trim().split(/\s+/).filter(Boolean);
|
|
18
|
+
// Parse args: count and optional filter
|
|
19
|
+
let count = 20;
|
|
20
|
+
let filter = '';
|
|
21
|
+
for (const arg of args) {
|
|
22
|
+
if (/^\d+$/.test(arg)) {
|
|
23
|
+
count = Math.min(parseInt(arg), 100);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
filter = arg.toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
statSync(LOG_FILE);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { text: zh ? '⚠️ 审计日志文件不存在,尚无安全事件记录。' : '⚠️ Audit log not found. No security events recorded yet.' };
|
|
34
|
+
}
|
|
35
|
+
const content = readFileSync(LOG_FILE, 'utf-8');
|
|
36
|
+
let lines = content.trim().split('\n').filter(Boolean);
|
|
37
|
+
// Apply filter
|
|
38
|
+
if (filter === 'block') {
|
|
39
|
+
lines = lines.filter(l => l.includes('"action":"block"'));
|
|
40
|
+
}
|
|
41
|
+
else if (filter === 'audit') {
|
|
42
|
+
lines = lines.filter(l => l.includes('"action":"audit"'));
|
|
43
|
+
}
|
|
44
|
+
else if (filter === 'redact') {
|
|
45
|
+
lines = lines.filter(l => l.includes('"action":"redact"'));
|
|
46
|
+
}
|
|
47
|
+
else if (filter === 'critical') {
|
|
48
|
+
lines = lines.filter(l => l.includes('"level":"CRITICAL"'));
|
|
49
|
+
}
|
|
50
|
+
else if (filter === 'high') {
|
|
51
|
+
lines = lines.filter(l => l.includes('"level":"HIGH"') || l.includes('"level":"CRITICAL"'));
|
|
52
|
+
}
|
|
53
|
+
// Get last N entries
|
|
54
|
+
const recent = lines.slice(-count);
|
|
55
|
+
if (recent.length === 0) {
|
|
56
|
+
return { text: zh ? '✅ 没有匹配的审计事件。' : '✅ No matching audit events.' };
|
|
57
|
+
}
|
|
58
|
+
const header = zh
|
|
59
|
+
? `📋 **审计日志** (最近 ${recent.length} 条${filter ? `, 过滤: ${filter}` : ''})`
|
|
60
|
+
: `📋 **Audit Log** (last ${recent.length} entries${filter ? `, filter: ${filter}` : ''})`;
|
|
61
|
+
const formatted = recent.map(line => {
|
|
62
|
+
try {
|
|
63
|
+
const e = JSON.parse(line);
|
|
64
|
+
const icon = e.action === 'block' ? '🚫' : e.action === 'audit' ? '📋' : e.action === 'redact' ? '🔒' : e.level === 'CRITICAL' ? '🔴' : 'ℹ️';
|
|
65
|
+
const time = e.ts?.slice(11, 19) || '??:??:??';
|
|
66
|
+
return `${icon} \`${time}\` **${e.layer}** ${e.action}: ${e.detail?.slice(0, 80) || ''}${e.pattern ? ` [${e.pattern}]` : ''}`;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return line.slice(0, 100);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return { text: `${header}\n\n${formatted.join('\n')}` };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|