shellward 0.3.2
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/LICENSE +190 -0
- package/README.md +319 -0
- package/install.ps1 +84 -0
- package/install.sh +201 -0
- package/openclaw.plugin.json +47 -0
- package/package.json +39 -0
- package/skills/security-guide/SKILL.md +68 -0
- package/src/audit-log.ts +57 -0
- package/src/commands/audit.ts +79 -0
- package/src/commands/check-updates.ts +151 -0
- package/src/commands/harden.ts +192 -0
- package/src/commands/index.ts +57 -0
- package/src/commands/scan-plugins.ts +187 -0
- package/src/commands/security.ts +113 -0
- package/src/index.ts +119 -0
- package/src/layers/data-flow-guard.ts +159 -0
- package/src/layers/input-auditor.ts +171 -0
- package/src/layers/outbound-guard.ts +67 -0
- package/src/layers/output-scanner.ts +94 -0
- package/src/layers/prompt-guard.ts +71 -0
- package/src/layers/security-gate.ts +131 -0
- package/src/layers/session-guard.ts +46 -0
- package/src/layers/tool-blocker.ts +182 -0
- package/src/rules/dangerous-commands.ts +105 -0
- package/src/rules/injection-en.ts +102 -0
- package/src/rules/injection-zh.ts +99 -0
- package/src/rules/protected-paths.ts +78 -0
- package/src/rules/sensitive-patterns.ts +204 -0
- package/src/types.ts +93 -0
package/install.sh
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ShellWard One-Click Installer / 一键安装脚本
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/jnMetaCode/shellward/main/install.sh | bash
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Colors
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
BLUE='\033[0;34m'
|
|
12
|
+
CYAN='\033[0;36m'
|
|
13
|
+
NC='\033[0m'
|
|
14
|
+
|
|
15
|
+
# Detect language
|
|
16
|
+
is_zh() {
|
|
17
|
+
local lang="${LANG:-}${LANGUAGE:-}${LC_ALL:-}"
|
|
18
|
+
[[ "$lang" == *zh* ]]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if is_zh; then
|
|
22
|
+
MSG_BANNER="🛡️ ShellWard 安全插件 一键安装"
|
|
23
|
+
MSG_CHECKING="正在检查环境..."
|
|
24
|
+
MSG_NODE_MISSING="❌ 未找到 Node.js (需要 v18+)。请先安装: https://nodejs.org"
|
|
25
|
+
MSG_NODE_OLD="❌ Node.js 版本过低 (当前: %s, 需要: v18+)。请升级: https://nodejs.org"
|
|
26
|
+
MSG_NODE_OK="✅ Node.js %s"
|
|
27
|
+
MSG_OC_MISSING="❌ 未找到 OpenClaw。请先安装: npm install -g openclaw"
|
|
28
|
+
MSG_OC_OK="✅ OpenClaw %s"
|
|
29
|
+
MSG_INSTALLING="正在安装 ShellWard..."
|
|
30
|
+
MSG_ALREADY="⚠️ ShellWard 已安装,正在更新..."
|
|
31
|
+
MSG_CLONE="正在下载 ShellWard..."
|
|
32
|
+
MSG_NPM="通过 npm 安装..."
|
|
33
|
+
MSG_REGISTER="正在注册插件..."
|
|
34
|
+
MSG_SUCCESS="🎉 安装成功!"
|
|
35
|
+
MSG_VERIFY="验证安装..."
|
|
36
|
+
MSG_USAGE="使用方法:"
|
|
37
|
+
MSG_CMD1=" openclaw agent --local -m \"你好\" # 启动安全防护的 Agent"
|
|
38
|
+
MSG_CMD2=" /security # 查看安全状态"
|
|
39
|
+
MSG_CMD3=" /audit # 查看审计日志"
|
|
40
|
+
MSG_CMD4=" /harden # 安全扫描"
|
|
41
|
+
MSG_DOCS="文档: https://github.com/jnMetaCode/shellward"
|
|
42
|
+
MSG_MODE_TITLE="选择安装方式:"
|
|
43
|
+
MSG_MODE_1=" 1) npm 安装 (推荐,自动更新)"
|
|
44
|
+
MSG_MODE_2=" 2) 源码安装 (离线可用)"
|
|
45
|
+
MSG_MODE_PROMPT="请输入 [1/2] (默认 1): "
|
|
46
|
+
MSG_DONE="安装完成!ShellWard 将在下次启动 OpenClaw 时自动加载。"
|
|
47
|
+
MSG_CONFIG="配置文件 (可选):"
|
|
48
|
+
else
|
|
49
|
+
MSG_BANNER="🛡️ ShellWard Security Plugin — One-Click Install"
|
|
50
|
+
MSG_CHECKING="Checking environment..."
|
|
51
|
+
MSG_NODE_MISSING="❌ Node.js not found (v18+ required). Install: https://nodejs.org"
|
|
52
|
+
MSG_NODE_OLD="❌ Node.js too old (current: %s, need: v18+). Upgrade: https://nodejs.org"
|
|
53
|
+
MSG_NODE_OK="✅ Node.js %s"
|
|
54
|
+
MSG_OC_MISSING="❌ OpenClaw not found. Install: npm install -g openclaw"
|
|
55
|
+
MSG_OC_OK="✅ OpenClaw %s"
|
|
56
|
+
MSG_INSTALLING="Installing ShellWard..."
|
|
57
|
+
MSG_ALREADY="⚠️ ShellWard already installed, updating..."
|
|
58
|
+
MSG_CLONE="Downloading ShellWard..."
|
|
59
|
+
MSG_NPM="Installing via npm..."
|
|
60
|
+
MSG_REGISTER="Registering plugin..."
|
|
61
|
+
MSG_SUCCESS="🎉 Installation successful!"
|
|
62
|
+
MSG_VERIFY="Verifying installation..."
|
|
63
|
+
MSG_USAGE="Usage:"
|
|
64
|
+
MSG_CMD1=" openclaw agent --local -m \"hello\" # Start agent with security"
|
|
65
|
+
MSG_CMD2=" /security # View security status"
|
|
66
|
+
MSG_CMD3=" /audit # View audit log"
|
|
67
|
+
MSG_CMD4=" /harden # Security scan"
|
|
68
|
+
MSG_DOCS="Docs: https://github.com/jnMetaCode/shellward"
|
|
69
|
+
MSG_MODE_TITLE="Choose install method:"
|
|
70
|
+
MSG_MODE_1=" 1) npm install (recommended, auto-update)"
|
|
71
|
+
MSG_MODE_2=" 2) source install (works offline)"
|
|
72
|
+
MSG_MODE_PROMPT="Enter [1/2] (default 1): "
|
|
73
|
+
MSG_DONE="Done! ShellWard will auto-load next time OpenClaw starts."
|
|
74
|
+
MSG_CONFIG="Configuration (optional):"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
echo ""
|
|
78
|
+
echo -e "${CYAN}══════════════════════════════════════════${NC}"
|
|
79
|
+
echo -e "${CYAN} $MSG_BANNER${NC}"
|
|
80
|
+
echo -e "${CYAN}══════════════════════════════════════════${NC}"
|
|
81
|
+
echo ""
|
|
82
|
+
|
|
83
|
+
# --- Step 1: Check environment ---
|
|
84
|
+
echo -e "${BLUE}$MSG_CHECKING${NC}"
|
|
85
|
+
|
|
86
|
+
# Check Node.js
|
|
87
|
+
if ! command -v node &>/dev/null; then
|
|
88
|
+
echo -e "${RED}$MSG_NODE_MISSING${NC}"
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
NODE_VER=$(node -v)
|
|
93
|
+
NODE_MAJOR=$(echo "$NODE_VER" | sed 's/v//' | cut -d. -f1)
|
|
94
|
+
if [ "$NODE_MAJOR" -lt 18 ]; then
|
|
95
|
+
printf "${RED}$MSG_NODE_OLD${NC}\n" "$NODE_VER"
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
printf "${GREEN}$MSG_NODE_OK${NC}\n" "$NODE_VER"
|
|
99
|
+
|
|
100
|
+
# Check OpenClaw
|
|
101
|
+
if ! command -v openclaw &>/dev/null; then
|
|
102
|
+
echo -e "${RED}$MSG_OC_MISSING${NC}"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
OC_VER=$(openclaw --version 2>/dev/null | head -1 || echo "unknown")
|
|
107
|
+
printf "${GREEN}$MSG_OC_OK${NC}\n" "$OC_VER"
|
|
108
|
+
echo ""
|
|
109
|
+
|
|
110
|
+
# --- Step 2: Choose install method ---
|
|
111
|
+
INSTALL_METHOD="1"
|
|
112
|
+
if [ -t 0 ]; then
|
|
113
|
+
echo -e "${YELLOW}$MSG_MODE_TITLE${NC}"
|
|
114
|
+
echo -e "$MSG_MODE_1"
|
|
115
|
+
echo -e "$MSG_MODE_2"
|
|
116
|
+
printf "$MSG_MODE_PROMPT"
|
|
117
|
+
read -r choice
|
|
118
|
+
if [ "$choice" = "2" ]; then
|
|
119
|
+
INSTALL_METHOD="2"
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
122
|
+
echo ""
|
|
123
|
+
|
|
124
|
+
# --- Step 3: Install ---
|
|
125
|
+
PLUGIN_DIR="${HOME}/.openclaw/plugins/shellward"
|
|
126
|
+
|
|
127
|
+
if [ -d "$PLUGIN_DIR" ]; then
|
|
128
|
+
echo -e "${YELLOW}$MSG_ALREADY${NC}"
|
|
129
|
+
rm -rf "$PLUGIN_DIR"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
mkdir -p "${HOME}/.openclaw/plugins"
|
|
133
|
+
|
|
134
|
+
clone_install() {
|
|
135
|
+
echo -e "${BLUE}$MSG_CLONE${NC}"
|
|
136
|
+
git clone --depth 1 https://github.com/jnMetaCode/shellward.git "$PLUGIN_DIR" 2>/dev/null
|
|
137
|
+
rm -rf "$PLUGIN_DIR/.git"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if [ "$INSTALL_METHOD" = "1" ]; then
|
|
141
|
+
echo -e "${BLUE}$MSG_NPM${NC}"
|
|
142
|
+
cd /tmp
|
|
143
|
+
rm -rf shellward-npm-install
|
|
144
|
+
mkdir shellward-npm-install && cd shellward-npm-install
|
|
145
|
+
if npm pack shellward 2>/dev/null; then
|
|
146
|
+
tar xzf shellward-*.tgz
|
|
147
|
+
mv package "$PLUGIN_DIR"
|
|
148
|
+
# Verify npm package has correct structure
|
|
149
|
+
if [ ! -f "$PLUGIN_DIR/src/index.ts" ]; then
|
|
150
|
+
echo -e "${YELLOW}npm package outdated, switching to git...${NC}"
|
|
151
|
+
rm -rf "$PLUGIN_DIR"
|
|
152
|
+
clone_install
|
|
153
|
+
fi
|
|
154
|
+
else
|
|
155
|
+
clone_install
|
|
156
|
+
fi
|
|
157
|
+
cd /tmp && rm -rf shellward-npm-install
|
|
158
|
+
else
|
|
159
|
+
clone_install
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# Fix ownership
|
|
163
|
+
if [ "$(id -u)" = "0" ]; then
|
|
164
|
+
chown -R root:root "$PLUGIN_DIR"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
echo ""
|
|
168
|
+
|
|
169
|
+
# --- Step 4: Verify ---
|
|
170
|
+
echo -e "${BLUE}$MSG_VERIFY${NC}"
|
|
171
|
+
if [ -f "$PLUGIN_DIR/src/index.ts" ] && [ -f "$PLUGIN_DIR/openclaw.plugin.json" ]; then
|
|
172
|
+
echo -e "${GREEN}$MSG_SUCCESS${NC}"
|
|
173
|
+
else
|
|
174
|
+
echo -e "${RED}Installation failed — files missing${NC}"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
echo ""
|
|
179
|
+
echo -e "${CYAN}══════════════════════════════════════════${NC}"
|
|
180
|
+
echo -e "${GREEN}$MSG_DONE${NC}"
|
|
181
|
+
echo ""
|
|
182
|
+
echo -e "${YELLOW}$MSG_USAGE${NC}"
|
|
183
|
+
echo -e "$MSG_CMD1"
|
|
184
|
+
echo -e "$MSG_CMD2"
|
|
185
|
+
echo -e "$MSG_CMD3"
|
|
186
|
+
echo -e "$MSG_CMD4"
|
|
187
|
+
echo ""
|
|
188
|
+
echo -e "${YELLOW}$MSG_CONFIG${NC}"
|
|
189
|
+
cat <<'CONF'
|
|
190
|
+
# ~/.openclaw/openclaw.json → plugins section:
|
|
191
|
+
{
|
|
192
|
+
"plugins": {
|
|
193
|
+
"entries": {
|
|
194
|
+
"shellward": { "enabled": true }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
CONF
|
|
199
|
+
echo ""
|
|
200
|
+
echo -e "${BLUE}$MSG_DOCS${NC}"
|
|
201
|
+
echo ""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "shellward",
|
|
3
|
+
"name": "ShellWard",
|
|
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.3.2",
|
|
6
|
+
"skills": ["./skills"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"properties": {
|
|
11
|
+
"mode": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": ["enforce", "audit"],
|
|
14
|
+
"default": "enforce",
|
|
15
|
+
"description": "enforce = block + log, audit = log only"
|
|
16
|
+
},
|
|
17
|
+
"locale": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": ["auto", "zh", "en"],
|
|
20
|
+
"default": "auto",
|
|
21
|
+
"description": "auto = detect from system language"
|
|
22
|
+
},
|
|
23
|
+
"layers": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"promptGuard": { "type": "boolean", "default": true },
|
|
27
|
+
"outputScanner": { "type": "boolean", "default": true },
|
|
28
|
+
"toolBlocker": { "type": "boolean", "default": true },
|
|
29
|
+
"inputAuditor": { "type": "boolean", "default": true },
|
|
30
|
+
"securityGate": { "type": "boolean", "default": true },
|
|
31
|
+
"outboundGuard": { "type": "boolean", "default": true },
|
|
32
|
+
"dataFlowGuard": { "type": "boolean", "default": true },
|
|
33
|
+
"sessionGuard": { "type": "boolean", "default": true }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"injectionThreshold": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"default": 60,
|
|
39
|
+
"description": "Injection risk score threshold (0-100) to trigger block/alert"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"uiHints": {
|
|
44
|
+
"mode": { "label": "Security Mode", "help": "enforce blocks dangerous operations; audit only logs them" },
|
|
45
|
+
"locale": { "label": "Language", "help": "auto detects from system LANG; zh for Chinese; en for English" }
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shellward",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "First bilingual (EN/ZH) security plugin for OpenClaw — injection detection, dangerous operation blocking, PII/secret redaction, audit logging",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openclaw",
|
|
7
|
+
"security",
|
|
8
|
+
"plugin",
|
|
9
|
+
"injection-detection",
|
|
10
|
+
"pii-redaction",
|
|
11
|
+
"chinese",
|
|
12
|
+
"bilingual"
|
|
13
|
+
],
|
|
14
|
+
"author": "jnMetaCode",
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/jnMetaCode/clawguard"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "src/index.ts",
|
|
22
|
+
"openclaw": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./src/index.ts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src/",
|
|
29
|
+
"skills/",
|
|
30
|
+
"openclaw.plugin.json",
|
|
31
|
+
"install.sh",
|
|
32
|
+
"install.ps1",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-guide
|
|
3
|
+
description: OpenClaw 安全部署指南 / Security deployment guide — help users secure their OpenClaw installation
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# ShellWard Security Deployment Guide / 安全部署指南
|
|
9
|
+
|
|
10
|
+
When the user invokes this skill, provide a complete security deployment checklist based on the following best practices. Check the current system state using available tools and give actionable recommendations.
|
|
11
|
+
|
|
12
|
+
## Security Checklist
|
|
13
|
+
|
|
14
|
+
### 1. Network Control / 网络控制
|
|
15
|
+
- Check if OpenClaw gateway port (19000/19001) is exposed to public network
|
|
16
|
+
- Recommend binding to 127.0.0.1 or using a reverse proxy with authentication
|
|
17
|
+
- Suggest firewall rules: `ufw allow from 127.0.0.1 to any port 19000`
|
|
18
|
+
- For cloud servers: check security group rules
|
|
19
|
+
|
|
20
|
+
### 2. Container Isolation / 容器隔离
|
|
21
|
+
- Recommend running OpenClaw in Docker with restricted capabilities:
|
|
22
|
+
```
|
|
23
|
+
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE \
|
|
24
|
+
--read-only --tmpfs /tmp \
|
|
25
|
+
-u 1000:1000 \
|
|
26
|
+
openclaw
|
|
27
|
+
```
|
|
28
|
+
- Suggest resource limits: `--memory=2g --cpus=1`
|
|
29
|
+
- Mount only necessary directories
|
|
30
|
+
|
|
31
|
+
### 3. Credential Management / 凭证管理
|
|
32
|
+
- Scan for plaintext secrets in .env, .bashrc, environment variables
|
|
33
|
+
- Recommend using a secret manager (Vault, doppler, etc.)
|
|
34
|
+
- Check file permissions on sensitive files (should be 0600)
|
|
35
|
+
- Suggest `chmod 600 ~/.env ~/.ssh/* ~/.aws/credentials`
|
|
36
|
+
|
|
37
|
+
### 4. Audit Logging / 审计日志
|
|
38
|
+
- Verify ShellWard audit log is active at ~/.openclaw/shellward/audit.jsonl
|
|
39
|
+
- Show recent security events
|
|
40
|
+
- Recommend log rotation and backup strategy
|
|
41
|
+
- Suggest sending critical events to external SIEM
|
|
42
|
+
|
|
43
|
+
### 5. Plugin Security / 插件安全
|
|
44
|
+
- List all installed plugins and check for known risks
|
|
45
|
+
- Disable auto-update for plugins
|
|
46
|
+
- Only install from trusted sources
|
|
47
|
+
- Scan plugin code for suspicious patterns
|
|
48
|
+
|
|
49
|
+
### 6. Patch Management / 补丁管理
|
|
50
|
+
- Check current OpenClaw version
|
|
51
|
+
- Report known vulnerabilities for current version
|
|
52
|
+
- Recommend upgrade path
|
|
53
|
+
- Check Node.js version (must be >= 22.12)
|
|
54
|
+
|
|
55
|
+
## Available Commands
|
|
56
|
+
Remind the user about ShellWard's quick commands:
|
|
57
|
+
- `/security` — Full security status overview
|
|
58
|
+
- `/audit [count] [filter]` — View audit log
|
|
59
|
+
- `/harden` — Scan for issues, `/harden fix` to auto-fix
|
|
60
|
+
- `/scan-plugins` — Scan plugins for security risks
|
|
61
|
+
- `/check-updates` — Check versions and vulnerabilities
|
|
62
|
+
|
|
63
|
+
## Response Style
|
|
64
|
+
- Be concise and actionable
|
|
65
|
+
- Use the user's language (detect from their message)
|
|
66
|
+
- Prioritize critical issues first
|
|
67
|
+
- For each issue, provide the exact command to fix it
|
|
68
|
+
- Ask for confirmation before executing destructive operations
|
package/src/audit-log.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/audit-log.ts — JSONL audit log, zero dependencies
|
|
2
|
+
|
|
3
|
+
import { appendFileSync, chmodSync, mkdirSync, renameSync, statSync, writeFileSync } from 'fs'
|
|
4
|
+
import { join } from 'path'
|
|
5
|
+
import type { AuditEntry, ShellWardConfig } from './types'
|
|
6
|
+
|
|
7
|
+
const LOG_DIR = join(process.env.HOME || '~', '.openclaw', 'shellward')
|
|
8
|
+
const LOG_FILE = join(LOG_DIR, 'audit.jsonl')
|
|
9
|
+
const MAX_SIZE_BYTES = 100 * 1024 * 1024 // 100 MB
|
|
10
|
+
|
|
11
|
+
export class AuditLog {
|
|
12
|
+
private config: ShellWardConfig
|
|
13
|
+
private rotating = false
|
|
14
|
+
|
|
15
|
+
constructor(config: ShellWardConfig) {
|
|
16
|
+
this.config = config
|
|
17
|
+
try {
|
|
18
|
+
mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 })
|
|
19
|
+
// Ensure log file exists with restricted permissions (owner-only)
|
|
20
|
+
try {
|
|
21
|
+
statSync(LOG_FILE)
|
|
22
|
+
} catch {
|
|
23
|
+
writeFileSync(LOG_FILE, '', { mode: 0o600 })
|
|
24
|
+
}
|
|
25
|
+
} catch { /* directory may already exist */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
write(entry: Omit<AuditEntry, 'ts' | 'mode'>): void {
|
|
29
|
+
try {
|
|
30
|
+
const record: AuditEntry = {
|
|
31
|
+
ts: new Date().toISOString(),
|
|
32
|
+
mode: this.config.mode,
|
|
33
|
+
...entry,
|
|
34
|
+
}
|
|
35
|
+
appendFileSync(LOG_FILE, JSON.stringify(record) + '\n', { mode: 0o600 })
|
|
36
|
+
this.rotateIfNeeded()
|
|
37
|
+
} catch (e: any) {
|
|
38
|
+
// Log failure must not break plugin, but warn via stderr
|
|
39
|
+
try { process.stderr.write(`[ShellWard] audit log write failed: ${e?.message}\n`) } catch {}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private rotateIfNeeded(): void {
|
|
44
|
+
if (this.rotating) return
|
|
45
|
+
try {
|
|
46
|
+
const stat = statSync(LOG_FILE)
|
|
47
|
+
if (stat.size > MAX_SIZE_BYTES) {
|
|
48
|
+
this.rotating = true
|
|
49
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
|
50
|
+
renameSync(LOG_FILE, `${LOG_FILE}.${ts}.bak`)
|
|
51
|
+
writeFileSync(LOG_FILE, '', { mode: 0o600 })
|
|
52
|
+
}
|
|
53
|
+
} catch { /* ignore */ } finally {
|
|
54
|
+
this.rotating = false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/commands/audit.ts — /audit command: view recent audit log entries
|
|
2
|
+
|
|
3
|
+
import { readFileSync, statSync } from 'fs'
|
|
4
|
+
import { join } from 'path'
|
|
5
|
+
import type { ShellWardConfig } from '../types'
|
|
6
|
+
import { resolveLocale } from '../types'
|
|
7
|
+
|
|
8
|
+
const LOG_FILE = join(process.env.HOME || '~', '.openclaw', 'shellward', 'audit.jsonl')
|
|
9
|
+
|
|
10
|
+
export function registerAuditCommand(api: any, config: ShellWardConfig) {
|
|
11
|
+
const locale = resolveLocale(config)
|
|
12
|
+
|
|
13
|
+
api.registerCommand({
|
|
14
|
+
name: 'audit',
|
|
15
|
+
description: locale === 'zh'
|
|
16
|
+
? '📋 查看 ShellWard 审计日志 (用法: /audit [数量] [block|redact|critical])'
|
|
17
|
+
: '📋 View ShellWard audit log (usage: /audit [count] [block|redact|critical])',
|
|
18
|
+
acceptsArgs: true,
|
|
19
|
+
handler: (ctx: any) => {
|
|
20
|
+
const zh = locale === 'zh'
|
|
21
|
+
const args = (ctx.args || '').trim().split(/\s+/).filter(Boolean)
|
|
22
|
+
|
|
23
|
+
// Parse args: count and optional filter
|
|
24
|
+
let count = 20
|
|
25
|
+
let filter = ''
|
|
26
|
+
for (const arg of args) {
|
|
27
|
+
if (/^\d+$/.test(arg)) {
|
|
28
|
+
count = Math.min(parseInt(arg), 100)
|
|
29
|
+
} else {
|
|
30
|
+
filter = arg.toLowerCase()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
statSync(LOG_FILE)
|
|
36
|
+
} catch {
|
|
37
|
+
return { text: zh ? '⚠️ 审计日志文件不存在,尚无安全事件记录。' : '⚠️ Audit log not found. No security events recorded yet.' }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const content = readFileSync(LOG_FILE, 'utf-8')
|
|
41
|
+
let lines = content.trim().split('\n').filter(Boolean)
|
|
42
|
+
|
|
43
|
+
// Apply filter
|
|
44
|
+
if (filter === 'block') {
|
|
45
|
+
lines = lines.filter(l => l.includes('"action":"block"'))
|
|
46
|
+
} else if (filter === 'redact') {
|
|
47
|
+
lines = lines.filter(l => l.includes('"action":"redact"'))
|
|
48
|
+
} else if (filter === 'critical') {
|
|
49
|
+
lines = lines.filter(l => l.includes('"level":"CRITICAL"'))
|
|
50
|
+
} else if (filter === 'high') {
|
|
51
|
+
lines = lines.filter(l => l.includes('"level":"HIGH"') || l.includes('"level":"CRITICAL"'))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Get last N entries
|
|
55
|
+
const recent = lines.slice(-count)
|
|
56
|
+
|
|
57
|
+
if (recent.length === 0) {
|
|
58
|
+
return { text: zh ? '✅ 没有匹配的审计事件。' : '✅ No matching audit events.' }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const header = zh
|
|
62
|
+
? `📋 **审计日志** (最近 ${recent.length} 条${filter ? `, 过滤: ${filter}` : ''})`
|
|
63
|
+
: `📋 **Audit Log** (last ${recent.length} entries${filter ? `, filter: ${filter}` : ''})`
|
|
64
|
+
|
|
65
|
+
const formatted = recent.map(line => {
|
|
66
|
+
try {
|
|
67
|
+
const e = JSON.parse(line)
|
|
68
|
+
const icon = e.action === 'block' ? '🚫' : e.action === 'redact' ? '🔒' : e.level === 'CRITICAL' ? '🔴' : 'ℹ️'
|
|
69
|
+
const time = e.ts?.slice(11, 19) || '??:??:??'
|
|
70
|
+
return `${icon} \`${time}\` **${e.layer}** ${e.action}: ${e.detail?.slice(0, 80) || ''}${e.pattern ? ` [${e.pattern}]` : ''}`
|
|
71
|
+
} catch {
|
|
72
|
+
return line.slice(0, 100)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return { text: `${header}\n\n${formatted.join('\n')}` }
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/commands/check-updates.ts — /check-updates: check OpenClaw version and known vulnerabilities
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'child_process'
|
|
4
|
+
import { existsSync, readFileSync } from 'fs'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import type { ShellWardConfig } from '../types'
|
|
7
|
+
import { resolveLocale } from '../types'
|
|
8
|
+
|
|
9
|
+
// Known vulnerability database (hardcoded, updated with plugin releases)
|
|
10
|
+
// Format: { version_range, severity, cve, description_zh, description_en }
|
|
11
|
+
const KNOWN_VULNS = [
|
|
12
|
+
{
|
|
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',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
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',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
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',
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
export function registerCheckUpdatesCommand(api: any, config: ShellWardConfig) {
|
|
36
|
+
const locale = resolveLocale(config)
|
|
37
|
+
|
|
38
|
+
api.registerCommand({
|
|
39
|
+
name: 'check-updates',
|
|
40
|
+
description: locale === 'zh'
|
|
41
|
+
? '🔄 检查 OpenClaw 版本和已知漏洞'
|
|
42
|
+
: '🔄 Check OpenClaw version and known vulnerabilities',
|
|
43
|
+
acceptsArgs: false,
|
|
44
|
+
handler: () => {
|
|
45
|
+
const zh = locale === 'zh'
|
|
46
|
+
const lines: string[] = []
|
|
47
|
+
|
|
48
|
+
lines.push(zh ? '🔄 **版本与漏洞检查**' : '🔄 **Version & Vulnerability Check**')
|
|
49
|
+
lines.push('')
|
|
50
|
+
|
|
51
|
+
// 1. Get OpenClaw version
|
|
52
|
+
let currentVersion = 'unknown'
|
|
53
|
+
try {
|
|
54
|
+
const out = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString().trim()
|
|
55
|
+
// Extract version like "2026.3.8"
|
|
56
|
+
const match = out.match(/(\d{4}\.\d+\.\d+)/)
|
|
57
|
+
if (match) currentVersion = match[1]
|
|
58
|
+
} catch { /* skip */ }
|
|
59
|
+
|
|
60
|
+
lines.push(zh
|
|
61
|
+
? `### OpenClaw 版本: ${currentVersion}`
|
|
62
|
+
: `### OpenClaw Version: ${currentVersion}`)
|
|
63
|
+
lines.push('')
|
|
64
|
+
|
|
65
|
+
// 2. Check ShellWard version
|
|
66
|
+
let shellwardVersion = 'unknown'
|
|
67
|
+
try {
|
|
68
|
+
const pkgPath = join(__dirname, '../../package.json')
|
|
69
|
+
if (existsSync(pkgPath)) {
|
|
70
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
71
|
+
shellwardVersion = pkg.version || 'unknown'
|
|
72
|
+
}
|
|
73
|
+
} catch { /* skip */ }
|
|
74
|
+
|
|
75
|
+
lines.push(zh
|
|
76
|
+
? `### ShellWard 版本: ${shellwardVersion}`
|
|
77
|
+
: `### ShellWard Version: ${shellwardVersion}`)
|
|
78
|
+
lines.push('')
|
|
79
|
+
|
|
80
|
+
// 3. Check known vulnerabilities
|
|
81
|
+
lines.push(zh ? '### 已知漏洞检查' : '### Known Vulnerability Check')
|
|
82
|
+
|
|
83
|
+
if (currentVersion === 'unknown') {
|
|
84
|
+
lines.push(zh
|
|
85
|
+
? ' ⚠️ 无法确定 OpenClaw 版本,请手动检查'
|
|
86
|
+
: ' ⚠️ Cannot determine OpenClaw version, please check manually')
|
|
87
|
+
} else {
|
|
88
|
+
const affected = KNOWN_VULNS.filter(v => compareVersions(currentVersion, v.affectedBelow) < 0)
|
|
89
|
+
if (affected.length === 0) {
|
|
90
|
+
lines.push(zh
|
|
91
|
+
? ' ✅ 当前版本未发现已知漏洞'
|
|
92
|
+
: ' ✅ No known vulnerabilities for current version')
|
|
93
|
+
} else {
|
|
94
|
+
for (const vuln of affected) {
|
|
95
|
+
const icon = vuln.severity === 'CRITICAL' ? '🔴' : '🟡'
|
|
96
|
+
const desc = zh ? vuln.description_zh : vuln.description_en
|
|
97
|
+
lines.push(` ${icon} **${vuln.id}** [${vuln.severity}]: ${desc}`)
|
|
98
|
+
lines.push(zh
|
|
99
|
+
? ` 影响版本: < ${vuln.affectedBelow} — 请升级 OpenClaw`
|
|
100
|
+
: ` Affected: < ${vuln.affectedBelow} — please upgrade OpenClaw`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
lines.push('')
|
|
105
|
+
|
|
106
|
+
// 4. Check Node.js version
|
|
107
|
+
lines.push(zh ? '### 运行环境' : '### Runtime Environment')
|
|
108
|
+
try {
|
|
109
|
+
const nodeVer = process.version
|
|
110
|
+
const major = parseInt(nodeVer.slice(1))
|
|
111
|
+
if (major < 22) {
|
|
112
|
+
lines.push(zh
|
|
113
|
+
? ` ⚠️ Node.js ${nodeVer} — OpenClaw 要求 >= 22.12,请升级`
|
|
114
|
+
: ` ⚠️ Node.js ${nodeVer} — OpenClaw requires >= 22.12, please upgrade`)
|
|
115
|
+
} else {
|
|
116
|
+
lines.push(zh
|
|
117
|
+
? ` ✅ Node.js ${nodeVer}`
|
|
118
|
+
: ` ✅ Node.js ${nodeVer}`)
|
|
119
|
+
}
|
|
120
|
+
} catch { /* skip */ }
|
|
121
|
+
|
|
122
|
+
lines.push(` ${zh ? '平台' : 'Platform'}: ${process.platform} ${process.arch}`)
|
|
123
|
+
lines.push('')
|
|
124
|
+
|
|
125
|
+
// 5. Recommendations
|
|
126
|
+
lines.push('---')
|
|
127
|
+
lines.push(zh
|
|
128
|
+
? '💡 **建议**: 定期运行 `/check-updates` 检查,及时升级到最新版本'
|
|
129
|
+
: '💡 **Tip**: Run `/check-updates` regularly, upgrade to latest versions promptly')
|
|
130
|
+
lines.push(zh
|
|
131
|
+
? '📖 关注 OpenClaw 安全公告: https://github.com/nicepkg/openclaw/security'
|
|
132
|
+
: '📖 Follow OpenClaw security advisories: https://github.com/nicepkg/openclaw/security')
|
|
133
|
+
|
|
134
|
+
return { text: lines.join('\n') }
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
}
|
|
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
|
+
}
|