@pwddd/skills-scanner 1.0.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.

Potentially problematic release.


This version of @pwddd/skills-scanner might be problematic. Click here for more details.

package/index.ts ADDED
@@ -0,0 +1,373 @@
1
+ /**
2
+ * OpenClaw Plugin: skills-scanner
3
+ *
4
+ * Security scanner for OpenClaw Skills to detect potential threats.
5
+ */
6
+
7
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ import { existsSync } from "node:fs";
11
+ import type { ScannerConfig } from "./src/types.js";
12
+ import { skillsScannerConfigSchema, generateConfigGuide } from "./src/config.js";
13
+ import {
14
+ loadState,
15
+ saveState,
16
+ expandPath,
17
+ defaultScanDirs,
18
+ isFirstRun,
19
+ markConfigReviewed,
20
+ } from "./src/state.js";
21
+ import { ensureDeps, isVenvReady } from "./src/deps.js";
22
+ import { runScan } from "./src/scanner.js";
23
+ import { buildDailyReport } from "./src/report.js";
24
+ import { ensureCronJob } from "./src/cron.js";
25
+ import { startWatcher } from "./src/watcher.js";
26
+ import { createCommandHandlers } from "./src/commands.js";
27
+ import { SKILLS_SECURITY_GUIDANCE } from "./src/prompt-guidance.js";
28
+
29
+ // Constants
30
+ const PLUGIN_ROOT = process.env.OPENCLAW_PLUGIN_ROOT || __dirname;
31
+ const SKILL_DIR = join(PLUGIN_ROOT, "skills", "skills-scanner");
32
+ const VENV_PYTHON = join(SKILL_DIR, ".venv", "bin", "python");
33
+ const SCAN_SCRIPT = join(SKILL_DIR, "scan.py");
34
+ const STATE_DIR = join(homedir(), ".openclaw", "skills-scanner");
35
+ const QUARANTINE_DIR = join(STATE_DIR, "quarantine");
36
+
37
+ export default function register(api: OpenClawPluginApi) {
38
+ const cfg: ScannerConfig =
39
+ api.config?.plugins?.entries?.["skills-scanner"]?.config ?? {};
40
+ const apiUrl = cfg.apiUrl ?? "http://localhost:8000";
41
+ const scanDirs =
42
+ (cfg.scanDirs?.map(expandPath) ?? []).filter(existsSync).length > 0
43
+ ? cfg.scanDirs!.map(expandPath)
44
+ : defaultScanDirs();
45
+ const behavioral = cfg.behavioral ?? false;
46
+ const useLLM = cfg.useLLM ?? false;
47
+ const policy = cfg.policy ?? "balanced";
48
+ const preInstallScan = cfg.preInstallScan ?? "on";
49
+ const onUnsafe = cfg.onUnsafe ?? "quarantine";
50
+ const injectSecurityGuidance = cfg.injectSecurityGuidance ?? true;
51
+
52
+ api.logger.info("[skills-scanner] ═══════════════════════════════════════");
53
+ api.logger.info("[skills-scanner] Plugin loading...");
54
+ api.logger.info(`[skills-scanner] API URL: ${apiUrl}`);
55
+ api.logger.info(`[skills-scanner] Scan directories: ${scanDirs.join(", ")}`);
56
+ api.logger.info(
57
+ `[skills-scanner] Python dependencies: ${isVenvReady(VENV_PYTHON) ? "✅ Ready" : "❌ Not installed"}`
58
+ );
59
+
60
+ // Inject system prompt guidance (can be disabled via config)
61
+ if (injectSecurityGuidance) {
62
+ api.on("before_prompt_build", async () => ({
63
+ prependSystemContext: SKILLS_SECURITY_GUIDANCE,
64
+ }));
65
+ api.logger.info("[skills-scanner] ✅ Security guidance injected into system prompt");
66
+ } else {
67
+ api.logger.info("[skills-scanner] ⏭️ Security guidance injection disabled");
68
+ }
69
+
70
+ // Check if first run
71
+ const firstRun = isFirstRun(cfg);
72
+ if (firstRun) {
73
+ api.logger.info("[skills-scanner] 🎉 First run detected");
74
+ const configGuide = generateConfigGuide(
75
+ cfg,
76
+ apiUrl,
77
+ scanDirs,
78
+ behavioral,
79
+ useLLM,
80
+ policy,
81
+ preInstallScan,
82
+ onUnsafe
83
+ );
84
+ console.log(configGuide);
85
+ markConfigReviewed();
86
+ }
87
+
88
+ // Install dependencies immediately
89
+ if (!isVenvReady(VENV_PYTHON)) {
90
+ api.logger.info("[skills-scanner] Installing Python dependencies...");
91
+ ensureDeps(SKILL_DIR, VENV_PYTHON, api.logger)
92
+ .then((success) => {
93
+ if (success) {
94
+ api.logger.info("[skills-scanner] ✅ Dependencies installed");
95
+ }
96
+ })
97
+ .catch((err) => {
98
+ api.logger.error(`[skills-scanner] Dependency installation failed: ${err.message}`);
99
+ });
100
+ }
101
+
102
+ // Helper for watcher alerts
103
+ function persistWatcherAlert(msg: string): void {
104
+ const state = loadState();
105
+ const alerts: string[] = (state as any).pendingAlerts ?? [];
106
+ alerts.push(`[${new Date().toLocaleString("en-US")}] ${msg}`);
107
+ saveState({ ...state, pendingAlerts: alerts } as any);
108
+ api.logger.warn(`[skills-scanner] ${msg}`);
109
+ }
110
+
111
+ // Service: Install deps + start watcher
112
+ let stopWatcher: (() => void) | null = null;
113
+
114
+ api.registerService({
115
+ id: "skills-scanner-setup",
116
+ start: async () => {
117
+ api.logger.info("[skills-scanner] 🚀 Service starting...");
118
+
119
+ const depsReady = await ensureDeps(SKILL_DIR, VENV_PYTHON, api.logger);
120
+
121
+ if (!depsReady) {
122
+ api.logger.error("[skills-scanner] ❌ Dependencies installation failed");
123
+ return;
124
+ }
125
+
126
+ if (preInstallScan === "on" && scanDirs.length > 0) {
127
+ api.logger.info(`[skills-scanner] 📁 Starting file monitoring: ${scanDirs.length} directories`);
128
+ stopWatcher = startWatcher(
129
+ scanDirs,
130
+ onUnsafe,
131
+ behavioral,
132
+ apiUrl,
133
+ useLLM,
134
+ policy,
135
+ persistWatcherAlert,
136
+ api.logger,
137
+ VENV_PYTHON,
138
+ SCAN_SCRIPT,
139
+ QUARANTINE_DIR
140
+ );
141
+ api.logger.info("[skills-scanner] ✅ File monitoring started");
142
+ } else {
143
+ api.logger.info("[skills-scanner] ⏭️ Pre-install scan disabled");
144
+ }
145
+
146
+ // Register cron job (only in Gateway mode)
147
+ const isGatewayMode = !!(api as any).runtime;
148
+ if (isGatewayMode) {
149
+ await ensureCronJob(api.logger);
150
+ }
151
+
152
+ api.logger.info("[skills-scanner] ─────────────────────────────────────");
153
+ },
154
+ stop: () => {
155
+ api.logger.info("[skills-scanner] 🛑 Service stopping...");
156
+ stopWatcher?.();
157
+ stopWatcher = null;
158
+ },
159
+ });
160
+
161
+ // Command handlers
162
+ const handlers = createCommandHandlers(
163
+ cfg,
164
+ apiUrl,
165
+ scanDirs,
166
+ behavioral,
167
+ useLLM,
168
+ policy,
169
+ preInstallScan,
170
+ onUnsafe,
171
+ VENV_PYTHON,
172
+ SCAN_SCRIPT,
173
+ api.logger
174
+ );
175
+
176
+ // Chat command: /skills-scanner
177
+ api.registerCommand({
178
+ name: "skills-scanner",
179
+ description: "Skills 安全扫描工具。用法: /skills-scanner <子命令> [参数]",
180
+ acceptsArgs: true,
181
+ requireAuth: true,
182
+ handler: async (ctx: any) => {
183
+ const args = (ctx.args ?? "").trim();
184
+
185
+ if (!args) {
186
+ return {
187
+ text: [
188
+ "🔍 *Skills Scanner - 安全扫描工具*",
189
+ "",
190
+ "可用命令:",
191
+ "• `/skills-scanner scan <路径> [选项]` - 扫描 Skill",
192
+ "• `/skills-scanner status` - 查看状态",
193
+ "• `/skills-scanner config [操作]` - 配置管理",
194
+ "• `/skills-scanner cron [操作]` - 定时任务管理",
195
+ "",
196
+ "扫描选项:",
197
+ "• `--detailed` - 显示详细发现",
198
+ "• `--behavioral` - 启用行为分析",
199
+ "• `--recursive` - 递归扫描子目录",
200
+ "• `--report` - 生成日报格式",
201
+ "",
202
+ "示例:",
203
+ "```",
204
+ "/skills-scanner scan ~/my-skill",
205
+ "/skills-scanner scan ~/skills --recursive",
206
+ "/skills-scanner status",
207
+ "```",
208
+ "",
209
+ "💡 使用 `/skills-scanner help` 查看详细帮助",
210
+ ].join("\n"),
211
+ };
212
+ }
213
+
214
+ const parts = args.split(/\s+/);
215
+ const subCommand = parts[0].toLowerCase();
216
+ const subArgs = parts.slice(1).join(" ");
217
+
218
+ if (subCommand === "scan") {
219
+ return await handlers.handleScanCommand(subArgs);
220
+ } else if (subCommand === "status") {
221
+ return await handlers.handleStatusCommand();
222
+ } else if (subCommand === "config") {
223
+ return await handlers.handleConfigCommand(subArgs);
224
+ } else if (subCommand === "cron") {
225
+ return await handlers.handleCronCommand(subArgs);
226
+ } else if (subCommand === "help" || subCommand === "--help" || subCommand === "-h") {
227
+ return { text: handlers.getHelpText() };
228
+ } else {
229
+ return {
230
+ text: `❌ 未知子命令: ${subCommand}\n\n使用 \`/skills-scanner help\` 查看帮助`,
231
+ };
232
+ }
233
+ },
234
+ });
235
+
236
+ // Gateway RPC methods
237
+ api.registerGatewayMethod("skillsScanner.scan", async ({ respond, params }: any) => {
238
+ const { path: p, mode = "scan", recursive = false, detailed = false } = params ?? {};
239
+ if (!p) return respond(false, { error: "Missing path parameter" });
240
+ if (!isVenvReady(VENV_PYTHON))
241
+ return respond(false, { error: "Python dependencies not ready" });
242
+ const res = await runScan(VENV_PYTHON, SCAN_SCRIPT, mode === "batch" ? "batch" : "scan", expandPath(p), {
243
+ recursive,
244
+ detailed,
245
+ behavioral,
246
+ apiUrl,
247
+ useLLM,
248
+ policy,
249
+ });
250
+ respond(res.exitCode === 0, {
251
+ output: res.output,
252
+ exitCode: res.exitCode,
253
+ is_safe: res.exitCode === 0,
254
+ });
255
+ });
256
+
257
+ api.registerGatewayMethod("skillsScanner.report", async ({ respond }: any) => {
258
+ if (!isVenvReady(VENV_PYTHON))
259
+ return respond(false, { error: "Python dependencies not ready" });
260
+ if (scanDirs.length === 0) return respond(false, { error: "No scan directories found" });
261
+ const report = await buildDailyReport(
262
+ scanDirs,
263
+ behavioral,
264
+ apiUrl,
265
+ useLLM,
266
+ policy,
267
+ api.logger,
268
+ VENV_PYTHON,
269
+ SCAN_SCRIPT
270
+ );
271
+ respond(true, { report, state: loadState() });
272
+ });
273
+
274
+ // CLI commands
275
+ api.registerCli(
276
+ ({ program }: any) => {
277
+ const cmd = program.command("skills-scanner").description("OpenClaw Skills 安全扫描工具");
278
+
279
+ cmd
280
+ .command("scan <path>")
281
+ .description("扫描单个 Skill")
282
+ .option("--detailed", "显示所有发现")
283
+ .option("--behavioral", "启用行为分析")
284
+ .action(async (p: string, opts: any) => {
285
+ const res = await runScan(VENV_PYTHON, SCAN_SCRIPT, "scan", expandPath(p), {
286
+ ...opts,
287
+ apiUrl,
288
+ useLLM,
289
+ policy,
290
+ });
291
+ console.log(res.output);
292
+ process.exit(res.exitCode);
293
+ });
294
+
295
+ cmd
296
+ .command("batch <directory>")
297
+ .description("批量扫描目录")
298
+ .option("--recursive", "递归扫描子目录")
299
+ .option("--detailed", "显示所有发现")
300
+ .option("--behavioral", "启用行为分析")
301
+ .action(async (d: string, opts: any) => {
302
+ const res = await runScan(VENV_PYTHON, SCAN_SCRIPT, "batch", expandPath(d), {
303
+ ...opts,
304
+ apiUrl,
305
+ useLLM,
306
+ policy,
307
+ });
308
+ console.log(res.output);
309
+ process.exit(res.exitCode);
310
+ });
311
+
312
+ cmd
313
+ .command("report")
314
+ .description("生成日报")
315
+ .action(async () => {
316
+ const report = await buildDailyReport(
317
+ scanDirs,
318
+ behavioral,
319
+ apiUrl,
320
+ useLLM,
321
+ policy,
322
+ console,
323
+ VENV_PYTHON,
324
+ SCAN_SCRIPT
325
+ );
326
+ console.log(report);
327
+ });
328
+
329
+ cmd
330
+ .command("health")
331
+ .description("检查 API 服务健康状态")
332
+ .action(async () => {
333
+ if (!isVenvReady(VENV_PYTHON)) {
334
+ console.error("❌ Python 依赖未就绪");
335
+ process.exit(1);
336
+ }
337
+
338
+ try {
339
+ const { exec } = await import("node:child_process");
340
+ const { promisify } = await import("node:util");
341
+ const execAsync = promisify(exec);
342
+
343
+ const cmd = `"${VENV_PYTHON}" "${SCAN_SCRIPT}" --api-url "${apiUrl}" health`;
344
+ const env = { ...process.env };
345
+ delete env.http_proxy;
346
+ delete env.https_proxy;
347
+ delete env.HTTP_PROXY;
348
+ delete env.HTTPS_PROXY;
349
+ delete env.all_proxy;
350
+ delete env.ALL_PROXY;
351
+
352
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 5000, env });
353
+ const output = (stdout + stderr).trim();
354
+ console.log(output);
355
+
356
+ if (output.includes("✓") || output.includes("OK")) {
357
+ process.exit(0);
358
+ } else {
359
+ process.exit(1);
360
+ }
361
+ } catch (err: any) {
362
+ console.error(`❌ 连接失败: ${err.message}`);
363
+ console.error(`\n💡 请确保 skill-scanner-api 服务正在运行:`);
364
+ console.error(` skill-scanner-api`);
365
+ process.exit(1);
366
+ }
367
+ });
368
+ },
369
+ { commands: ["skills-scanner"] }
370
+ );
371
+
372
+ api.logger.info("[skills-scanner] ✅ Plugin registered");
373
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "id": "skills-scanner",
3
+ "name": "Skills Scanner",
4
+ "description": "Security scanner for OpenClaw Skills to detect potential threats",
5
+ "version": "2026.3.10",
6
+ "author": "OpenClaw",
7
+ "skills": ["./skills"],
8
+ "configSchema": {
9
+ "type": "object",
10
+ "additionalProperties": false,
11
+ "properties": {
12
+ "apiUrl": {
13
+ "type": "string",
14
+ "description": "Scanner API service URL",
15
+ "default": "http://localhost:8000"
16
+ },
17
+ "scanDirs": {
18
+ "type": "array",
19
+ "items": {
20
+ "type": "string"
21
+ },
22
+ "description": "List of directories to scan for Skills",
23
+ "default": []
24
+ },
25
+ "behavioral": {
26
+ "type": "boolean",
27
+ "description": "Enable behavioral analysis (slower but more accurate)",
28
+ "default": false
29
+ },
30
+ "useLLM": {
31
+ "type": "boolean",
32
+ "description": "Enable LLM-based semantic analysis",
33
+ "default": false
34
+ },
35
+ "policy": {
36
+ "type": "string",
37
+ "enum": ["strict", "balanced", "permissive"],
38
+ "description": "Scanning policy: strict (more false positives) / balanced (recommended) / permissive (may miss threats)",
39
+ "default": "balanced"
40
+ },
41
+ "preInstallScan": {
42
+ "type": "string",
43
+ "enum": ["on", "off"],
44
+ "description": "Enable pre-installation scanning (monitors directories for new Skills)",
45
+ "default": "on"
46
+ },
47
+ "onUnsafe": {
48
+ "type": "string",
49
+ "enum": ["quarantine", "delete", "warn"],
50
+ "description": "Action to take when unsafe Skill is detected: quarantine (recommended) / delete / warn",
51
+ "default": "quarantine"
52
+ },
53
+ "injectSecurityGuidance": {
54
+ "type": "boolean",
55
+ "description": "Inject Skills security guidance into system prompt (requires AI to scan before installing Skills)",
56
+ "default": true
57
+ }
58
+ }
59
+ }
60
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@pwddd/skills-scanner",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw Skills security scanner plugin - detect malicious code, data exfiltration, and prompt injection",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "author": "pwddd",
8
+ "license": "MIT",
9
+ "keywords": [
10
+ "openclaw",
11
+ "openclaw-plugin",
12
+ "security",
13
+ "scanner",
14
+ "skills",
15
+ "malware-detection"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/openclaw/openclaw.git",
20
+ "directory": "extensions/skills-scanner"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/openclaw/openclaw/issues"
24
+ },
25
+ "homepage": "https://github.com/openclaw/openclaw/tree/main/extensions/skills-scanner#readme",
26
+ "files": [
27
+ "index.ts",
28
+ "openclaw.plugin.json",
29
+ "README.md",
30
+ "src/**/*.ts",
31
+ "skills/**/*"
32
+ ],
33
+ "dependencies": {
34
+ "@sinclair/typebox": "0.34.48"
35
+ },
36
+ "peerDependencies": {
37
+ "openclaw": ">=2026.3.9"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "openclaw": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "openclaw": {
45
+ "extensions": [
46
+ "./index.ts"
47
+ ]
48
+ }
49
+ }
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: skills-scanner
3
+ description: OpenClaw Skills 安全扫描工具,使用 Cisco AI Skill Scanner 检测恶意代码、数据窃取、提示注入等威胁。
4
+ version: 1.0.0
5
+ user-invocable: true
6
+ metadata: {"openclaw": {"emoji": "🔍", "requires": {"bins": ["uv", "python3"]}, "install": [{"id": "uv-brew", "kind": "brew", "formula": "uv", "bins": ["uv"], "label": "安装 uv (macOS)", "os": ["darwin"]}, {"id": "uv-curl", "kind": "download", "url": "https://astral.sh/uv/install.sh", "label": "安装 uv (Linux)", "os": ["linux"]}]}}
7
+ ---
8
+
9
+ # Skills 安全扫描工具 🔍
10
+
11
+ OpenClaw Skills 安全扫描工具,检测恶意代码、数据窃取、提示注入等威胁。
12
+
13
+ ## 重要提示
14
+
15
+ **在向用户展示扫描结果前,请务必:**
16
+ 1. 检查结果中是否包含英文内容(如 category、description、finding 详情等)
17
+ 2. 如果包含英文,将所有英文内容翻译为中文后再展示给用户
18
+ 3. 保持技术术语的准确性(如 CRITICAL、HIGH、MEDIUM、LOW 可保留或翻译为:严重、高危、中危、低危)
19
+ 4. 确保翻译后的内容清晰易懂,符合中文表达习惯
20
+
21
+ 示例翻译:
22
+ - "Prompt injection detected" → "检测到提示注入"
23
+ - "Unauthorized file access" → "未授权的文件访问"
24
+ - "Data exfiltration attempt" → "数据窃取尝试"
25
+ - "Malicious code execution" → "恶意代码执行"
26
+
27
+ ## 环境准备(首次使用)
28
+
29
+ 首次运行前,检查并安装依赖:
30
+
31
+ ```bash
32
+ # 检查 uv 是否可用
33
+ which uv || echo "请安装 uv: brew install uv 或 curl -LsSf https://astral.sh/uv/install.sh | sh"
34
+
35
+ # 安装依赖到隔离虚拟环境
36
+ uv venv {baseDir}/.venv --python 3.10 --quiet
37
+ uv pip install --python {baseDir}/.venv/bin/python requests --quiet
38
+ ```
39
+
40
+ 安装只需执行一次。
41
+
42
+ ## 配置
43
+
44
+ 扫描器需要运行中的 API 服务。在 OpenClaw 配置中设置 API URL:
45
+
46
+ ```json
47
+ {
48
+ "plugins": {
49
+ "entries": {
50
+ "skills-scanner": {
51
+ "config": {
52
+ "apiUrl": "http://localhost:8000"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ 或直接调用时使用 `--api-url` 参数:
61
+
62
+ ```bash
63
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url http://localhost:8000 scan <路径>
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 单个 Skill 扫描
69
+
70
+ **触发词**: "扫描 skill"、"检查这个 skill"、"安全检查 [路径]"
71
+
72
+ ### 基础扫描(推荐,速度快)
73
+
74
+ ```bash
75
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} scan <skill路径>
76
+ ```
77
+
78
+ ### 详细模式(显示所有发现)
79
+
80
+ ```bash
81
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} scan <skill路径> --detailed
82
+ ```
83
+
84
+ ### 深度扫描(加入行为分析)
85
+
86
+ ```bash
87
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} scan <skill路径> --detailed --behavioral
88
+ ```
89
+
90
+ ### 最强扫描(加入 LLM 语义分析)
91
+
92
+ ```bash
93
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} scan <skill路径> --detailed --behavioral --llm
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 批量扫描
99
+
100
+ **触发词**: "批量扫描"、"扫描所有 skills"、"检查 skills 目录"
101
+
102
+ ### 扫描指定目录下的所有 Skills
103
+
104
+ ```bash
105
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} batch <目录路径>
106
+ ```
107
+
108
+ ### 递归扫描(含子目录)
109
+
110
+ ```bash
111
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} batch <目录路径> --recursive
112
+ ```
113
+
114
+ ### 批量扫描并输出 JSON 报告
115
+
116
+ ```bash
117
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} batch <目录路径> --detailed --json /tmp/scan-report.json
118
+ ```
119
+
120
+ ### 常用目录示例
121
+
122
+ 扫描 OpenClaw 默认 skills 目录:
123
+ ```bash
124
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} batch ~/.openclaw/skills
125
+ ```
126
+
127
+ 扫描 workspace skills:
128
+ ```bash
129
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} batch ~/.openclaw/workspace/skills --recursive
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 健康检查
135
+
136
+ 检查 API 服务是否运行:
137
+
138
+ ```bash
139
+ {baseDir}/.venv/bin/python {baseDir}/scan.py --api-url {apiUrl} health
140
+ ```
141
+
142
+ ---
143
+
144
+ ## 结果解读
145
+
146
+ | 状态 | 含义 |
147
+ |------|------|
148
+ | ✅ 安全 | 未检测到 HIGH/CRITICAL 问题,可正常使用 |
149
+ | ⚠️ 需关注 | 存在 LOW/MEDIUM 问题,建议人工复核 |
150
+ | ❌ 发现问题 | 存在 HIGH/CRITICAL 威胁,**强烈建议不要安装** |
151
+
152
+ ### 严重级别说明
153
+
154
+ - **CRITICAL**: 主动利用尝试(数据窃取、代码注入)
155
+ - **HIGH**: 危险模式(提示注入、未授权访问)
156
+ - **MEDIUM**: 可疑行为(未声明的能力、误导性描述)
157
+ - **LOW**: 轻微风险,需人工判断
158
+
159
+ ---
160
+
161
+ ## 参数说明
162
+
163
+ | 参数 | 说明 |
164
+ |------|------|
165
+ | `--api-url <url>` | API 服务地址(默认: http://localhost:8000) |
166
+ | `--detailed` | 显示每条 finding 的完整详情 |
167
+ | `--behavioral` | 启用 AST 数据流分析(更准确,稍慢) |
168
+ | `--llm` | 启用 LLM 语义分析(最准确,需 API 支持) |
169
+ | `--recursive` | 批量扫描时递归子目录 |
170
+ | `--json <文件>` | 将结果保存为 JSON 文件 |
171
+ | `--policy <strict\|balanced\|permissive>` | 扫描策略(默认: balanced) |
172
+
173
+ ---
174
+
175
+ ## 注意事项
176
+
177
+ - **扫描结果不等于安全保证**。`is_safe=True` 表示未检测到已知威胁模式,不代表 skill 绝对安全。
178
+ - 扫描使用静态分析,不会执行任何 skill 中的代码。
179
+ - 退出码 `0` 表示安全,`1` 表示存在问题(便于 CI/CD 集成)。
180
+ - `{apiUrl}` 占位符会自动替换为插件配置中的 API URL。