shellward 0.3.3 → 0.4.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 +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -2
- package/src/commands/check-updates.ts +72 -47
- package/src/commands/scan-plugins.ts +3 -3
- package/src/index.ts +61 -10
- package/src/layers/data-flow-guard.ts +4 -4
- package/src/layers/input-auditor.ts +1 -1
- package/src/layers/prompt-guard.ts +1 -1
- package/src/layers/tool-blocker.ts +2 -2
- package/src/rules/dangerous-commands.ts +1 -1
- package/src/rules/injection-zh.ts +10 -2
- package/src/rules/sensitive-patterns.ts +2 -2
- package/src/types.ts +4 -4
- package/src/update-check.ts +184 -0
- package/vuln-db.json +137 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ShellWard
|
|
2
2
|
|
|
3
|
-
**First bilingual (EN/ZH) security plugin for OpenClaw** — prompt injection detection
|
|
3
|
+
**First bilingual (EN/ZH) security plugin for OpenClaw** — the only plugin with Chinese prompt injection detection & Chinese PII redaction (ID card, phone, bank card). 8 defense layers, zero dependencies.
|
|
4
4
|
|
|
5
5
|
[中文说明](#中文说明) | [English](#english)
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ ShellWard protects your OpenClaw agent with 8 defense layers:
|
|
|
30
30
|
- **Bilingual** — all messages, rules, and prompts in English and Chinese
|
|
31
31
|
- **Chinese PII detection** — ID card (with checksum validation), phone number, bank card (Luhn)
|
|
32
32
|
- **Global PII detection** — API keys, JWT, passwords, US SSN, credit cards, emails
|
|
33
|
-
- **
|
|
33
|
+
- **26 injection rules** — 14 Chinese + 12 English patterns with risk scoring
|
|
34
34
|
- **15 dangerous command rules** — fork bombs, reverse shells, disk formatting, etc. (all case-insensitive)
|
|
35
35
|
- **12 protected path rules** — .env, .ssh, private keys, cloud credentials
|
|
36
36
|
- **Dual mode** — `enforce` (block + log) or `audit` (log only)
|
|
@@ -230,7 +230,7 @@ ShellWard 通过 8 层防御保护你的 OpenClaw 智能体:
|
|
|
230
230
|
- **中英双语** — 所有消息、规则、提示均支持中英文
|
|
231
231
|
- **中国 PII 检测** — 身份证号(含校验位验证)、手机号、银行卡号(Luhn 校验)
|
|
232
232
|
- **国际 PII 检测** — API Key、JWT、密码、美国 SSN、信用卡、邮箱
|
|
233
|
-
- **
|
|
233
|
+
- **26 条注入规则** — 14 条中文 + 12 条英文,带风险评分
|
|
234
234
|
- **双模式** — `enforce`(拦截+记录)或 `audit`(仅记录)
|
|
235
235
|
- **JSONL 审计日志** — 零依赖、支持 grep/jq 查询、100MB 自动轮转
|
|
236
236
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "shellward",
|
|
3
3
|
"name": "ShellWard",
|
|
4
4
|
"description": "First bilingual (EN/ZH) security plugin for OpenClaw — injection detection, dangerous operation blocking, PII/secret redaction (incl. Chinese ID card, phone, bank card), audit logging",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.0",
|
|
6
6
|
"skills": ["./skills"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellward",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "First bilingual (EN/ZH) security plugin for OpenClaw — injection detection, dangerous
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "First bilingual (EN/ZH) security plugin for OpenClaw — Chinese PII detection (ID card/phone/bank card), prompt injection detection (13 ZH + 12 EN rules), dangerous command blocking, audit logging. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"shellward",
|
|
7
7
|
"openclaw",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"src/",
|
|
32
32
|
"skills/",
|
|
33
33
|
"openclaw.plugin.json",
|
|
34
|
+
"vuln-db.json",
|
|
34
35
|
"install.sh",
|
|
35
36
|
"install.ps1",
|
|
36
37
|
"LICENSE",
|
|
@@ -1,34 +1,35 @@
|
|
|
1
|
-
// src/commands/check-updates.ts — /check-updates: check
|
|
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
|
-
//
|
|
10
|
-
//
|
|
11
|
-
const
|
|
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: '
|
|
14
|
-
severity: 'HIGH',
|
|
15
|
-
id: '
|
|
16
|
-
description_zh: '
|
|
17
|
-
description_en: '
|
|
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: '
|
|
21
|
-
severity: '
|
|
22
|
-
id: '
|
|
23
|
-
description_zh: '
|
|
24
|
-
description_en: '
|
|
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.
|
|
28
|
-
severity: 'HIGH',
|
|
29
|
-
id: '
|
|
30
|
-
description_zh: '
|
|
31
|
-
description_en: '
|
|
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
|
-
? '🔄
|
|
42
|
-
: '🔄 Check
|
|
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
|
|
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)
|
|
57
|
+
if (match) openclawVersion = match[1]
|
|
58
58
|
} catch { /* skip */ }
|
|
59
59
|
|
|
60
60
|
lines.push(zh
|
|
61
|
-
? `### OpenClaw 版本: ${
|
|
62
|
-
: `### OpenClaw Version: ${
|
|
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
|
-
|
|
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 =
|
|
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} —
|
|
100
|
-
: ` Affected: < ${vuln.affectedBelow} — please upgrade
|
|
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
|
-
}
|
|
@@ -113,9 +113,9 @@ export function registerScanPluginsCommand(api: any, config: ShellWardConfig) {
|
|
|
113
113
|
try {
|
|
114
114
|
const content = readFileSync(file, 'utf-8')
|
|
115
115
|
for (const rule of SUSPICIOUS_PATTERNS) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
// Use fresh regex to avoid lastIndex state issues with global patterns
|
|
117
|
+
const regex = new RegExp(rule.pattern.source, rule.pattern.flags)
|
|
118
|
+
if (regex.test(content)) {
|
|
119
119
|
const relPath = file.replace(plugin.path + '/', '')
|
|
120
120
|
risks.push(zh
|
|
121
121
|
? `⚠️ ${relPath}: ${rule.name} (${rule.risk})`
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/index.ts — ShellWard plugin entry point (v0.
|
|
1
|
+
// src/index.ts — ShellWard plugin entry point (v0.4.0)
|
|
2
2
|
// 8 defense layers + 6 slash commands + 1 security skill
|
|
3
3
|
|
|
4
4
|
import { AuditLog } from './audit-log'
|
|
@@ -12,8 +12,46 @@ import { setupDataFlowGuard } from './layers/data-flow-guard'
|
|
|
12
12
|
import { setupSessionGuard } from './layers/session-guard'
|
|
13
13
|
import { registerAllCommands } from './commands/index'
|
|
14
14
|
import { DEFAULT_CONFIG, resolveLocale } from './types'
|
|
15
|
+
import { checkForUpdate } from './update-check'
|
|
15
16
|
import type { ShellWardConfig } from './types'
|
|
16
17
|
|
|
18
|
+
const CURRENT_VERSION = '0.4.0'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Wrap api.on so every hook handler gets try-catch protection.
|
|
22
|
+
* If a security hook throws, we log the error and fail-safe:
|
|
23
|
+
* - before_tool_call: block (deny on error, safer than allow)
|
|
24
|
+
* - other hooks: return undefined (don't break the chain)
|
|
25
|
+
*/
|
|
26
|
+
function createSafeApi(api: any, log: AuditLog): any {
|
|
27
|
+
return {
|
|
28
|
+
...api,
|
|
29
|
+
on(hookName: string, handler: Function, opts?: any) {
|
|
30
|
+
const isBlockHook = hookName === 'before_tool_call'
|
|
31
|
+
const wrappedHandler = (event: any) => {
|
|
32
|
+
try {
|
|
33
|
+
return handler(event)
|
|
34
|
+
} catch (err: any) {
|
|
35
|
+
const msg = err?.message || String(err)
|
|
36
|
+
log.write({
|
|
37
|
+
level: 'CRITICAL',
|
|
38
|
+
layer: 'L0',
|
|
39
|
+
action: 'error',
|
|
40
|
+
detail: `Hook ${opts?.name || hookName} threw: ${msg.slice(0, 200)}`,
|
|
41
|
+
})
|
|
42
|
+
try { api.logger.warn(`[ShellWard] Hook error in ${opts?.name || hookName}: ${msg}`) } catch {}
|
|
43
|
+
// Fail-safe: block on security hooks, pass on others
|
|
44
|
+
if (isBlockHook) {
|
|
45
|
+
return { block: true, blockReason: `⚠️ [ShellWard] Internal error in security check — operation blocked for safety` }
|
|
46
|
+
}
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
api.on(hookName, wrappedHandler, opts)
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
17
55
|
function mergeConfig(userConfig: Partial<ShellWardConfig> | undefined): ShellWardConfig {
|
|
18
56
|
if (!userConfig) return { ...DEFAULT_CONFIG }
|
|
19
57
|
|
|
@@ -49,6 +87,7 @@ export default {
|
|
|
49
87
|
const log = new AuditLog(config)
|
|
50
88
|
const enforce = config.mode === 'enforce'
|
|
51
89
|
const locale = resolveLocale(config)
|
|
90
|
+
const safe = createSafeApi(api, log)
|
|
52
91
|
|
|
53
92
|
const modeLabel = locale === 'zh'
|
|
54
93
|
? `模式: ${config.mode}`
|
|
@@ -56,45 +95,46 @@ export default {
|
|
|
56
95
|
api.logger.info(`[ShellWard] Security plugin started (${modeLabel})`)
|
|
57
96
|
|
|
58
97
|
// === Defense Layers (L1-L8) ===
|
|
98
|
+
// All layers use `safe` wrapper — hooks get automatic try-catch + fail-safe
|
|
59
99
|
|
|
60
100
|
// L1: Prompt Guard (before_prompt_build — prependSystemContext for caching)
|
|
61
101
|
if (config.layers.promptGuard) {
|
|
62
|
-
setupPromptGuard(
|
|
102
|
+
setupPromptGuard(safe, config, log)
|
|
63
103
|
}
|
|
64
104
|
|
|
65
105
|
// L2: Output Scanner (tool_result_persist — redact PII in tool results)
|
|
66
106
|
if (config.layers.outputScanner) {
|
|
67
|
-
setupOutputScanner(
|
|
107
|
+
setupOutputScanner(safe, config, log, enforce)
|
|
68
108
|
}
|
|
69
109
|
|
|
70
110
|
// L3: Tool Blocker (before_tool_call — block dangerous commands/paths)
|
|
71
111
|
if (config.layers.toolBlocker) {
|
|
72
|
-
setupToolBlocker(
|
|
112
|
+
setupToolBlocker(safe, config, log, enforce)
|
|
73
113
|
}
|
|
74
114
|
|
|
75
115
|
// L4: Input Auditor (before_tool_call + message_received — injection detection)
|
|
76
116
|
if (config.layers.inputAuditor) {
|
|
77
|
-
setupInputAuditor(
|
|
117
|
+
setupInputAuditor(safe, config, log, enforce)
|
|
78
118
|
}
|
|
79
119
|
|
|
80
|
-
// L5: Security Gate (registerTool — defense in depth)
|
|
120
|
+
// L5: Security Gate (registerTool — defense in depth, uses raw api for registerTool)
|
|
81
121
|
if (config.layers.securityGate) {
|
|
82
122
|
setupSecurityGate(api, config, log, enforce)
|
|
83
123
|
}
|
|
84
124
|
|
|
85
125
|
// L6: Outbound Guard (message_sending — redact PII in LLM responses + canary detection)
|
|
86
126
|
if (config.layers.outboundGuard) {
|
|
87
|
-
setupOutboundGuard(
|
|
127
|
+
setupOutboundGuard(safe, config, log, enforce)
|
|
88
128
|
}
|
|
89
129
|
|
|
90
130
|
// L7: Data Flow Guard (after_tool_call + before_tool_call — anti-exfiltration)
|
|
91
131
|
if (config.layers.dataFlowGuard) {
|
|
92
|
-
setupDataFlowGuard(
|
|
132
|
+
setupDataFlowGuard(safe, config, log, enforce)
|
|
93
133
|
}
|
|
94
134
|
|
|
95
135
|
// L8: Session Guard (session_end + subagent_spawning — lifecycle security)
|
|
96
136
|
if (config.layers.sessionGuard) {
|
|
97
|
-
setupSessionGuard(
|
|
137
|
+
setupSessionGuard(safe, config, log, enforce)
|
|
98
138
|
}
|
|
99
139
|
|
|
100
140
|
// === Slash Commands ===
|
|
@@ -113,7 +153,18 @@ export default {
|
|
|
113
153
|
level: 'INFO',
|
|
114
154
|
layer: 'L1',
|
|
115
155
|
action: 'allow',
|
|
116
|
-
detail: `ShellWard
|
|
156
|
+
detail: `ShellWard v${CURRENT_VERSION} started with ${enabledCount} layers`,
|
|
117
157
|
})
|
|
158
|
+
|
|
159
|
+
// === Non-blocking update check (async, won't delay startup) ===
|
|
160
|
+
// Only notifies ONCE per new version — won't repeat after user has seen it
|
|
161
|
+
checkForUpdate(CURRENT_VERSION).then(result => {
|
|
162
|
+
if (result?.shouldNotify) {
|
|
163
|
+
const msg = locale === 'zh'
|
|
164
|
+
? `[ShellWard] 新版本 v${result.latest} 可用 (当前 v${result.current})。运行 \`openclaw plugins update shellward\` 更新`
|
|
165
|
+
: `[ShellWard] Update available: v${result.latest} (current v${result.current}). Run \`openclaw plugins update shellward\` to update`
|
|
166
|
+
api.logger.warn(msg)
|
|
167
|
+
}
|
|
168
|
+
}).catch(() => { /* silently ignore network errors */ })
|
|
118
169
|
},
|
|
119
170
|
}
|
|
@@ -37,8 +37,8 @@ export function setupDataFlowGuard(
|
|
|
37
37
|
|
|
38
38
|
// === Part 1: Track sensitive file reads via after_tool_call ===
|
|
39
39
|
api.on('after_tool_call', (event: any) => {
|
|
40
|
-
const toolName = (event.toolName || '').toLowerCase()
|
|
41
|
-
const params = event.params
|
|
40
|
+
const toolName = String(event.toolName || '').toLowerCase()
|
|
41
|
+
const params = (event.params && typeof event.params === 'object') ? event.params : {}
|
|
42
42
|
const path = String(params.path || params.file_path || params.filename || '')
|
|
43
43
|
|
|
44
44
|
if (!READ_TOOLS.has(toolName) || !path) return
|
|
@@ -79,8 +79,8 @@ export function setupDataFlowGuard(
|
|
|
79
79
|
|
|
80
80
|
// === Part 2: Block network tool calls if sensitive data was recently read ===
|
|
81
81
|
api.on('before_tool_call', (event: any) => {
|
|
82
|
-
const toolName = (event.toolName || '').toLowerCase()
|
|
83
|
-
const params = event.params
|
|
82
|
+
const toolName = String(event.toolName || '').toLowerCase()
|
|
83
|
+
const params = (event.params && typeof event.params === 'object') ? event.params : {}
|
|
84
84
|
|
|
85
85
|
// 2a. Block network tools if sensitive files were recently read
|
|
86
86
|
if (NETWORK_TOOLS.has(toolName) && sensitiveReads.size > 0) {
|
|
@@ -43,7 +43,7 @@ export function setupInputAuditor(
|
|
|
43
43
|
|
|
44
44
|
// Hook 1: Check tool call arguments for injection
|
|
45
45
|
api.on('before_tool_call', (event: any) => {
|
|
46
|
-
const args: Record<string, any> = event.params
|
|
46
|
+
const args: Record<string, any> = (event.params && typeof event.params === 'object') ? event.params : {}
|
|
47
47
|
const texts = extractTexts(args)
|
|
48
48
|
if (texts.length === 0) return
|
|
49
49
|
|
|
@@ -48,7 +48,7 @@ export function setupPromptGuard(
|
|
|
48
48
|
const locale = resolveLocale(config)
|
|
49
49
|
|
|
50
50
|
// Generate canary token for system prompt exfiltration detection
|
|
51
|
-
canaryToken = '
|
|
51
|
+
canaryToken = 'SW-' + randomBytes(8).toString('hex')
|
|
52
52
|
|
|
53
53
|
const basePrompt = locale === 'zh' ? SECURITY_PROMPT_ZH : SECURITY_PROMPT_EN
|
|
54
54
|
const canaryRule = locale === 'zh'
|
|
@@ -34,9 +34,9 @@ export function setupToolBlocker(
|
|
|
34
34
|
const locale = resolveLocale(config)
|
|
35
35
|
|
|
36
36
|
api.on('before_tool_call', (event: any) => {
|
|
37
|
-
const tool: string = event.toolName || ''
|
|
37
|
+
const tool: string = String(event.toolName || '')
|
|
38
38
|
const toolLower = tool.toLowerCase()
|
|
39
|
-
const args: Record<string, any> = event.params
|
|
39
|
+
const args: Record<string, any> = (event.params && typeof event.params === 'object') ? event.params : {}
|
|
40
40
|
|
|
41
41
|
// 1. Always-blocked tools (case-insensitive)
|
|
42
42
|
if (BLOCKED_TOOLS.has(toolLower)) {
|
|
@@ -71,7 +71,7 @@ export const DANGEROUS_COMMANDS: DangerousCommandRule[] = [
|
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
id: 'fork_bomb',
|
|
74
|
-
pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s
|
|
74
|
+
pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;?|\.\/[a-z]+\s*&\s*\.\/[a-z]+/i,
|
|
75
75
|
description_zh: 'Fork 炸弹(耗尽系统资源)',
|
|
76
76
|
description_en: 'Fork bomb (exhaust system resources)',
|
|
77
77
|
},
|
|
@@ -13,7 +13,7 @@ export const INJECTION_RULES_ZH: InjectionRule[] = [
|
|
|
13
13
|
{
|
|
14
14
|
id: 'zh_new_role',
|
|
15
15
|
name: '角色扮演攻击',
|
|
16
|
-
pattern: '(
|
|
16
|
+
pattern: '(?:你现在是|你的新身份是|从现在起你是|假装你是|请?扮演|你是一个|充当)(?:.*?)(?:助手|AI|机器人|黑客|专家|没有限制)',
|
|
17
17
|
riskScore: 35,
|
|
18
18
|
category: 'role_hijack',
|
|
19
19
|
},
|
|
@@ -34,7 +34,7 @@ export const INJECTION_RULES_ZH: InjectionRule[] = [
|
|
|
34
34
|
{
|
|
35
35
|
id: 'zh_no_restriction',
|
|
36
36
|
name: '解除限制',
|
|
37
|
-
pattern: '(
|
|
37
|
+
pattern: '(?:取消|移除|去掉|关闭|忽略|跳过|绕过|忘记|丢弃)(?:之前|所有|一切|你的|以上|前面)*(?:的)?(?:限制|约束|安全|过滤|审查|规则|指令|设定)',
|
|
38
38
|
riskScore: 40,
|
|
39
39
|
category: 'override',
|
|
40
40
|
},
|
|
@@ -96,4 +96,12 @@ export const INJECTION_RULES_ZH: InjectionRule[] = [
|
|
|
96
96
|
riskScore: 30,
|
|
97
97
|
category: 'injection',
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
id: 'zh_mixed_lang_injection',
|
|
101
|
+
name: '中英混合注入',
|
|
102
|
+
pattern: '(?:please|pls|now)?\\s*(?:ignore|forget|disregard)\\s+.*(?:指令|规则|之前|以上)|(?:忽略|忘记|跳过).*(?:instruction|rule|prompt|previous)',
|
|
103
|
+
flags: 'i',
|
|
104
|
+
riskScore: 40,
|
|
105
|
+
category: 'override',
|
|
106
|
+
},
|
|
99
107
|
]
|
|
@@ -59,7 +59,7 @@ export const SENSITIVE_PATTERNS: SensitivePattern[] = [
|
|
|
59
59
|
{
|
|
60
60
|
id: 'password',
|
|
61
61
|
name: 'Password',
|
|
62
|
-
regex: /(?:password|passwd|pwd)\s*[=:]\s*['"]?\S{6,}['"]?/gi,
|
|
62
|
+
regex: /(?:password|passwd|pwd)\s*[=:]\s*['"]?\S{6,100}['"]?/gi,
|
|
63
63
|
replacement: '[REDACTED:Password]',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
@@ -95,7 +95,7 @@ export const SENSITIVE_PATTERNS: SensitivePattern[] = [
|
|
|
95
95
|
{
|
|
96
96
|
id: 'email',
|
|
97
97
|
name: 'Email Address',
|
|
98
|
-
regex: /[A-Za-z0-9._%+-]
|
|
98
|
+
regex: /[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,255}\.[A-Za-z]{2,10}/g,
|
|
99
99
|
replacement: '[REDACTED:Email]',
|
|
100
100
|
},
|
|
101
101
|
{
|
package/src/types.ts
CHANGED
|
@@ -21,8 +21,8 @@ export type ResolvedLocale = 'zh' | 'en'
|
|
|
21
21
|
export interface AuditEntry {
|
|
22
22
|
ts: string
|
|
23
23
|
level: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO'
|
|
24
|
-
layer: 'L1' | 'L2' | 'L3' | 'L4' | 'L5' | 'L6' | 'L7' | 'L8'
|
|
25
|
-
action: 'block' | 'redact' | 'detect' | 'allow' | 'inject'
|
|
24
|
+
layer: 'L0' | 'L1' | 'L2' | 'L3' | 'L4' | 'L5' | 'L6' | 'L7' | 'L8'
|
|
25
|
+
action: 'block' | 'redact' | 'detect' | 'allow' | 'inject' | 'error'
|
|
26
26
|
detail: string
|
|
27
27
|
tool?: string
|
|
28
28
|
pattern?: string
|
|
@@ -88,6 +88,6 @@ export function resolveLocale(config: ShellWardConfig): ResolvedLocale {
|
|
|
88
88
|
if (config.locale === 'zh') return 'zh'
|
|
89
89
|
if (config.locale === 'en') return 'en'
|
|
90
90
|
// auto detection
|
|
91
|
-
const lang = process.env.LANG || process.env.
|
|
92
|
-
return /
|
|
91
|
+
const lang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANGUAGE || ''
|
|
92
|
+
return /\bzh[_-]|chinese/i.test(lang) ? 'zh' : 'en'
|
|
93
93
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// src/update-check.ts — Non-blocking version check + remote vulnerability DB
|
|
2
|
+
// Uses only Node.js built-in https module (zero dependencies)
|
|
3
|
+
//
|
|
4
|
+
// Anti-annoyance design:
|
|
5
|
+
// - Network check at most once per 24 hours
|
|
6
|
+
// - Same version update only notified ONCE (dismissed = silenced until next version)
|
|
7
|
+
// - Vuln DB cached 24h, /check-updates always shows latest cache
|
|
8
|
+
// - All network failures are silent and cached to avoid repeated timeouts
|
|
9
|
+
|
|
10
|
+
import { get } from 'https'
|
|
11
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
12
|
+
import { join } from 'path'
|
|
13
|
+
|
|
14
|
+
const CACHE_DIR = join(process.env.HOME || '~', '.openclaw', 'shellward')
|
|
15
|
+
const CACHE_FILE = join(CACHE_DIR, 'update-cache.json')
|
|
16
|
+
const VULN_CACHE_FILE = join(CACHE_DIR, 'vuln-db-cache.json')
|
|
17
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
18
|
+
|
|
19
|
+
// Remote sources
|
|
20
|
+
const NPM_REGISTRY_URL = 'https://registry.npmjs.org/shellward/latest'
|
|
21
|
+
const VULN_DB_URL = 'https://raw.githubusercontent.com/jnMetaCode/shellward/main/vuln-db.json'
|
|
22
|
+
|
|
23
|
+
interface UpdateCache {
|
|
24
|
+
lastCheck: number
|
|
25
|
+
latestVersion: string | null
|
|
26
|
+
notifiedVersion: string | null // version user was already notified about — won't repeat
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface VulnEntry {
|
|
30
|
+
affectedBelow: string
|
|
31
|
+
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM'
|
|
32
|
+
id: string
|
|
33
|
+
ghsa?: string
|
|
34
|
+
description_zh: string
|
|
35
|
+
description_en: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SupplyChainAlert {
|
|
39
|
+
id: string
|
|
40
|
+
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM'
|
|
41
|
+
date: string
|
|
42
|
+
description_zh: string
|
|
43
|
+
description_en: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Simple HTTPS GET with redirect support. Timeout: 5s.
|
|
48
|
+
*/
|
|
49
|
+
function httpsGet(url: string): Promise<string> {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const req = get(url, { timeout: 5000 }, (res) => {
|
|
52
|
+
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
53
|
+
get(res.headers.location, { timeout: 5000 }, (res2) => {
|
|
54
|
+
let data = ''
|
|
55
|
+
res2.on('data', (chunk: Buffer) => { data += chunk.toString() })
|
|
56
|
+
res2.on('end', () => resolve(data))
|
|
57
|
+
res2.on('error', reject)
|
|
58
|
+
}).on('error', reject)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
if (res.statusCode !== 200) {
|
|
62
|
+
reject(new Error(`HTTP ${res.statusCode}`))
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
let data = ''
|
|
66
|
+
res.on('data', (chunk: Buffer) => { data += chunk.toString() })
|
|
67
|
+
res.on('end', () => resolve(data))
|
|
68
|
+
res.on('error', reject)
|
|
69
|
+
})
|
|
70
|
+
req.on('error', reject)
|
|
71
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')) })
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check npm for latest version.
|
|
77
|
+
*
|
|
78
|
+
* Returns result with `shouldNotify`:
|
|
79
|
+
* - true = first time seeing this new version, show the message
|
|
80
|
+
* - false = already notified for this version, stay quiet
|
|
81
|
+
* Returns null if check skipped or failed.
|
|
82
|
+
*/
|
|
83
|
+
export async function checkForUpdate(currentVersion: string): Promise<{
|
|
84
|
+
current: string
|
|
85
|
+
latest: string
|
|
86
|
+
updateAvailable: boolean
|
|
87
|
+
shouldNotify: boolean
|
|
88
|
+
} | null> {
|
|
89
|
+
try {
|
|
90
|
+
const cache = readCache<UpdateCache>(CACHE_FILE)
|
|
91
|
+
|
|
92
|
+
// Use cached version if within interval
|
|
93
|
+
let latest: string | null = null
|
|
94
|
+
if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS && cache.latestVersion) {
|
|
95
|
+
latest = cache.latestVersion
|
|
96
|
+
} else {
|
|
97
|
+
// Fetch from npm
|
|
98
|
+
const body = await httpsGet(NPM_REGISTRY_URL)
|
|
99
|
+
const data = JSON.parse(body)
|
|
100
|
+
latest = data.version
|
|
101
|
+
if (!latest || typeof latest !== 'string') return null
|
|
102
|
+
|
|
103
|
+
// Save to cache (preserve notifiedVersion)
|
|
104
|
+
writeCache(CACHE_FILE, {
|
|
105
|
+
lastCheck: Date.now(),
|
|
106
|
+
latestVersion: latest,
|
|
107
|
+
notifiedVersion: cache?.notifiedVersion || null,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const updateAvailable = compareVersions(latest, currentVersion) > 0
|
|
112
|
+
|
|
113
|
+
// Determine if we should notify:
|
|
114
|
+
// Only notify if update available AND we haven't already notified for this exact version
|
|
115
|
+
const alreadyNotified = cache?.notifiedVersion === latest
|
|
116
|
+
const shouldNotify = updateAvailable && !alreadyNotified
|
|
117
|
+
|
|
118
|
+
// If we're going to notify, mark it so we don't repeat
|
|
119
|
+
if (shouldNotify) {
|
|
120
|
+
const freshCache = readCache<UpdateCache>(CACHE_FILE) || { lastCheck: Date.now(), latestVersion: latest, notifiedVersion: null }
|
|
121
|
+
freshCache.notifiedVersion = latest
|
|
122
|
+
writeCache(CACHE_FILE, freshCache)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { current: currentVersion, latest, updateAvailable, shouldNotify }
|
|
126
|
+
} catch {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fetch remote vulnerability database. Cached 24h. Local fallback on failure.
|
|
133
|
+
*/
|
|
134
|
+
export async function fetchVulnDB(): Promise<{ vulns: VulnEntry[]; alerts: SupplyChainAlert[] }> {
|
|
135
|
+
try {
|
|
136
|
+
const cached = readCache<{ lastCheck: number; vulns: VulnEntry[]; alerts: SupplyChainAlert[] }>(VULN_CACHE_FILE)
|
|
137
|
+
if (cached && Date.now() - cached.lastCheck < CHECK_INTERVAL_MS && cached.vulns) {
|
|
138
|
+
return { vulns: cached.vulns, alerts: cached.alerts || [] }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const body = await httpsGet(VULN_DB_URL)
|
|
142
|
+
const data = JSON.parse(body)
|
|
143
|
+
const vulns: VulnEntry[] = Array.isArray(data.vulnerabilities) ? data.vulnerabilities : []
|
|
144
|
+
const alerts: SupplyChainAlert[] = Array.isArray(data.supplyChainAlerts) ? data.supplyChainAlerts : []
|
|
145
|
+
|
|
146
|
+
writeCache(VULN_CACHE_FILE, { lastCheck: Date.now(), vulns, alerts })
|
|
147
|
+
return { vulns, alerts }
|
|
148
|
+
} catch {
|
|
149
|
+
// Cache failure result to avoid repeated timeouts
|
|
150
|
+
const cached = readCache<{ vulns: VulnEntry[]; alerts: SupplyChainAlert[] }>(VULN_CACHE_FILE)
|
|
151
|
+
const fallback = { vulns: cached?.vulns || [], alerts: cached?.alerts || [] }
|
|
152
|
+
writeCache(VULN_CACHE_FILE, { lastCheck: Date.now(), ...fallback })
|
|
153
|
+
return fallback
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Compare semver-like version strings. Positive if a > b, negative if a < b, 0 if equal.
|
|
159
|
+
*/
|
|
160
|
+
export function compareVersions(a: string, b: string): number {
|
|
161
|
+
const pa = a.split('.').map(Number)
|
|
162
|
+
const pb = b.split('.').map(Number)
|
|
163
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
164
|
+
const diff = (pa[i] || 0) - (pb[i] || 0)
|
|
165
|
+
if (diff !== 0) return diff
|
|
166
|
+
}
|
|
167
|
+
return 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ===== Cache helpers =====
|
|
171
|
+
|
|
172
|
+
function readCache<T>(path: string): T | null {
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(readFileSync(path, 'utf-8')) as T
|
|
175
|
+
} catch {
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function writeCache(path: string, data: unknown): void {
|
|
181
|
+
try {
|
|
182
|
+
writeFileSync(path, JSON.stringify(data), { mode: 0o600 })
|
|
183
|
+
} catch { /* ignore */ }
|
|
184
|
+
}
|
package/vuln-db.json
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 2,
|
|
3
|
+
"lastUpdated": "2026-03-12T00:00:00Z",
|
|
4
|
+
"source": "ShellWard Security Team — aggregated from NVD, GitHub Advisories, security research",
|
|
5
|
+
"vulnerabilities": [
|
|
6
|
+
{
|
|
7
|
+
"affectedBelow": "1.0.111",
|
|
8
|
+
"severity": "HIGH",
|
|
9
|
+
"id": "CVE-2025-59536",
|
|
10
|
+
"ghsa": "GHSA-ph6w-f82w-28w6",
|
|
11
|
+
"description_zh": "远程代码执行:恶意仓库通过 Hooks 和 MCP Server 在信任提示前执行任意命令 (CVSS 8.7)",
|
|
12
|
+
"description_en": "RCE via Hooks and MCP Server bypass — arbitrary shell execution before trust dialog (CVSS 8.7)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"affectedBelow": "2.0.65",
|
|
16
|
+
"severity": "MEDIUM",
|
|
17
|
+
"id": "CVE-2026-21852",
|
|
18
|
+
"ghsa": "GHSA-jh7p-qr78-84p7",
|
|
19
|
+
"description_zh": "API 密钥泄露:恶意仓库通过 settings.json 设置 ANTHROPIC_BASE_URL 窃取用户 API Key (CVSS 5.3)",
|
|
20
|
+
"description_en": "API key exfiltration via ANTHROPIC_BASE_URL in settings.json before trust prompt (CVSS 5.3)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"affectedBelow": "2026.2.7",
|
|
24
|
+
"severity": "HIGH",
|
|
25
|
+
"id": "GHSA-66q4-vfjg-2qhh",
|
|
26
|
+
"description_zh": "命令注入:通过目录切换绕过文件写入保护",
|
|
27
|
+
"description_en": "Command injection via directory change bypasses write protection"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"affectedBelow": "2026.2.7",
|
|
31
|
+
"severity": "HIGH",
|
|
32
|
+
"id": "GHSA-mhg7-666j-cqg4",
|
|
33
|
+
"description_zh": "命令注入:通过管道 sed 命令绕过文件写入限制",
|
|
34
|
+
"description_en": "Command injection via piped sed command bypasses file write restrictions"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"affectedBelow": "2026.2.7",
|
|
38
|
+
"severity": "HIGH",
|
|
39
|
+
"id": "GHSA-ff64-7w26-62rf",
|
|
40
|
+
"description_zh": "沙箱逃逸:通过 settings.json 持久化配置注入",
|
|
41
|
+
"description_en": "Sandbox escape via persistent configuration injection in settings.json"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"affectedBelow": "2026.2.4",
|
|
45
|
+
"severity": "HIGH",
|
|
46
|
+
"id": "GHSA-qgqw-h4xq-7w8w",
|
|
47
|
+
"description_zh": "命令注入:find 命令绕过用户审批提示",
|
|
48
|
+
"description_en": "Command injection in find command bypasses user approval prompt"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"affectedBelow": "2026.2.4",
|
|
52
|
+
"severity": "HIGH",
|
|
53
|
+
"id": "GHSA-q728-gf8j-w49r",
|
|
54
|
+
"description_zh": "路径绕过:通过 ZSH clobber 实现任意文件写入",
|
|
55
|
+
"description_en": "Path restriction bypass via ZSH clobber allows arbitrary file writes"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"affectedBelow": "2026.2.4",
|
|
59
|
+
"severity": "HIGH",
|
|
60
|
+
"id": "GHSA-vhw5-3g5m-8ggf",
|
|
61
|
+
"description_zh": "域名验证绕过:自动向攻击者控制的域名发送请求",
|
|
62
|
+
"description_en": "Domain validation bypass allows automatic requests to attacker-controlled domains"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"affectedBelow": "1.0.131",
|
|
66
|
+
"severity": "HIGH",
|
|
67
|
+
"id": "GHSA-xq4m-mc3c-vvg3",
|
|
68
|
+
"description_zh": "命令验证绕过:允许执行任意代码",
|
|
69
|
+
"description_en": "Command validation bypass allows arbitrary code execution"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"affectedBelow": "1.0.120",
|
|
73
|
+
"severity": "HIGH",
|
|
74
|
+
"id": "GHSA-5hhx-v7f6-x7gv",
|
|
75
|
+
"description_zh": "信任提示前执行命令:启动时即执行恶意代码",
|
|
76
|
+
"description_en": "Command execution prior to startup trust dialog"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"affectedBelow": "1.0.120",
|
|
80
|
+
"severity": "HIGH",
|
|
81
|
+
"id": "GHSA-7mv8-j34q-vp7q",
|
|
82
|
+
"description_zh": "sed 命令验证绕过:实现任意文件写入",
|
|
83
|
+
"description_en": "Sed command validation bypass allows arbitrary file writes"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"affectedBelow": "1.0.111",
|
|
87
|
+
"severity": "HIGH",
|
|
88
|
+
"id": "GHSA-2jjv-qf24-vfm4",
|
|
89
|
+
"description_zh": "特定 Yarn 版本下插件自动加载导致任意代码执行",
|
|
90
|
+
"description_en": "Arbitrary code execution via plugin autoloading with specific Yarn versions"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"affectedBelow": "1.0.102",
|
|
94
|
+
"severity": "HIGH",
|
|
95
|
+
"id": "GHSA-j4h9-wv2m-wrf7",
|
|
96
|
+
"description_zh": "恶意 git email 配置导致任意代码执行",
|
|
97
|
+
"description_en": "Arbitrary code execution caused by maliciously configured git email"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"affectedBelow": "1.0.102",
|
|
101
|
+
"severity": "HIGH",
|
|
102
|
+
"id": "GHSA-qxfv-fcpc-w36x",
|
|
103
|
+
"description_zh": "rg 命令注入绕过用户审批提示",
|
|
104
|
+
"description_en": "Command injection in rg command bypassed user approval prompt"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"affectedBelow": "1.0.87",
|
|
108
|
+
"severity": "HIGH",
|
|
109
|
+
"id": "GHSA-x5gv-jw7f-j6xj",
|
|
110
|
+
"description_zh": "默认允许列表过于宽松:未授权文件读取和网络外泄",
|
|
111
|
+
"description_en": "Permissive default allowlist enables unauthorized file read and network exfiltration"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"affectedBelow": "1.0.82",
|
|
115
|
+
"severity": "HIGH",
|
|
116
|
+
"id": "GHSA-x56v-x2h6-7j34",
|
|
117
|
+
"description_zh": "echo 命令注入绕过用户审批提示",
|
|
118
|
+
"description_en": "Command injection in echo command bypassed user approval prompt"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"affectedBelow": "1.0.3",
|
|
122
|
+
"severity": "HIGH",
|
|
123
|
+
"id": "GHSA-9f65-56v6-gxw7",
|
|
124
|
+
"description_zh": "IDE 扩展 WebSocket 接受任意来源连接",
|
|
125
|
+
"description_en": "IDE extensions allow websocket connections from arbitrary origins"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"supplyChainAlerts": [
|
|
129
|
+
{
|
|
130
|
+
"id": "SW-ALERT-2026-001",
|
|
131
|
+
"severity": "HIGH",
|
|
132
|
+
"date": "2026-02-15",
|
|
133
|
+
"description_zh": "SANDWORM_MODE 供应链攻击:19 个恶意 npm 包伪装为 Claude Code 等 AI 工具,植入恶意 MCP Server 窃取 SSH 密钥和凭证",
|
|
134
|
+
"description_en": "SANDWORM_MODE supply chain attack: 19 malicious npm packages impersonating Claude Code and AI tools, deploying rogue MCP servers to exfiltrate SSH keys and credentials"
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|