shellward 0.3.4 → 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.
@@ -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
+ }
@@ -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(process.env.HOME || '~', '.openclaw', 'shellward', 'audit.jsonl')
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|redact|critical])'
17
- : '📋 View ShellWard audit log (usage: /audit [count] [block|redact|critical])',
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 {
@@ -1,34 +1,35 @@
1
- // src/commands/check-updates.ts — /check-updates: check OpenClaw version and known vulnerabilities
1
+ // src/commands/check-updates.ts — /check-updates: check versions + remote vulnerability DB
2
2
 
3
3
  import { execSync } from 'child_process'
4
4
  import { existsSync, readFileSync } from 'fs'
5
5
  import { join } from 'path'
6
6
  import type { ShellWardConfig } from '../types'
7
7
  import { resolveLocale } from '../types'
8
+ import { checkForUpdate, fetchVulnDB, compareVersions } from '../update-check'
8
9
 
9
- // Known vulnerability database (hardcoded, updated with plugin releases)
10
- // Format: { version_range, severity, cve, description_zh, description_en }
11
- const KNOWN_VULNS = [
10
+ // Local fallback vulnerability database (used when remote fetch fails)
11
+ // Contains only CVE-assigned vulnerabilities as minimum baseline
12
+ const LOCAL_VULNS = [
12
13
  {
13
- affectedBelow: '2026.3.6',
14
- severity: 'HIGH',
15
- id: 'CG-2026-001',
16
- description_zh: 'tool_result_persist hook 可被绕过泄露敏感数据',
17
- description_en: 'tool_result_persist hook bypass may leak sensitive data',
14
+ affectedBelow: '1.0.111',
15
+ severity: 'HIGH' as const,
16
+ id: 'CVE-2025-59536',
17
+ description_zh: '远程代码执行:恶意仓库通过 Hooks 和 MCP Server 在信任提示前执行任意命令 (CVSS 8.7)',
18
+ description_en: 'RCE via Hooks and MCP Server bypass arbitrary shell execution before trust dialog (CVSS 8.7)',
18
19
  },
19
20
  {
20
- affectedBelow: '2026.3.4',
21
- severity: 'CRITICAL',
22
- id: 'CG-2026-002',
23
- description_zh: '插件系统缺少签名验证,可加载恶意插件',
24
- description_en: 'Plugin system lacks signature verification, allows malicious plugins',
21
+ affectedBelow: '2.0.65',
22
+ severity: 'MEDIUM' as const,
23
+ id: 'CVE-2026-21852',
24
+ description_zh: 'API 密钥泄露:恶意仓库通过 settings.json 设置 ANTHROPIC_BASE_URL 窃取用户 API Key (CVSS 5.3)',
25
+ description_en: 'API key exfiltration via ANTHROPIC_BASE_URL in settings.json before trust prompt (CVSS 5.3)',
25
26
  },
26
27
  {
27
- affectedBelow: '2026.3.2',
28
- severity: 'HIGH',
29
- id: 'CG-2026-003',
30
- description_zh: 'Gateway 默认绑定 0.0.0.0,未认证即可远程执行',
31
- description_en: 'Gateway binds 0.0.0.0 by default, allows unauthenticated remote execution',
28
+ affectedBelow: '2026.2.7',
29
+ severity: 'HIGH' as const,
30
+ id: 'GHSA-ff64-7w26-62rf',
31
+ description_zh: '沙箱逃逸:通过 settings.json 持久化配置注入',
32
+ description_en: 'Sandbox escape via persistent configuration injection in settings.json',
32
33
  },
33
34
  ]
34
35
 
@@ -38,10 +39,10 @@ export function registerCheckUpdatesCommand(api: any, config: ShellWardConfig) {
38
39
  api.registerCommand({
39
40
  name: 'check-updates',
40
41
  description: locale === 'zh'
41
- ? '🔄 检查 OpenClaw 版本和已知漏洞'
42
- : '🔄 Check OpenClaw version and known vulnerabilities',
42
+ ? '🔄 检查版本更新和已知漏洞(支持远程漏洞库)'
43
+ : '🔄 Check for updates and known vulnerabilities (remote vuln DB)',
43
44
  acceptsArgs: false,
44
- handler: () => {
45
+ handler: async () => {
45
46
  const zh = locale === 'zh'
46
47
  const lines: string[] = []
47
48
 
@@ -49,20 +50,19 @@ export function registerCheckUpdatesCommand(api: any, config: ShellWardConfig) {
49
50
  lines.push('')
50
51
 
51
52
  // 1. Get OpenClaw version
52
- let currentVersion = 'unknown'
53
+ let openclawVersion = 'unknown'
53
54
  try {
54
55
  const out = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString().trim()
55
- // Extract version like "2026.3.8"
56
56
  const match = out.match(/(\d{4}\.\d+\.\d+)/)
57
- if (match) currentVersion = match[1]
57
+ if (match) openclawVersion = match[1]
58
58
  } catch { /* skip */ }
59
59
 
60
60
  lines.push(zh
61
- ? `### OpenClaw 版本: ${currentVersion}`
62
- : `### OpenClaw Version: ${currentVersion}`)
61
+ ? `### OpenClaw 版本: ${openclawVersion}`
62
+ : `### OpenClaw Version: ${openclawVersion}`)
63
63
  lines.push('')
64
64
 
65
- // 2. Check ShellWard version
65
+ // 2. Check ShellWard version + available update
66
66
  let shellwardVersion = 'unknown'
67
67
  try {
68
68
  const pkgPath = join(__dirname, '../../package.json')
@@ -75,17 +75,45 @@ export function registerCheckUpdatesCommand(api: any, config: ShellWardConfig) {
75
75
  lines.push(zh
76
76
  ? `### ShellWard 版本: ${shellwardVersion}`
77
77
  : `### ShellWard Version: ${shellwardVersion}`)
78
+
79
+ // Check for ShellWard update from npm
80
+ try {
81
+ const updateInfo = await checkForUpdate(shellwardVersion)
82
+ if (updateInfo?.updateAvailable) {
83
+ lines.push(zh
84
+ ? ` 🆕 **新版本 v${updateInfo.latest} 可用!** 运行 \`openclaw plugins update shellward\` 更新`
85
+ : ` 🆕 **v${updateInfo.latest} available!** Run \`openclaw plugins update shellward\` to update`)
86
+ } else if (updateInfo) {
87
+ lines.push(zh ? ' ✅ 已是最新版本' : ' ✅ Up to date')
88
+ }
89
+ } catch { /* skip */ }
78
90
  lines.push('')
79
91
 
80
- // 3. Check known vulnerabilities
92
+ // 3. Check known vulnerabilities (remote DB with local fallback)
81
93
  lines.push(zh ? '### 已知漏洞检查' : '### Known Vulnerability Check')
82
94
 
83
- if (currentVersion === 'unknown') {
95
+ let vulnDB = LOCAL_VULNS
96
+ let alerts: { id: string; severity: string; date: string; description_zh: string; description_en: string }[] = []
97
+ let dbSource = 'local'
98
+ try {
99
+ const remote = await fetchVulnDB()
100
+ if (remote.vulns.length > 0) {
101
+ vulnDB = remote.vulns
102
+ dbSource = 'remote'
103
+ }
104
+ alerts = remote.alerts || []
105
+ } catch { /* use local */ }
106
+
107
+ lines.push(zh
108
+ ? ` 数据源: ${dbSource === 'remote' ? `远程漏洞库 (GitHub) — ${vulnDB.length} 条记录` : '本地内置数据库'}`
109
+ : ` Source: ${dbSource === 'remote' ? `Remote vuln DB (GitHub) — ${vulnDB.length} entries` : 'Local built-in database'}`)
110
+
111
+ if (openclawVersion === 'unknown') {
84
112
  lines.push(zh
85
113
  ? ' ⚠️ 无法确定 OpenClaw 版本,请手动检查'
86
114
  : ' ⚠️ Cannot determine OpenClaw version, please check manually')
87
115
  } else {
88
- const affected = KNOWN_VULNS.filter(v => compareVersions(currentVersion, v.affectedBelow) < 0)
116
+ const affected = vulnDB.filter(v => compareVersions(openclawVersion, v.affectedBelow) < 0)
89
117
  if (affected.length === 0) {
90
118
  lines.push(zh
91
119
  ? ' ✅ 当前版本未发现已知漏洞'
@@ -96,11 +124,22 @@ export function registerCheckUpdatesCommand(api: any, config: ShellWardConfig) {
96
124
  const desc = zh ? vuln.description_zh : vuln.description_en
97
125
  lines.push(` ${icon} **${vuln.id}** [${vuln.severity}]: ${desc}`)
98
126
  lines.push(zh
99
- ? ` 影响版本: < ${vuln.affectedBelow} — 请升级 OpenClaw`
100
- : ` Affected: < ${vuln.affectedBelow} — please upgrade OpenClaw`)
127
+ ? ` 影响版本: < ${vuln.affectedBelow} — 请升级`
128
+ : ` Affected: < ${vuln.affectedBelow} — please upgrade`)
101
129
  }
102
130
  }
103
131
  }
132
+
133
+ // Supply chain alerts
134
+ if (alerts.length > 0) {
135
+ lines.push('')
136
+ lines.push(zh ? '### 供应链安全警告' : '### Supply Chain Alerts')
137
+ for (const alert of alerts) {
138
+ const icon = alert.severity === 'CRITICAL' ? '🔴' : '🟡'
139
+ const desc = zh ? alert.description_zh : alert.description_en
140
+ lines.push(` ${icon} **${alert.id}** [${alert.date}]: ${desc}`)
141
+ }
142
+ }
104
143
  lines.push('')
105
144
 
106
145
  // 4. Check Node.js version
@@ -135,17 +174,3 @@ export function registerCheckUpdatesCommand(api: any, config: ShellWardConfig) {
135
174
  },
136
175
  })
137
176
  }
138
-
139
- /**
140
- * Compare two version strings like "2026.3.8" vs "2026.3.6"
141
- * Returns: negative if a < b, 0 if equal, positive if a > b
142
- */
143
- function compareVersions(a: string, b: string): number {
144
- const pa = a.split('.').map(Number)
145
- const pb = b.split('.').map(Number)
146
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
147
- const diff = (pa[i] || 0) - (pb[i] || 0)
148
- if (diff !== 0) return diff
149
- }
150
- return 0
151
- }
@@ -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
- const HOME = process.env.HOME || '~'
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) {
@@ -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/redact/critical/high) |
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 输出脱敏 · L3 工具拦截 · L4 注入检测
41
- L5 安全门 · L6 回复脱敏 · L7 数据流监控 · L8 会话安全`
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/redact/critical/high) |
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
- const HOME = process.env.HOME || '~'
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[] = []
@@ -113,9 +115,9 @@ export function registerScanPluginsCommand(api: any, config: ShellWardConfig) {
113
115
  try {
114
116
  const content = readFileSync(file, 'utf-8')
115
117
  for (const rule of SUSPICIOUS_PATTERNS) {
116
- if (rule.pattern.test(content)) {
117
- // Reset lastIndex for global regexes
118
- rule.pattern.lastIndex = 0
118
+ // Use fresh regex to avoid lastIndex state issues with global patterns
119
+ const regex = new RegExp(rule.pattern.source, rule.pattern.flags)
120
+ if (regex.test(content)) {
119
121
  const relPath = file.replace(plugin.path + '/', '')
120
122
  risks.push(zh
121
123
  ? `⚠️ ${relPath}: ${rule.name} (${rule.risk})`
@@ -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')
@@ -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(process.env.HOME || '~', '.openclaw', 'shellward')
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 redacts = 0
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":"redact"')) redacts++
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 ? '脱敏' : 'Redacted'}: ${redacts} | ${zh ? '严重' : 'Critical'}: ${criticals}`)
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
  }