shellward 0.4.0 → 0.5.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.
- package/README.md +231 -230
- package/openclaw.plugin.json +7 -2
- package/package.json +24 -8
- package/src/audit-log.ts +12 -2
- package/src/auto-check.ts +177 -0
- package/src/commands/audit.ts +7 -4
- package/src/commands/harden.ts +39 -1
- package/src/commands/index.ts +8 -4
- package/src/commands/scan-plugins.ts +18 -2
- package/src/commands/security.ts +8 -4
- package/src/commands/upgrade-openclaw.ts +58 -0
- package/src/core/engine.ts +667 -0
- package/src/index.ts +65 -87
- package/src/layers/data-flow-guard.ts +11 -142
- package/src/layers/input-auditor.ts +17 -156
- package/src/layers/outbound-guard.ts +11 -54
- package/src/layers/output-scanner.ts +6 -79
- package/src/layers/prompt-guard.ts +6 -59
- package/src/layers/security-gate.ts +11 -86
- package/src/layers/session-guard.ts +8 -23
- package/src/layers/tool-blocker.ts +19 -166
- package/src/rules/dangerous-commands.ts +12 -0
- package/src/rules/injection-en.ts +16 -0
- package/src/rules/injection-zh.ts +29 -1
- package/src/types.ts +4 -1
- package/src/update-check.ts +4 -2
- package/src/utils.ts +10 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/auto-check.ts — 启动时自动安全检查,减少人为操作
|
|
2
|
+
// 异步执行,不阻塞启动;发现问题时通过 logger 告警
|
|
3
|
+
|
|
4
|
+
import { execSync } from 'child_process'
|
|
5
|
+
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
import { getHomeDir } from './utils'
|
|
8
|
+
import { fetchVulnDB, compareVersions } from './update-check'
|
|
9
|
+
|
|
10
|
+
const OPENCLAW_DIR = join(getHomeDir(), '.openclaw')
|
|
11
|
+
|
|
12
|
+
export interface AutoCheckResult {
|
|
13
|
+
openclawVulns: { id: string; severity: string; description: string }[]
|
|
14
|
+
pluginRisks: { plugin: string; risk: string }[]
|
|
15
|
+
mcpRisks: { config: string; risk: string }[]
|
|
16
|
+
rootWarning: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SUSPICIOUS_PATTERNS = [
|
|
20
|
+
{ pattern: /eval\s*\(/, name: 'eval()' },
|
|
21
|
+
{ pattern: /\/dev\/tcp|nc\s+-e|ncat/, name: 'reverse shell' },
|
|
22
|
+
{ pattern: /webhook|exfil|callback.*http/i, name: 'data exfil' },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 获取 OpenClaw 版本
|
|
27
|
+
*/
|
|
28
|
+
function getOpenClawVersion(): string {
|
|
29
|
+
try {
|
|
30
|
+
const out = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString().trim()
|
|
31
|
+
const match = out.match(/(\d{4}\.\d+\.\d+|\d+\.\d+\.\d+)/)
|
|
32
|
+
return match ? match[1] : 'unknown'
|
|
33
|
+
} catch {
|
|
34
|
+
return 'unknown'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 检查 OpenClaw 是否受已知漏洞影响
|
|
40
|
+
*/
|
|
41
|
+
async function checkOpenClawVulns(version: string): Promise<{ id: string; severity: string; description: string }[]> {
|
|
42
|
+
const vulns: { id: string; severity: string; description: string }[] = []
|
|
43
|
+
try {
|
|
44
|
+
const { vulns: db } = await fetchVulnDB()
|
|
45
|
+
const list = db.length > 0 ? db : [
|
|
46
|
+
{ affectedBelow: '1.0.111', severity: 'HIGH' as const, id: 'CVE-2025-59536', description_zh: 'RCE via Hooks/MCP', description_en: 'RCE via Hooks/MCP' },
|
|
47
|
+
{ affectedBelow: '2.0.65', severity: 'MEDIUM' as const, id: 'CVE-2026-21852', description_zh: 'API Key exfil', description_en: 'API Key exfil' },
|
|
48
|
+
]
|
|
49
|
+
for (const v of list) {
|
|
50
|
+
if (version !== 'unknown' && compareVersions(version, v.affectedBelow) < 0) {
|
|
51
|
+
vulns.push({
|
|
52
|
+
id: v.id,
|
|
53
|
+
severity: v.severity || 'MEDIUM',
|
|
54
|
+
description: (v as any).description_zh || (v as any).description_en || v.id,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch { /* ignore */ }
|
|
59
|
+
return vulns
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 快速扫描插件中的高风险模式
|
|
64
|
+
*/
|
|
65
|
+
function scanPluginsQuick(): { plugin: string; risk: string }[] {
|
|
66
|
+
const risks: { plugin: string; risk: string }[] = []
|
|
67
|
+
const dirs = [
|
|
68
|
+
join(OPENCLAW_DIR, 'extensions'),
|
|
69
|
+
join(OPENCLAW_DIR, 'plugins'),
|
|
70
|
+
]
|
|
71
|
+
for (const dir of dirs) {
|
|
72
|
+
if (!existsSync(dir)) continue
|
|
73
|
+
try {
|
|
74
|
+
for (const name of readdirSync(dir)) {
|
|
75
|
+
const p = join(dir, name)
|
|
76
|
+
if (name.startsWith('.')) continue
|
|
77
|
+
try {
|
|
78
|
+
const files = readdirSync(p)
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
if (!/\.(ts|js)$/.test(f)) continue
|
|
81
|
+
const content = readFileSync(join(p, f), 'utf-8').slice(0, 50000)
|
|
82
|
+
for (const { pattern, name: riskName } of SUSPICIOUS_PATTERNS) {
|
|
83
|
+
if (pattern.test(content)) {
|
|
84
|
+
risks.push({ plugin: name, risk: riskName })
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch { /* skip */ }
|
|
90
|
+
}
|
|
91
|
+
} catch { /* skip */ }
|
|
92
|
+
}
|
|
93
|
+
return risks
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 扫描 MCP 配置中的可疑项
|
|
98
|
+
*/
|
|
99
|
+
function scanMcpConfig(): { config: string; risk: string }[] {
|
|
100
|
+
const risks: { config: string; risk: string }[] = []
|
|
101
|
+
const configPaths = [
|
|
102
|
+
join(OPENCLAW_DIR, 'mcp.json'),
|
|
103
|
+
join(OPENCLAW_DIR, 'config', 'mcp.json'),
|
|
104
|
+
join(OPENCLAW_DIR, 'settings.json'),
|
|
105
|
+
]
|
|
106
|
+
for (const p of configPaths) {
|
|
107
|
+
if (!existsSync(p)) continue
|
|
108
|
+
try {
|
|
109
|
+
const content = readFileSync(p, 'utf-8')
|
|
110
|
+
if (/webhook|exfil|callback|pastebin|requestbin/i.test(content)) {
|
|
111
|
+
risks.push({ config: p, risk: 'suspicious URL in config' })
|
|
112
|
+
}
|
|
113
|
+
if (/command.*:.*["'](?:curl|wget|nc)\s/i.test(content)) {
|
|
114
|
+
risks.push({ config: p, risk: 'network command in MCP' })
|
|
115
|
+
}
|
|
116
|
+
} catch { /* skip */ }
|
|
117
|
+
}
|
|
118
|
+
return risks
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 执行全部自动检查,返回结果(供启动时告警用)
|
|
123
|
+
*/
|
|
124
|
+
export async function runAutoCheck(): Promise<AutoCheckResult> {
|
|
125
|
+
const ocVersion = getOpenClawVersion()
|
|
126
|
+
const [openclawVulns, pluginRisks, mcpRisks] = await Promise.all([
|
|
127
|
+
checkOpenClawVulns(ocVersion),
|
|
128
|
+
Promise.resolve(scanPluginsQuick()),
|
|
129
|
+
Promise.resolve(scanMcpConfig()),
|
|
130
|
+
])
|
|
131
|
+
const rootWarning = typeof process.getuid === 'function' && process.getuid() === 0
|
|
132
|
+
return { openclawVulns, pluginRisks, mcpRisks, rootWarning }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 启动时执行检查,发现问题时通过 logger 告警
|
|
137
|
+
*/
|
|
138
|
+
export function runAutoCheckOnStartup(logger: { warn: (s: string) => void }, locale: 'zh' | 'en'): void {
|
|
139
|
+
runAutoCheck().then(result => {
|
|
140
|
+
const zh = locale === 'zh'
|
|
141
|
+
const lines: string[] = []
|
|
142
|
+
|
|
143
|
+
if (result.openclawVulns.length > 0) {
|
|
144
|
+
lines.push(zh ? '⚠️ OpenClaw 存在已知漏洞:' : '⚠️ OpenClaw has known vulnerabilities:')
|
|
145
|
+
for (const v of result.openclawVulns) {
|
|
146
|
+
lines.push(` ${v.id} [${v.severity}]: ${v.description}`)
|
|
147
|
+
}
|
|
148
|
+
lines.push(zh ? ' 请运行 /check-updates 查看详情并升级' : ' Run /check-updates for details and upgrade')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (result.pluginRisks.length > 0) {
|
|
152
|
+
lines.push(zh ? '⚠️ 插件扫描发现可疑模式:' : '⚠️ Plugin scan found suspicious patterns:')
|
|
153
|
+
for (const r of result.pluginRisks.slice(0, 3)) {
|
|
154
|
+
lines.push(` ${r.plugin}: ${r.risk}`)
|
|
155
|
+
}
|
|
156
|
+
if (result.pluginRisks.length > 3) {
|
|
157
|
+
lines.push(` ... 共 ${result.pluginRisks.length} 项`)
|
|
158
|
+
}
|
|
159
|
+
lines.push(zh ? ' 请运行 /scan-plugins 查看详情' : ' Run /scan-plugins for details')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (result.mcpRisks.length > 0) {
|
|
163
|
+
lines.push(zh ? '⚠️ MCP 配置存在可疑项:' : '⚠️ Suspicious items in MCP config:')
|
|
164
|
+
for (const r of result.mcpRisks) {
|
|
165
|
+
lines.push(` ${r.config}: ${r.risk}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (result.rootWarning) {
|
|
170
|
+
lines.push(zh ? '⚠️ 正在以 root 运行,建议使用普通用户' : '⚠️ Running as root, consider using non-root user')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (lines.length > 0) {
|
|
174
|
+
logger.warn('[ShellWard] 自动安全检查:\n' + lines.join('\n'))
|
|
175
|
+
}
|
|
176
|
+
}).catch(() => { /* 静默失败,不阻塞 */ })
|
|
177
|
+
}
|
package/src/commands/audit.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { readFileSync, statSync } from 'fs'
|
|
4
4
|
import { join } from 'path'
|
|
5
|
+
import { getHomeDir } from '../utils'
|
|
5
6
|
import type { ShellWardConfig } from '../types'
|
|
6
7
|
import { resolveLocale } from '../types'
|
|
7
8
|
|
|
8
|
-
const LOG_FILE = join(
|
|
9
|
+
const LOG_FILE = join(getHomeDir(), '.openclaw', 'shellward', 'audit.jsonl')
|
|
9
10
|
|
|
10
11
|
export function registerAuditCommand(api: any, config: ShellWardConfig) {
|
|
11
12
|
const locale = resolveLocale(config)
|
|
@@ -13,8 +14,8 @@ export function registerAuditCommand(api: any, config: ShellWardConfig) {
|
|
|
13
14
|
api.registerCommand({
|
|
14
15
|
name: 'audit',
|
|
15
16
|
description: locale === 'zh'
|
|
16
|
-
? '📋 查看 ShellWard 审计日志 (用法: /audit [数量] [block|
|
|
17
|
-
: '📋 View ShellWard audit log (usage: /audit [count] [block|
|
|
17
|
+
? '📋 查看 ShellWard 审计日志 (用法: /audit [数量] [block|audit|critical])'
|
|
18
|
+
: '📋 View ShellWard audit log (usage: /audit [count] [block|audit|critical])',
|
|
18
19
|
acceptsArgs: true,
|
|
19
20
|
handler: (ctx: any) => {
|
|
20
21
|
const zh = locale === 'zh'
|
|
@@ -43,6 +44,8 @@ export function registerAuditCommand(api: any, config: ShellWardConfig) {
|
|
|
43
44
|
// Apply filter
|
|
44
45
|
if (filter === 'block') {
|
|
45
46
|
lines = lines.filter(l => l.includes('"action":"block"'))
|
|
47
|
+
} else if (filter === 'audit') {
|
|
48
|
+
lines = lines.filter(l => l.includes('"action":"audit"'))
|
|
46
49
|
} else if (filter === 'redact') {
|
|
47
50
|
lines = lines.filter(l => l.includes('"action":"redact"'))
|
|
48
51
|
} else if (filter === 'critical') {
|
|
@@ -65,7 +68,7 @@ export function registerAuditCommand(api: any, config: ShellWardConfig) {
|
|
|
65
68
|
const formatted = recent.map(line => {
|
|
66
69
|
try {
|
|
67
70
|
const e = JSON.parse(line)
|
|
68
|
-
const icon = e.action === 'block' ? '🚫' : e.action === 'redact' ? '🔒' : e.level === 'CRITICAL' ? '🔴' : 'ℹ️'
|
|
71
|
+
const icon = e.action === 'block' ? '🚫' : e.action === 'audit' ? '📋' : e.action === 'redact' ? '🔒' : e.level === 'CRITICAL' ? '🔴' : 'ℹ️'
|
|
69
72
|
const time = e.ts?.slice(11, 19) || '??:??:??'
|
|
70
73
|
return `${icon} \`${time}\` **${e.layer}** ${e.action}: ${e.detail?.slice(0, 80) || ''}${e.pattern ? ` [${e.pattern}]` : ''}`
|
|
71
74
|
} catch {
|
package/src/commands/harden.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { execSync } from 'child_process'
|
|
|
6
6
|
import type { ShellWardConfig } from '../types'
|
|
7
7
|
import { resolveLocale } from '../types'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import { getHomeDir } from '../utils'
|
|
10
|
+
const HOME = getHomeDir()
|
|
10
11
|
|
|
11
12
|
export function registerHardenCommand(api: any, config: ShellWardConfig) {
|
|
12
13
|
const locale = resolveLocale(config)
|
|
@@ -171,6 +172,43 @@ export function registerHardenCommand(api: any, config: ShellWardConfig) {
|
|
|
171
172
|
}
|
|
172
173
|
lines.push('')
|
|
173
174
|
|
|
175
|
+
// === 5. One-click scripts ===
|
|
176
|
+
lines.push(zh ? '### 一键安全脚本' : '### One-click Security Scripts')
|
|
177
|
+
|
|
178
|
+
// Dockerfile
|
|
179
|
+
lines.push(zh ? '**容器隔离** — 复制以下 Dockerfile:' : '**Container isolation** — copy this Dockerfile:')
|
|
180
|
+
lines.push('```dockerfile')
|
|
181
|
+
lines.push('FROM node:22-slim')
|
|
182
|
+
lines.push('RUN useradd -m -s /bin/bash openclaw')
|
|
183
|
+
lines.push('USER openclaw')
|
|
184
|
+
lines.push('WORKDIR /home/openclaw')
|
|
185
|
+
lines.push('RUN npm install -g openclaw')
|
|
186
|
+
lines.push('COPY .env .env')
|
|
187
|
+
lines.push('EXPOSE 19000')
|
|
188
|
+
lines.push('CMD ["openclaw", "agent", "--local"]')
|
|
189
|
+
lines.push('```')
|
|
190
|
+
lines.push(zh
|
|
191
|
+
? '运行: `docker build -t openclaw-safe . && docker run --rm -it openclaw-safe`'
|
|
192
|
+
: 'Run: `docker build -t openclaw-safe . && docker run --rm -it openclaw-safe`')
|
|
193
|
+
lines.push('')
|
|
194
|
+
|
|
195
|
+
// Firewall
|
|
196
|
+
lines.push(zh ? '**防火墙限制** — 仅允许必要出站:' : '**Firewall** — allow only necessary outbound:')
|
|
197
|
+
lines.push('```bash')
|
|
198
|
+
lines.push('# 只允许 HTTPS 出站(API 调用),禁止其他出站')
|
|
199
|
+
lines.push('sudo iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT')
|
|
200
|
+
lines.push('sudo iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT')
|
|
201
|
+
lines.push('sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT # DNS')
|
|
202
|
+
lines.push('sudo iptables -A OUTPUT -o lo -j ACCEPT # localhost')
|
|
203
|
+
lines.push('sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT')
|
|
204
|
+
lines.push('sudo iptables -A OUTPUT -j LOG --log-prefix "BLOCKED: "')
|
|
205
|
+
lines.push('sudo iptables -A OUTPUT -j DROP')
|
|
206
|
+
lines.push('```')
|
|
207
|
+
lines.push(zh
|
|
208
|
+
? '⚠️ 执行前请确认不会影响其他服务'
|
|
209
|
+
: '⚠️ Review before applying — may affect other services')
|
|
210
|
+
lines.push('')
|
|
211
|
+
|
|
174
212
|
// === Summary ===
|
|
175
213
|
lines.push('---')
|
|
176
214
|
if (issues.length === 0) {
|
package/src/commands/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { registerAuditCommand } from './audit'
|
|
|
7
7
|
import { registerHardenCommand } from './harden'
|
|
8
8
|
import { registerScanPluginsCommand } from './scan-plugins'
|
|
9
9
|
import { registerCheckUpdatesCommand } from './check-updates'
|
|
10
|
+
import { registerUpgradeOpenClawCommand } from './upgrade-openclaw'
|
|
10
11
|
|
|
11
12
|
export function registerAllCommands(api: any, config: ShellWardConfig) {
|
|
12
13
|
const locale = resolveLocale(config)
|
|
@@ -17,6 +18,7 @@ export function registerAllCommands(api: any, config: ShellWardConfig) {
|
|
|
17
18
|
registerHardenCommand(api, config)
|
|
18
19
|
registerScanPluginsCommand(api, config)
|
|
19
20
|
registerCheckUpdatesCommand(api, config)
|
|
21
|
+
registerUpgradeOpenClawCommand(api, config)
|
|
20
22
|
|
|
21
23
|
// Register /cg shortcut with help
|
|
22
24
|
api.registerCommand({
|
|
@@ -31,23 +33,25 @@ export function registerAllCommands(api: any, config: ShellWardConfig) {
|
|
|
31
33
|
| 命令 | 说明 |
|
|
32
34
|
|------|------|
|
|
33
35
|
| \`/security\` | 安全状态总览(防御层、审计统计、系统检查) |
|
|
34
|
-
| \`/audit [数量] [过滤]\` | 查看审计日志 (过滤: block/
|
|
36
|
+
| \`/audit [数量] [过滤]\` | 查看审计日志 (过滤: block/audit/critical/high) |
|
|
35
37
|
| \`/harden\` | 安全扫描 · \`/harden fix\` 自动修复权限 |
|
|
36
38
|
| \`/scan-plugins\` | 扫描已安装插件的安全风险 |
|
|
37
39
|
| \`/check-updates\` | 检查 OpenClaw 版本和已知漏洞 |
|
|
40
|
+
| \`/upgrade-openclaw\` | 一键升级 OpenClaw · \`/upgrade-openclaw yes\` 直接执行 |
|
|
38
41
|
|
|
39
42
|
**当前防御层 (8层):**
|
|
40
|
-
L1 提示注入 · L2
|
|
41
|
-
L5 安全门 · L6
|
|
43
|
+
L1 提示注入 · L2 输出审计 · L3 工具拦截 · L4 注入检测
|
|
44
|
+
L5 安全门 · L6 回复审计 · L7 数据流监控 · L8 会话安全`
|
|
42
45
|
: `🛡️ **ShellWard Quick Commands**
|
|
43
46
|
|
|
44
47
|
| Command | Description |
|
|
45
48
|
|---------|-------------|
|
|
46
49
|
| \`/security\` | Security status overview (layers, audit stats, system checks) |
|
|
47
|
-
| \`/audit [count] [filter]\` | View audit log (filter: block/
|
|
50
|
+
| \`/audit [count] [filter]\` | View audit log (filter: block/audit/critical/high) |
|
|
48
51
|
| \`/harden\` | Security scan · \`/harden fix\` to auto-fix permissions |
|
|
49
52
|
| \`/scan-plugins\` | Scan installed plugins for security risks |
|
|
50
53
|
| \`/check-updates\` | Check OpenClaw version and known vulnerabilities |
|
|
54
|
+
| \`/upgrade-openclaw\` | Upgrade OpenClaw · \`/upgrade-openclaw yes\` to execute |
|
|
51
55
|
|
|
52
56
|
**Active Defense Layers (8):**
|
|
53
57
|
L1 Prompt Guard · L2 Output Scanner · L3 Tool Blocker · L4 Input Auditor
|
|
@@ -5,7 +5,8 @@ import { join } from 'path'
|
|
|
5
5
|
import type { ShellWardConfig } from '../types'
|
|
6
6
|
import { resolveLocale } from '../types'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import { getHomeDir } from '../utils'
|
|
9
|
+
const HOME = getHomeDir()
|
|
9
10
|
const OPENCLAW_DIR = join(HOME, '.openclaw')
|
|
10
11
|
|
|
11
12
|
// Known suspicious patterns in plugin code
|
|
@@ -74,6 +75,7 @@ export function registerScanPluginsCommand(api: any, config: ShellWardConfig) {
|
|
|
74
75
|
lines.push('')
|
|
75
76
|
|
|
76
77
|
let totalRisks = 0
|
|
78
|
+
const riskyPluginNames = new Set<string>()
|
|
77
79
|
|
|
78
80
|
for (const plugin of pluginDirs) {
|
|
79
81
|
const risks: string[] = []
|
|
@@ -147,11 +149,14 @@ export function registerScanPluginsCommand(api: any, config: ShellWardConfig) {
|
|
|
147
149
|
lines.push(` ${risk}`)
|
|
148
150
|
totalRisks++
|
|
149
151
|
}
|
|
152
|
+
if (risks.some(r => r.startsWith('🔴') || (r.startsWith('⚠️') && !r.includes('签名') && !r.includes('signature') && !r.includes('依赖') && !r.includes('deps')))) {
|
|
153
|
+
riskyPluginNames.add(plugin.name)
|
|
154
|
+
}
|
|
150
155
|
}
|
|
151
156
|
lines.push('')
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
// Summary
|
|
159
|
+
// Summary + removal commands
|
|
155
160
|
lines.push('---')
|
|
156
161
|
if (totalRisks === 0) {
|
|
157
162
|
lines.push(zh ? '✅ **所有插件扫描通过**' : '✅ **All plugins passed scan**')
|
|
@@ -159,6 +164,17 @@ export function registerScanPluginsCommand(api: any, config: ShellWardConfig) {
|
|
|
159
164
|
lines.push(zh
|
|
160
165
|
? `⚠️ **发现 ${totalRisks} 个潜在风险** — 请审查标记的插件`
|
|
161
166
|
: `⚠️ **${totalRisks} potential risks found** — review flagged plugins`)
|
|
167
|
+
|
|
168
|
+
if (riskyPluginNames.size > 0) {
|
|
169
|
+
lines.push('')
|
|
170
|
+
lines.push(zh ? '**一键移除高风险插件** — 复制执行:' : '**Remove risky plugins** — copy & run:')
|
|
171
|
+
lines.push('```bash')
|
|
172
|
+
for (const name of riskyPluginNames) {
|
|
173
|
+
lines.push(`openclaw plugins uninstall ${name}`)
|
|
174
|
+
}
|
|
175
|
+
lines.push('```')
|
|
176
|
+
}
|
|
177
|
+
|
|
162
178
|
lines.push(zh
|
|
163
179
|
? '💡 建议: 仅从可信渠道安装插件,定期运行 `/scan-plugins` 检查'
|
|
164
180
|
: '💡 Tip: Only install plugins from trusted sources, run `/scan-plugins` regularly')
|
package/src/commands/security.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { readFileSync, statSync, existsSync, readdirSync } from 'fs'
|
|
4
4
|
import { join } from 'path'
|
|
5
5
|
import { execSync } from 'child_process'
|
|
6
|
+
import { getHomeDir } from '../utils'
|
|
6
7
|
import type { ShellWardConfig } from '../types'
|
|
7
8
|
import { resolveLocale } from '../types'
|
|
8
9
|
|
|
9
|
-
const LOG_DIR = join(
|
|
10
|
+
const LOG_DIR = join(getHomeDir(), '.openclaw', 'shellward')
|
|
10
11
|
const LOG_FILE = join(LOG_DIR, 'audit.jsonl')
|
|
11
12
|
|
|
12
13
|
export function registerSecurityCommand(api: any, config: ShellWardConfig) {
|
|
@@ -33,6 +34,9 @@ export function registerSecurityCommand(api: any, config: ShellWardConfig) {
|
|
|
33
34
|
['L3 Tool Blocker', config.layers.toolBlocker],
|
|
34
35
|
['L4 Input Auditor', config.layers.inputAuditor],
|
|
35
36
|
['L5 Security Gate', config.layers.securityGate],
|
|
37
|
+
['L6 Outbound Guard', config.layers.outboundGuard],
|
|
38
|
+
['L7 Data Flow Guard', config.layers.dataFlowGuard],
|
|
39
|
+
['L8 Session Guard', config.layers.sessionGuard],
|
|
36
40
|
]
|
|
37
41
|
for (const [name, enabled] of layers) {
|
|
38
42
|
lines.push(` ${enabled ? '✅' : '❌'} ${name}`)
|
|
@@ -54,15 +58,15 @@ export function registerSecurityCommand(api: any, config: ShellWardConfig) {
|
|
|
54
58
|
const allLines = content.trim().split('\n').filter(Boolean)
|
|
55
59
|
const total = allLines.length
|
|
56
60
|
let blocks = 0
|
|
57
|
-
let
|
|
61
|
+
let audits = 0
|
|
58
62
|
let criticals = 0
|
|
59
63
|
for (const line of allLines) {
|
|
60
64
|
if (line.includes('"action":"block"')) blocks++
|
|
61
|
-
if (line.includes('"action":"
|
|
65
|
+
if (line.includes('"action":"audit"')) audits++
|
|
62
66
|
if (line.includes('"level":"CRITICAL"')) criticals++
|
|
63
67
|
}
|
|
64
68
|
lines.push(` ${zh ? '总事件' : 'Total events'}: ${total}`)
|
|
65
|
-
lines.push(` ${zh ? '拦截' : 'Blocked'}: ${blocks} | ${zh ? '
|
|
69
|
+
lines.push(` ${zh ? '拦截' : 'Blocked'}: ${blocks} | ${zh ? '审计' : 'Audited'}: ${audits} | ${zh ? '严重' : 'Critical'}: ${criticals}`)
|
|
66
70
|
} catch {
|
|
67
71
|
lines.push(zh ? ' ⚠️ 日志文件不存在' : ' ⚠️ Log file not found')
|
|
68
72
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/commands/upgrade-openclaw.ts — 一键升级 OpenClaw,减少手动操作
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'child_process'
|
|
4
|
+
import type { ShellWardConfig } from '../types'
|
|
5
|
+
import { resolveLocale } from '../types'
|
|
6
|
+
|
|
7
|
+
export function registerUpgradeOpenClawCommand(api: any, config: ShellWardConfig) {
|
|
8
|
+
const locale = resolveLocale(config)
|
|
9
|
+
|
|
10
|
+
api.registerCommand({
|
|
11
|
+
name: 'upgrade-openclaw',
|
|
12
|
+
description: locale === 'zh'
|
|
13
|
+
? '⬆️ 升级 OpenClaw 到最新版本(一键执行)'
|
|
14
|
+
: '⬆️ Upgrade OpenClaw to latest (one-click)',
|
|
15
|
+
acceptsArgs: true,
|
|
16
|
+
handler: (ctx: any) => {
|
|
17
|
+
const zh = locale === 'zh'
|
|
18
|
+
const args = (ctx.args || '').trim().toLowerCase()
|
|
19
|
+
const doUpgrade = args === 'yes' || args === 'y' || args === '--yes'
|
|
20
|
+
|
|
21
|
+
let currentVer = 'unknown'
|
|
22
|
+
try {
|
|
23
|
+
const out = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString()
|
|
24
|
+
const m = out.match(/(\d{4}\.\d+\.\d+|\d+\.\d+\.\d+)/)
|
|
25
|
+
if (m) currentVer = m[1]
|
|
26
|
+
} catch { /* skip */ }
|
|
27
|
+
|
|
28
|
+
const cmd = 'npm update -g openclaw'
|
|
29
|
+
const lines: string[] = []
|
|
30
|
+
|
|
31
|
+
if (doUpgrade) {
|
|
32
|
+
try {
|
|
33
|
+
execSync(cmd, { stdio: 'inherit', timeout: 120000 })
|
|
34
|
+
const newOut = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString()
|
|
35
|
+
const newM = newOut.match(/(\d{4}\.\d+\.\d+|\d+\.\d+\.\d+)/)
|
|
36
|
+
const newVer = newM ? newM[1] : 'unknown'
|
|
37
|
+
lines.push(zh ? `✅ 升级完成!当前版本: ${newVer}` : `✅ Upgrade done! Current version: ${newVer}`)
|
|
38
|
+
} catch (e: any) {
|
|
39
|
+
lines.push(zh ? `❌ 升级失败: ${e?.message || e}` : `❌ Upgrade failed: ${e?.message || e}`)
|
|
40
|
+
lines.push(zh ? `请手动执行: \`${cmd}\`` : `Run manually: \`${cmd}\``)
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(zh ? `当前版本: ${currentVer}` : `Current version: ${currentVer}`)
|
|
44
|
+
lines.push('')
|
|
45
|
+
lines.push(zh ? '**一键升级**(复制执行):' : '**One-click upgrade** (copy & run):')
|
|
46
|
+
lines.push('```bash')
|
|
47
|
+
lines.push(cmd)
|
|
48
|
+
lines.push('```')
|
|
49
|
+
lines.push('')
|
|
50
|
+
lines.push(zh
|
|
51
|
+
? '或在 OpenClaw 中执行: `/upgrade-openclaw yes` 自动升级'
|
|
52
|
+
: 'Or run in OpenClaw: `/upgrade-openclaw yes` to auto-upgrade')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { text: lines.join('\n') }
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
}
|