@pwddd/skills-scanner 1.0.0-beta.2 → 1.0.0-beta.21
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 -45
- package/index.ts +13 -266
- package/openclaw.plugin.json +1 -11
- package/package.json +1 -1
- package/skills/skills-scanner/SKILL.md +15 -51
- package/src/api-client.ts +32 -2
- package/src/before-install-hook.ts +42 -9
- package/src/commands.ts +21 -98
- package/src/config-validator.ts +0 -16
- package/src/config.ts +8 -50
- package/src/cron-manager.ts +117 -169
- package/src/prompt-guidance.ts +8 -18
- package/src/state.ts +0 -17
- package/src/types.ts +0 -4
- package/src/high-risk-operation-guard.ts +0 -62
- package/src/prompt-injection-guard.ts +0 -56
- package/src/report.ts +0 -128
- package/src/watcher.ts +0 -178
|
@@ -23,27 +23,6 @@ OpenClaw Skills 安全扫描工具,检测恶意代码、数据窃取、提示
|
|
|
23
23
|
|
|
24
24
|
**🔒 安全扫描是强制性的,不可跳过。在向用户展示扫描结果前,请务必:**
|
|
25
25
|
|
|
26
|
-
### 🔄 插件版本检查 - 每次运行前必须执行
|
|
27
|
-
|
|
28
|
-
**在执行任何扫描操作前,必须先检查并更新 skills-scanner 插件到最新版本。**
|
|
29
|
-
|
|
30
|
-
**更新命令**:
|
|
31
|
-
```bash
|
|
32
|
-
openclaw plugins update skills-scanner
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**执行时机**:
|
|
36
|
-
- 每次用户请求扫描 Skill 时
|
|
37
|
-
- 每次用户请求安装 Skill 时
|
|
38
|
-
- 每次会话开始时(如果涉及 Skill 操作)
|
|
39
|
-
|
|
40
|
-
**为什么要每次检查更新?**
|
|
41
|
-
- 安全威胁不断演变,扫描规则需要持续更新
|
|
42
|
-
- 新版本可能包含关键的安全修复
|
|
43
|
-
- 确保使用最新的威胁检测能力
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
26
|
### 🎯 命令识别 - 自动触发安全扫描
|
|
48
27
|
|
|
49
28
|
**当用户输入以下任何命令或类似表达时,你必须先扫描再执行:**
|
|
@@ -67,10 +46,6 @@ openclaw plugins update skills-scanner
|
|
|
67
46
|
[执行:openclaw clawhub search caldav-calendar]
|
|
68
47
|
找到:https://clawhub.ai/Asleep123/caldav-calendar
|
|
69
48
|
|
|
70
|
-
现在更新扫描插件...
|
|
71
|
-
[执行:openclaw plugins update skills-scanner]
|
|
72
|
-
✅ 插件已更新
|
|
73
|
-
|
|
74
49
|
扫描安全性...
|
|
75
50
|
[执行扫描]
|
|
76
51
|
✅ 扫描通过,开始安装
|
|
@@ -80,11 +55,7 @@ openclaw plugins update skills-scanner
|
|
|
80
55
|
|
|
81
56
|
```
|
|
82
57
|
用户:openclaw clawhub install https://clawhub.ai/Asleep123/caldav-calendar
|
|
83
|
-
AI: 我看到你想安装 caldav-calendar
|
|
84
|
-
[执行:openclaw plugins update skills-scanner]
|
|
85
|
-
✅ 插件已更新
|
|
86
|
-
|
|
87
|
-
扫描安全性...
|
|
58
|
+
AI: 我看到你想安装 caldav-calendar,让我先扫描安全性...
|
|
88
59
|
[执行扫描]
|
|
89
60
|
✅ 扫描通过,开始安装
|
|
90
61
|
```
|
|
@@ -92,9 +63,8 @@ openclaw plugins update skills-scanner
|
|
|
92
63
|
3. **正确的处理流程**:
|
|
93
64
|
```
|
|
94
65
|
步骤 1: 如果只有名称 → 使用 clawhub search 查找完整 URL
|
|
95
|
-
步骤 2:
|
|
96
|
-
步骤 3:
|
|
97
|
-
步骤 4: 扫描通过后执行安装命令
|
|
66
|
+
步骤 2: 使用完整 URL 进行安全扫描
|
|
67
|
+
步骤 3: 扫描通过后执行安装命令
|
|
98
68
|
```
|
|
99
69
|
|
|
100
70
|
---
|
|
@@ -240,34 +210,28 @@ openclaw plugins update skills-scanner
|
|
|
240
210
|
**在处理任何 Skill 相关操作时,请先问自己这些问题:**
|
|
241
211
|
|
|
242
212
|
1. ✅ 如果是 ClawHub 安装且只有名称,我是否已经搜索了完整 URL?
|
|
243
|
-
2. ✅
|
|
244
|
-
3. ✅
|
|
245
|
-
4. ✅
|
|
246
|
-
5. ✅ 只有所有适用步骤都是 YES,才能继续操作
|
|
213
|
+
2. ✅ 我是否已经扫描了这个 Skill?
|
|
214
|
+
3. ✅ 扫描是否通过?
|
|
215
|
+
4. ✅ 只有所有适用步骤都是 YES,才能继续操作
|
|
247
216
|
|
|
248
|
-
|
|
217
|
+
**如果你忘记搜索或扫描就执行了操作,你就让用户的系统暴露在风险之中。**
|
|
249
218
|
|
|
250
219
|
### 🚨 强制性规则(永远不要忘记)
|
|
251
220
|
|
|
252
|
-
1.
|
|
253
|
-
-
|
|
254
|
-
- 确保使用最新的威胁检测规则
|
|
255
|
-
|
|
256
|
-
2. **看到安装命令 → 搜索(如需要)→ 更新插件 → 立即扫描**
|
|
257
|
-
- `openclaw clawhub install xxx` → 先搜索,再更新插件,再扫描
|
|
221
|
+
1. **看到安装命令 → 搜索(如需要)→ 立即扫描**
|
|
222
|
+
- `openclaw clawhub install xxx` → 先搜索,再扫描
|
|
258
223
|
- 任何包含 "install"、"add"、"download" + "skill" 的命令 → 完整流程
|
|
259
224
|
|
|
260
|
-
|
|
261
|
-
- 用户提到 `~/.openclaw/skills/xxx` →
|
|
262
|
-
- 用户说 "我下载了一个 skill" →
|
|
225
|
+
2. **看到 Skill 路径 → 立即扫描**
|
|
226
|
+
- 用户提到 `~/.openclaw/skills/xxx` → 立即扫描
|
|
227
|
+
- 用户说 "我下载了一个 skill" → 立即扫描
|
|
263
228
|
|
|
264
|
-
|
|
265
|
-
- `https://clawhub.ai/username/project` →
|
|
229
|
+
3. **看到 ClawHub URL → 立即扫描**
|
|
230
|
+
- `https://clawhub.ai/username/project` → 立即扫描
|
|
266
231
|
|
|
267
232
|
### ❌ 绝对禁止的行为
|
|
268
233
|
|
|
269
|
-
- ❌
|
|
270
|
-
- ❌ 直接执行安装命令而不先搜索、更新和扫描
|
|
234
|
+
- ❌ 直接执行安装命令而不先搜索和扫描
|
|
271
235
|
- ❌ 因为 "用户着急" 或 "看起来安全" 就跳过任何步骤
|
|
272
236
|
|
|
273
237
|
---
|
package/src/api-client.ts
CHANGED
|
@@ -84,11 +84,19 @@ export class SkillScannerApiClient {
|
|
|
84
84
|
* Scan a local skill by uploading as ZIP
|
|
85
85
|
*/
|
|
86
86
|
async scanUpload(skillPath: string, options: ScanOptions = {}): Promise<ScanResult> {
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
console.log(`[API Client] 开始扫描: ${skillPath}`);
|
|
89
|
+
console.log(`[API Client] 创建 ZIP 文件...`);
|
|
90
|
+
|
|
87
91
|
const zipPath = await this.createZip(skillPath);
|
|
92
|
+
const zipTime = Date.now() - startTime;
|
|
93
|
+
console.log(`[API Client] ZIP 创建完成: ${zipPath} (耗时 ${zipTime}ms)`);
|
|
88
94
|
|
|
89
95
|
try {
|
|
90
96
|
const formData = new FormData();
|
|
91
97
|
const zipBlob = await this.readFileAsBlob(zipPath);
|
|
98
|
+
console.log(`[API Client] ZIP 文件大小: ${zipBlob.size} bytes`);
|
|
99
|
+
|
|
92
100
|
formData.append("file", zipBlob, `${basename(skillPath)}.zip`);
|
|
93
101
|
formData.append("policy", options.policy || "balanced");
|
|
94
102
|
formData.append("use_llm", String(options.useLLM || false));
|
|
@@ -96,24 +104,46 @@ export class SkillScannerApiClient {
|
|
|
96
104
|
formData.append("use_zip_virus", String(options.useZipVirus !== false));
|
|
97
105
|
formData.append("enable_meta", String(options.enableMeta !== false));
|
|
98
106
|
|
|
107
|
+
console.log(`[API Client] 发送请求到: ${this.baseUrl}/scan-upload`);
|
|
108
|
+
console.log(`[API Client] 请求参数:`, {
|
|
109
|
+
policy: options.policy || "balanced",
|
|
110
|
+
use_llm: options.useLLM || false,
|
|
111
|
+
use_behavioral: options.useBehavioral || false,
|
|
112
|
+
timeout: this.timeout,
|
|
113
|
+
});
|
|
114
|
+
|
|
99
115
|
const response = await fetch(`${this.baseUrl}/scan-upload`, {
|
|
100
116
|
method: "POST",
|
|
101
117
|
body: formData,
|
|
102
118
|
signal: AbortSignal.timeout(this.timeout),
|
|
103
119
|
});
|
|
104
120
|
|
|
121
|
+
const requestTime = Date.now() - startTime;
|
|
122
|
+
console.log(`[API Client] 收到响应: HTTP ${response.status} (总耗时 ${requestTime}ms)`);
|
|
123
|
+
|
|
105
124
|
if (!response.ok) {
|
|
106
125
|
const errorText = await response.text();
|
|
126
|
+
console.error(`[API Client] 请求失败: ${errorText}`);
|
|
107
127
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
108
128
|
}
|
|
109
129
|
|
|
110
|
-
|
|
130
|
+
const result = await response.json();
|
|
131
|
+
console.log(`[API Client] 扫描完成:`, {
|
|
132
|
+
skill_name: result.skill_name,
|
|
133
|
+
is_safe: result.is_safe,
|
|
134
|
+
findings_count: result.findings_count,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return result;
|
|
111
138
|
} finally {
|
|
112
139
|
// Cleanup temp zip
|
|
113
140
|
try {
|
|
114
141
|
const fs = await import("node:fs/promises");
|
|
115
142
|
await fs.unlink(zipPath);
|
|
116
|
-
|
|
143
|
+
console.log(`[API Client] 清理临时文件: ${zipPath}`);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.warn(`[API Client] 清理临时文件失败: ${err}`);
|
|
146
|
+
}
|
|
117
147
|
}
|
|
118
148
|
}
|
|
119
149
|
|
|
@@ -66,7 +66,7 @@ export interface BeforeInstallHandlerOptions {
|
|
|
66
66
|
policy: "strict" | "balanced" | "permissive";
|
|
67
67
|
logger: PluginLogger;
|
|
68
68
|
enabled: boolean;
|
|
69
|
-
timeoutMs?: number; // Scan timeout in milliseconds (default:
|
|
69
|
+
timeoutMs?: number; // Scan timeout in milliseconds (default: 120000)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -79,17 +79,25 @@ export async function handleBeforeInstall(
|
|
|
79
79
|
event: BeforeInstallEvent,
|
|
80
80
|
options: BeforeInstallHandlerOptions
|
|
81
81
|
): Promise<BeforeInstallResult> {
|
|
82
|
-
const timeoutMs = options.timeoutMs ||
|
|
82
|
+
const timeoutMs = options.timeoutMs || 120000; // Default 120 seconds (2 minutes)
|
|
83
|
+
|
|
84
|
+
options.logger.debug("[before_install] 开始处理", {
|
|
85
|
+
targetName: event.targetName,
|
|
86
|
+
targetType: event.targetType,
|
|
87
|
+
sourcePath: event.sourcePath,
|
|
88
|
+
timeoutMs,
|
|
89
|
+
});
|
|
83
90
|
|
|
84
91
|
// Wrap the scan in a timeout promise
|
|
85
92
|
return Promise.race([
|
|
86
|
-
performScan(event, options),
|
|
93
|
+
performScan(event, options, timeoutMs),
|
|
87
94
|
new Promise<BeforeInstallResult>((_, reject) =>
|
|
88
|
-
setTimeout(() => reject(new Error(
|
|
95
|
+
setTimeout(() => reject(new Error(`扫描超时 (${timeoutMs}ms)`)), timeoutMs)
|
|
89
96
|
),
|
|
90
97
|
]).catch((err: any) => {
|
|
91
|
-
options.logger.error("[before_install]
|
|
98
|
+
options.logger.error("[before_install] 扫描失败或超时", {
|
|
92
99
|
error: err.message,
|
|
100
|
+
errorStack: err.stack,
|
|
93
101
|
targetName: event.targetName,
|
|
94
102
|
timeoutMs,
|
|
95
103
|
});
|
|
@@ -115,7 +123,8 @@ export async function handleBeforeInstall(
|
|
|
115
123
|
*/
|
|
116
124
|
async function performScan(
|
|
117
125
|
event: BeforeInstallEvent,
|
|
118
|
-
options: BeforeInstallHandlerOptions
|
|
126
|
+
options: BeforeInstallHandlerOptions,
|
|
127
|
+
timeoutMs: number
|
|
119
128
|
): Promise<BeforeInstallResult> {
|
|
120
129
|
const { targetType, targetName, sourcePath, builtinScan } = event;
|
|
121
130
|
const { apiUrl, behavioral, useLLM, policy, logger, enabled } = options;
|
|
@@ -135,6 +144,8 @@ async function performScan(
|
|
|
135
144
|
logger.info(`[安装前拦截] 🔍 拦截 Skill 安装: ${targetName}`);
|
|
136
145
|
logger.info(`[安装前拦截] 来源: ${sourcePath}`);
|
|
137
146
|
logger.info(`[安装前拦截] 内置扫描: ${builtinScan.status} (${builtinScan.critical} 严重, ${builtinScan.warn} 警告)`);
|
|
147
|
+
logger.debug(`[安装前拦截] API URL: ${apiUrl}`);
|
|
148
|
+
logger.debug(`[安装前拦截] 超时设置: ${timeoutMs}ms`);
|
|
138
149
|
|
|
139
150
|
// 首先检查内置扫描结果
|
|
140
151
|
if (builtinScan.status === "error") {
|
|
@@ -158,13 +169,31 @@ async function performScan(
|
|
|
158
169
|
// 运行增强扫描
|
|
159
170
|
try {
|
|
160
171
|
logger.info(`[安装前拦截] 🔬 正在运行增强安全扫描...`);
|
|
172
|
+
logger.debug(`[安装前拦截] 扫描参数`, {
|
|
173
|
+
mode: "scan",
|
|
174
|
+
target: sourcePath,
|
|
175
|
+
behavioral,
|
|
176
|
+
useLLM,
|
|
177
|
+
policy,
|
|
178
|
+
apiUrl,
|
|
179
|
+
timeoutMs,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const scanStartTime = Date.now();
|
|
161
183
|
|
|
162
184
|
const scanResult = await runScan("scan", sourcePath, {
|
|
163
185
|
behavioral,
|
|
164
186
|
useLLM,
|
|
165
187
|
policy,
|
|
166
188
|
apiUrl,
|
|
167
|
-
detailed: true
|
|
189
|
+
detailed: true,
|
|
190
|
+
timeoutMs: timeoutMs - 10000, // Reserve 10s for overhead
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const scanDuration = Date.now() - scanStartTime;
|
|
194
|
+
logger.debug(`[安装前拦截] 扫描完成`, {
|
|
195
|
+
exitCode: scanResult.exitCode,
|
|
196
|
+
durationMs: scanDuration,
|
|
168
197
|
});
|
|
169
198
|
|
|
170
199
|
if (scanResult.exitCode === 0) {
|
|
@@ -183,7 +212,7 @@ async function performScan(
|
|
|
183
212
|
|
|
184
213
|
// 扫描失败 - 阻止安装
|
|
185
214
|
const severity = scanResult.data?.max_severity || "未知";
|
|
186
|
-
const findingsCount = scanResult.data?.
|
|
215
|
+
const findingsCount = scanResult.data?.findings_count || 0;
|
|
187
216
|
|
|
188
217
|
logger.warn(`[安装前拦截] ❌ ${targetName} 未通过安全扫描`);
|
|
189
218
|
logger.warn(`[安装前拦截] 严重性: ${severity}, 发现数: ${findingsCount}`);
|
|
@@ -208,7 +237,11 @@ async function performScan(
|
|
|
208
237
|
|
|
209
238
|
} catch (error: any) {
|
|
210
239
|
// 扫描因错误失败 - 根据策略决定
|
|
211
|
-
logger.error(`[安装前拦截] ⚠️ ${targetName}
|
|
240
|
+
logger.error(`[安装前拦截] ⚠️ ${targetName} 扫描错误`, {
|
|
241
|
+
error: error.message,
|
|
242
|
+
errorStack: error.stack,
|
|
243
|
+
errorCode: error.code,
|
|
244
|
+
});
|
|
212
245
|
|
|
213
246
|
// 在严格模式下,扫描错误时阻止
|
|
214
247
|
if (policy === "strict") {
|
package/src/commands.ts
CHANGED
|
@@ -5,28 +5,24 @@
|
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { join, basename } from "node:path";
|
|
7
7
|
import { runScan } from "./scanner.js";
|
|
8
|
-
import { buildDailyReport } from "./report.js";
|
|
9
8
|
import { loadState, saveState, expandPath } from "./state.js";
|
|
10
9
|
import { generateConfigGuide } from "./config.js";
|
|
11
|
-
import { ensureCronJobViaGateway, checkCronJobStatus } from "./cron-manager.js";
|
|
12
10
|
import type { ScannerConfig, CommandResponse, PluginLogger } from "./types.js";
|
|
13
11
|
|
|
14
12
|
export function createCommandHandlers(
|
|
15
13
|
cfg: ScannerConfig,
|
|
16
14
|
apiUrl: string,
|
|
17
|
-
scanDirs: string[],
|
|
18
15
|
behavioral: boolean,
|
|
19
16
|
useLLM: boolean,
|
|
20
17
|
policy: string,
|
|
21
|
-
preInstallScan: string,
|
|
22
18
|
onUnsafe: string,
|
|
23
19
|
logger: PluginLogger,
|
|
24
|
-
|
|
20
|
+
apiConfig?: any
|
|
25
21
|
) {
|
|
26
22
|
async function handleScanCommand(args: string): Promise<CommandResponse> {
|
|
27
23
|
if (!args) {
|
|
28
24
|
return {
|
|
29
|
-
text: "用法:`/skills-scanner scan <路径> [--detailed] [--behavioral] [--recursive]
|
|
25
|
+
text: "用法:`/skills-scanner scan <路径> [--detailed] [--behavioral] [--recursive]`\n或:`/skills-scanner scan clawhub <URL> [--detailed] [--behavioral]`",
|
|
30
26
|
};
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -57,23 +53,6 @@ export function createCommandHandlers(
|
|
|
57
53
|
const detailed = parts.includes("--detailed");
|
|
58
54
|
const useBehav = parts.includes("--behavioral") || behavioral;
|
|
59
55
|
const recursive = parts.includes("--recursive");
|
|
60
|
-
const isReport = parts.includes("--report");
|
|
61
|
-
|
|
62
|
-
// Report mode: use configured scanDirs
|
|
63
|
-
if (isReport) {
|
|
64
|
-
if (scanDirs.length === 0) {
|
|
65
|
-
return { text: "⚠️ 未找到可扫描目录,请检查配置" };
|
|
66
|
-
}
|
|
67
|
-
const report = await buildDailyReport(
|
|
68
|
-
scanDirs,
|
|
69
|
-
useBehav,
|
|
70
|
-
apiUrl,
|
|
71
|
-
useLLM,
|
|
72
|
-
policy,
|
|
73
|
-
logger
|
|
74
|
-
);
|
|
75
|
-
return { text: report };
|
|
76
|
-
}
|
|
77
56
|
|
|
78
57
|
// Regular scan mode: require path
|
|
79
58
|
if (!targetPath) {
|
|
@@ -87,13 +66,31 @@ export function createCommandHandlers(
|
|
|
87
66
|
const isSingleSkill = existsSync(join(targetPath, "SKILL.md"));
|
|
88
67
|
|
|
89
68
|
if (isSingleSkill) {
|
|
69
|
+
logger.info(`[命令] 开始扫描单个 Skill: ${targetPath}`);
|
|
70
|
+
logger.debug(`[命令] 扫描参数`, {
|
|
71
|
+
mode: "scan",
|
|
72
|
+
targetPath,
|
|
73
|
+
detailed,
|
|
74
|
+
behavioral: useBehav,
|
|
75
|
+
apiUrl,
|
|
76
|
+
useLLM,
|
|
77
|
+
policy,
|
|
78
|
+
});
|
|
79
|
+
|
|
90
80
|
const res = await runScan("scan", targetPath, {
|
|
91
81
|
detailed,
|
|
92
82
|
behavioral: useBehav,
|
|
93
83
|
apiUrl,
|
|
94
84
|
useLLM,
|
|
95
85
|
policy,
|
|
86
|
+
timeoutMs: 180000, // 3 minutes
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
logger.info(`[命令] 扫描完成`, {
|
|
90
|
+
exitCode: res.exitCode,
|
|
91
|
+
hasData: !!res.data,
|
|
96
92
|
});
|
|
93
|
+
|
|
97
94
|
const icon = res.exitCode === 0 ? "✅" : "❌";
|
|
98
95
|
return { text: `${icon} 扫描完成\n\`\`\`\n${res.output}\n\`\`\`` };
|
|
99
96
|
} else {
|
|
@@ -117,12 +114,10 @@ export function createCommandHandlers(
|
|
|
117
114
|
const lines = [
|
|
118
115
|
"✅ *Skills Scanner 状态*",
|
|
119
116
|
`API 地址:${apiUrl}`,
|
|
120
|
-
`安装前扫描:${preInstallScan === "on" ? `✅ 监听中 (${onUnsafe})` : "⏭️ 已禁用"}`,
|
|
121
117
|
`扫描策略:${policy}`,
|
|
122
118
|
`LLM 分析:${useLLM ? "✅ 启用" : "❌ 禁用"}`,
|
|
123
119
|
`行为分析:${behavioral ? "✅ 启用" : "❌ 禁用"}`,
|
|
124
120
|
`上次扫描:${state.lastScanAt ? new Date(state.lastScanAt).toLocaleString("zh-CN") : "从未"}`,
|
|
125
|
-
`扫描目录:\n${scanDirs.map((d) => ` 📁 ${d}`).join("\n")}`,
|
|
126
121
|
];
|
|
127
122
|
|
|
128
123
|
// API health check
|
|
@@ -152,13 +147,7 @@ export function createCommandHandlers(
|
|
|
152
147
|
saveState({ ...state, pendingAlerts: [] });
|
|
153
148
|
}
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
if (state.cronJobId && state.cronJobId !== "manual-created") {
|
|
157
|
-
lines.push(`状态:✅ 已注册 (${state.cronJobId})`);
|
|
158
|
-
} else {
|
|
159
|
-
lines.push("状态:❌ 未注册");
|
|
160
|
-
lines.push("ℹ️ 使用 `/skills-scanner cron register` 注册");
|
|
161
|
-
}
|
|
150
|
+
// 定时任务功能已移除
|
|
162
151
|
|
|
163
152
|
return { text: lines.join("\n") };
|
|
164
153
|
}
|
|
@@ -170,11 +159,9 @@ export function createCommandHandlers(
|
|
|
170
159
|
const configGuide = generateConfigGuide(
|
|
171
160
|
cfg,
|
|
172
161
|
apiUrl,
|
|
173
|
-
scanDirs,
|
|
174
162
|
behavioral,
|
|
175
163
|
useLLM,
|
|
176
164
|
policy,
|
|
177
|
-
preInstallScan,
|
|
178
165
|
onUnsafe
|
|
179
166
|
);
|
|
180
167
|
return { text: "```\n" + configGuide + "\n```" };
|
|
@@ -189,66 +176,6 @@ export function createCommandHandlers(
|
|
|
189
176
|
}
|
|
190
177
|
}
|
|
191
178
|
|
|
192
|
-
async function handleCronCommand(args: string): Promise<CommandResponse> {
|
|
193
|
-
const action = args.trim().toLowerCase() || "status";
|
|
194
|
-
|
|
195
|
-
if (action === "setup" || action === "register") {
|
|
196
|
-
try {
|
|
197
|
-
await ensureCronJobViaGateway({
|
|
198
|
-
logger,
|
|
199
|
-
callGateway,
|
|
200
|
-
});
|
|
201
|
-
return { text: "✅ 定时任务注册完成\n请查看日志了解详情" };
|
|
202
|
-
} catch (err: any) {
|
|
203
|
-
return { text: `⚠️ 定时任务注册失败:${err.message}` };
|
|
204
|
-
}
|
|
205
|
-
} else if (action === "unregister") {
|
|
206
|
-
try {
|
|
207
|
-
// 列出所有任务
|
|
208
|
-
const listResult = await callGateway("cron.list", {});
|
|
209
|
-
const jobs = Array.isArray(listResult?.jobs) ? listResult.jobs : [];
|
|
210
|
-
const existingJobs = jobs.filter((job: any) => job.name === "skills-weekly-report");
|
|
211
|
-
|
|
212
|
-
if (existingJobs.length === 0) {
|
|
213
|
-
return { text: "ℹ️ 未找到已注册的定时任务" };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 删除所有同名任务
|
|
217
|
-
const deletedIds: string[] = [];
|
|
218
|
-
for (const job of existingJobs) {
|
|
219
|
-
const jobId = job.jobId || job.id;
|
|
220
|
-
try {
|
|
221
|
-
await callGateway("cron.remove", { jobId });
|
|
222
|
-
deletedIds.push(jobId);
|
|
223
|
-
} catch (err: any) {
|
|
224
|
-
logger.warn(`删除任务 ${jobId} 失败: ${err.message}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (deletedIds.length > 0) {
|
|
229
|
-
return {
|
|
230
|
-
text: `✅ 已删除 ${deletedIds.length} 个定时任务\n${deletedIds.map(id => `- ${id}`).join("\n")}`,
|
|
231
|
-
};
|
|
232
|
-
} else {
|
|
233
|
-
return { text: "⚠️ 删除失败,请查看日志" };
|
|
234
|
-
}
|
|
235
|
-
} catch (err: any) {
|
|
236
|
-
return { text: `⚠️ 删除失败:${err.message}` };
|
|
237
|
-
}
|
|
238
|
-
} else {
|
|
239
|
-
// status
|
|
240
|
-
try {
|
|
241
|
-
const statusText = await checkCronJobStatus({
|
|
242
|
-
logger,
|
|
243
|
-
callGateway,
|
|
244
|
-
});
|
|
245
|
-
return { text: statusText };
|
|
246
|
-
} catch (err: any) {
|
|
247
|
-
return { text: `⚠️ 查询状态失败:${err.message}` };
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
179
|
function getHelpText(): string {
|
|
253
180
|
return [
|
|
254
181
|
"✅ *Skills Scanner - 帮助*",
|
|
@@ -261,13 +188,11 @@ export function createCommandHandlers(
|
|
|
261
188
|
"• `--detailed` - 显示详细发现",
|
|
262
189
|
"• `--behavioral` - 启用行为分析",
|
|
263
190
|
"• `--recursive` - 递归扫描子目录",
|
|
264
|
-
"• `--report` - 生成日报格式",
|
|
265
191
|
"",
|
|
266
192
|
"示例:",
|
|
267
193
|
"```",
|
|
268
194
|
"/skills-scanner scan ~/.openclaw/skills/my-skill",
|
|
269
195
|
"/skills-scanner scan ~/.openclaw/skills --recursive",
|
|
270
|
-
"/skills-scanner scan ~/.openclaw/skills --report",
|
|
271
196
|
"/skills-scanner scan clawhub https://clawhub.ai/username/project",
|
|
272
197
|
"/skills-scanner scan clawhub https://clawhub.ai/username/project --detailed",
|
|
273
198
|
"```",
|
|
@@ -275,7 +200,6 @@ export function createCommandHandlers(
|
|
|
275
200
|
"═══ 其他命令 ═══",
|
|
276
201
|
"• `/skills-scanner health` - 健康检查",
|
|
277
202
|
"• `/skills-scanner config [show|reset]` - 配置管理",
|
|
278
|
-
"• `/skills-scanner cron [register|unregister|status]` - 定时任务管理",
|
|
279
203
|
].join("\n");
|
|
280
204
|
}
|
|
281
205
|
|
|
@@ -283,7 +207,6 @@ export function createCommandHandlers(
|
|
|
283
207
|
handleScanCommand,
|
|
284
208
|
handleHealthCommand,
|
|
285
209
|
handleConfigCommand,
|
|
286
|
-
handleCronCommand,
|
|
287
210
|
getHelpText,
|
|
288
211
|
};
|
|
289
212
|
}
|
package/src/config-validator.ts
CHANGED
|
@@ -31,15 +31,6 @@ export function validateConfig(
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Validate scan directories
|
|
35
|
-
if (cfg.scanDirs && cfg.scanDirs.length > 0) {
|
|
36
|
-
for (const dir of cfg.scanDirs) {
|
|
37
|
-
if (!existsSync(dir)) {
|
|
38
|
-
warnings.push(`Scan directory does not exist: ${dir}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
34
|
// Validate policy
|
|
44
35
|
if (cfg.policy && !["strict", "balanced", "permissive"].includes(cfg.policy)) {
|
|
45
36
|
errors.push(
|
|
@@ -47,13 +38,6 @@ export function validateConfig(
|
|
|
47
38
|
);
|
|
48
39
|
}
|
|
49
40
|
|
|
50
|
-
// Validate preInstallScan
|
|
51
|
-
if (cfg.preInstallScan && !["on", "off"].includes(cfg.preInstallScan)) {
|
|
52
|
-
errors.push(
|
|
53
|
-
`Invalid preInstallScan: "${cfg.preInstallScan}". Must be "on" or "off"`
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
41
|
// Validate onUnsafe
|
|
58
42
|
if (cfg.onUnsafe && !["warn", "delete", "quarantine"].includes(cfg.onUnsafe)) {
|
|
59
43
|
errors.push(
|
package/src/config.ts
CHANGED
|
@@ -24,19 +24,6 @@ export const skillsScannerConfigSchema: OpenClawPluginConfigSchema = {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// 验证 preInstallScan
|
|
28
|
-
if (config.preInstallScan && !["on", "off"].includes(config.preInstallScan)) {
|
|
29
|
-
return {
|
|
30
|
-
success: false,
|
|
31
|
-
error: {
|
|
32
|
-
issues: [{
|
|
33
|
-
path: ["preInstallScan"],
|
|
34
|
-
message: "preInstallScan 必须是 on 或 off"
|
|
35
|
-
}]
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
27
|
// 验证 onUnsafe
|
|
41
28
|
if (config.onUnsafe && !["quarantine", "delete", "warn"].includes(config.onUnsafe)) {
|
|
42
29
|
return {
|
|
@@ -72,12 +59,6 @@ export const skillsScannerConfigSchema: OpenClawPluginConfigSchema = {
|
|
|
72
59
|
type: "string",
|
|
73
60
|
format: "uri",
|
|
74
61
|
},
|
|
75
|
-
scanDirs: {
|
|
76
|
-
label: "扫描目录",
|
|
77
|
-
help: "要监控的 Skills 目录列表,支持 ~ 路径。默认监控 ~/.openclaw/skills 和 ~/.openclaw/workspace/skills",
|
|
78
|
-
type: "array",
|
|
79
|
-
items: { type: "string" },
|
|
80
|
-
},
|
|
81
62
|
behavioral: {
|
|
82
63
|
label: "行为分析",
|
|
83
64
|
help: "启用深度行为分析(较慢但更准确)。检测运行时行为模式,如网络请求、文件操作等",
|
|
@@ -97,13 +78,6 @@ export const skillsScannerConfigSchema: OpenClawPluginConfigSchema = {
|
|
|
97
78
|
enum: ["strict", "balanced", "permissive"],
|
|
98
79
|
default: "balanced",
|
|
99
80
|
},
|
|
100
|
-
preInstallScan: {
|
|
101
|
-
label: "安装前扫描",
|
|
102
|
-
help: "监听新 Skill 并自动扫描。启用后会实时监控 scanDirs 中的文件变化",
|
|
103
|
-
type: "string",
|
|
104
|
-
enum: ["on", "off"],
|
|
105
|
-
default: "on",
|
|
106
|
-
},
|
|
107
81
|
onUnsafe: {
|
|
108
82
|
label: "不安全处理",
|
|
109
83
|
help: "warn=仅警告(推荐,不影响使用)/ quarantine=隔离到隔离区 / delete=直接删除(危险)",
|
|
@@ -123,18 +97,6 @@ export const skillsScannerConfigSchema: OpenClawPluginConfigSchema = {
|
|
|
123
97
|
type: "boolean",
|
|
124
98
|
default: true,
|
|
125
99
|
},
|
|
126
|
-
enablePromptInjectionGuard: {
|
|
127
|
-
label: "提示注入防护",
|
|
128
|
-
help: "启用提示注入攻击防护,检测并阻止恶意提示",
|
|
129
|
-
type: "boolean",
|
|
130
|
-
default: false,
|
|
131
|
-
},
|
|
132
|
-
enableHighRiskOperationGuard: {
|
|
133
|
-
label: "高风险操作防护",
|
|
134
|
-
help: "启用高风险操作防护,对敏感操作进行额外检查",
|
|
135
|
-
type: "boolean",
|
|
136
|
-
default: false,
|
|
137
|
-
},
|
|
138
100
|
scanTimeoutMs: {
|
|
139
101
|
label: "扫描超时(毫秒)",
|
|
140
102
|
help: "单次扫描的最大时长,超时后自动取消。默认 180000ms (3分钟)",
|
|
@@ -159,11 +121,9 @@ export const skillsScannerConfigSchema: OpenClawPluginConfigSchema = {
|
|
|
159
121
|
export function generateConfigGuide(
|
|
160
122
|
cfg: ScannerConfig,
|
|
161
123
|
apiUrl: string,
|
|
162
|
-
scanDirs: string[],
|
|
163
124
|
behavioral: boolean,
|
|
164
125
|
useLLM: boolean,
|
|
165
126
|
policy: string,
|
|
166
|
-
preInstallScan: string,
|
|
167
127
|
onUnsafe: string
|
|
168
128
|
): string {
|
|
169
129
|
return [
|
|
@@ -176,11 +136,10 @@ export function generateConfigGuide(
|
|
|
176
136
|
"",
|
|
177
137
|
"📋 当前配置:",
|
|
178
138
|
` • API 服务地址:${apiUrl}`,
|
|
179
|
-
` • 扫描目录:${scanDirs.length} 个(自动检测)`,
|
|
180
139
|
` • 行为分析:${behavioral ? "✅ 启用" : "❌ 禁用"}`,
|
|
181
140
|
` • LLM 分析:${useLLM ? "✅ 启用" : "❌ 禁用"}`,
|
|
182
141
|
` • 扫描策略:${policy}`,
|
|
183
|
-
` •
|
|
142
|
+
` • 目录监控:❌ 已禁用(需手动扫描)`,
|
|
184
143
|
` • 不安全处理:${onUnsafe}`,
|
|
185
144
|
"",
|
|
186
145
|
"🔧 配置文件位置:",
|
|
@@ -196,11 +155,9 @@ export function generateConfigGuide(
|
|
|
196
155
|
' "enabled": true,',
|
|
197
156
|
' "config": {',
|
|
198
157
|
' "apiUrl": "https://110.vemic.com/skills-scanner",',
|
|
199
|
-
' "scanDirs": ["~/.openclaw/skills"],',
|
|
200
158
|
' "behavioral": false,',
|
|
201
159
|
' "useLLM": false,',
|
|
202
160
|
' "policy": "balanced",',
|
|
203
|
-
' "preInstallScan": "on",',
|
|
204
161
|
' "onUnsafe": "warn"',
|
|
205
162
|
' }',
|
|
206
163
|
' }',
|
|
@@ -212,12 +169,13 @@ export function generateConfigGuide(
|
|
|
212
169
|
"💡 配置说明:",
|
|
213
170
|
"",
|
|
214
171
|
"1. apiUrl 默认 https://110.vemic.com/skills-scanner",
|
|
215
|
-
"2.
|
|
216
|
-
"3.
|
|
217
|
-
"4.
|
|
218
|
-
"5.
|
|
219
|
-
"
|
|
220
|
-
"
|
|
172
|
+
"2. behavioral false=快速扫描(推荐),true=深度分析",
|
|
173
|
+
"3. useLLM false=不使用 LLM(推荐),true=语义分析",
|
|
174
|
+
"4. policy strict / balanced(推荐)/ permissive",
|
|
175
|
+
"5. onUnsafe warn=仅警告(推荐),quarantine=隔离,delete=删除",
|
|
176
|
+
"",
|
|
177
|
+
"⚠️ 注意:目录监控功能已禁用,请使用以下命令手动扫描:",
|
|
178
|
+
" /skills-scanner scan <路径>",
|
|
221
179
|
"",
|
|
222
180
|
"🚀 快速开始:",
|
|
223
181
|
" 编辑配置文件后重启 Gateway",
|