@pwddd/skills-scanner 3.0.22 → 4.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.
- package/README.md +3 -509
- package/index.ts +210 -212
- package/openclaw.plugin.json +112 -70
- package/package.json +64 -49
- package/skills/skills-scanner/SKILL.md +245 -1062
- package/src/api-client.ts +275 -0
- package/src/before-install-hook.ts +274 -0
- package/src/cache.ts +138 -0
- package/src/commands.ts +56 -152
- package/src/config-validator.ts +94 -0
- package/src/config.ts +187 -170
- package/src/cron-manager.ts +158 -0
- package/src/debug.ts +40 -0
- package/src/error-handler.ts +103 -0
- package/src/metrics.ts +140 -0
- package/src/prompt-guidance.ts +42 -250
- package/src/rate-limiter.ts +102 -0
- package/src/scanner.ts +230 -54
- package/src/state.ts +119 -71
- package/src/structured-logger.ts +97 -0
- package/src/types.ts +72 -50
- package/skills/skills-scanner/__pycache__/scan.cpython-314.pyc +0 -0
- package/skills/skills-scanner/scan.py +0 -446
- package/src/cron.ts +0 -292
- package/src/deps.ts +0 -77
- package/src/high-risk-operation-guard.ts +0 -62
- package/src/prompt-injection-guard.ts +0 -56
- package/src/report.ts +0 -100
- package/src/watcher.ts +0 -125
package/index.ts
CHANGED
|
@@ -4,180 +4,243 @@
|
|
|
4
4
|
* Security scanner for OpenClaw Skills to detect potential threats.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import os from "node:os";
|
|
10
|
-
import { existsSync } from "node:fs";
|
|
11
9
|
import type { ScannerConfig } from "./src/types.js";
|
|
12
10
|
import { skillsScannerConfigSchema, generateConfigGuide } from "./src/config.js";
|
|
13
11
|
import {
|
|
14
12
|
loadState,
|
|
15
13
|
saveState,
|
|
16
14
|
expandPath,
|
|
17
|
-
defaultScanDirs,
|
|
18
15
|
isFirstRun,
|
|
19
16
|
markConfigReviewed,
|
|
17
|
+
getStateDir,
|
|
20
18
|
} from "./src/state.js";
|
|
21
|
-
import { ensureDeps, getPythonCommand, isPythonReady } from "./src/deps.js";
|
|
22
19
|
import { runScan } from "./src/scanner.js";
|
|
23
|
-
import {
|
|
24
|
-
import { ensureCronJob, checkCronJobStatus } from "./src/cron.js";
|
|
25
|
-
import { startWatcher } from "./src/watcher.js";
|
|
20
|
+
import { ensureCronJob } from "./src/cron-manager.js";
|
|
26
21
|
import { createCommandHandlers } from "./src/commands.js";
|
|
27
22
|
import { SKILLS_SECURITY_GUIDANCE } from "./src/prompt-guidance.js";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
23
|
+
import { handleBeforeInstall } from "./src/before-install-hook.js";
|
|
24
|
+
import type { BeforeInstallEvent } from "./src/before-install-hook.js";
|
|
25
|
+
import { validateConfig } from "./src/config-validator.js";
|
|
26
|
+
import { getMetricsSummary } from "./src/metrics.js";
|
|
27
|
+
import { debugLog, isDebugMode } from "./src/debug.js";
|
|
30
28
|
|
|
31
29
|
// Constants
|
|
32
30
|
const PLUGIN_ROOT = process.env.OPENCLAW_PLUGIN_ROOT || __dirname;
|
|
33
|
-
const SKILL_DIR = join(PLUGIN_ROOT, "skills", "skills-scanner");
|
|
34
|
-
const SCAN_SCRIPT = join(SKILL_DIR, "scan.py");
|
|
35
|
-
const STATE_DIR = join(os.homedir(), ".openclaw", "skills-scanner");
|
|
36
|
-
const QUARANTINE_DIR = join(STATE_DIR, "quarantine");
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
export default definePluginEntry({
|
|
33
|
+
id: "skills-scanner",
|
|
34
|
+
name: "Skills Scanner",
|
|
35
|
+
description: "Security scanner for OpenClaw Skills to detect potential threats",
|
|
36
|
+
configSchema: skillsScannerConfigSchema,
|
|
37
|
+
register(api) {
|
|
38
|
+
// Get state directory using official API
|
|
39
|
+
const STATE_DIR = getStateDir(api.runtime);
|
|
40
|
+
const QUARANTINE_DIR = join(STATE_DIR, "quarantine");
|
|
39
41
|
|
|
40
|
-
export default function register(api: OpenClawPluginApi) {
|
|
41
42
|
const cfg: ScannerConfig =
|
|
42
43
|
api.config?.plugins?.entries?.["skills-scanner"]?.config ?? {};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
|
|
45
|
+
// Validate configuration
|
|
46
|
+
const validation = validateConfig(cfg, api.logger);
|
|
47
|
+
if (!validation.valid) {
|
|
48
|
+
api.logger.error("[skills-scanner] ❌ Invalid configuration, plugin may not work correctly");
|
|
49
|
+
// Continue loading but with warnings
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const apiUrl = cfg.apiUrl ?? "https://110.vemic.com/skills-scanner";
|
|
48
53
|
const behavioral = cfg.behavioral ?? false;
|
|
49
54
|
const useLLM = cfg.useLLM ?? false;
|
|
50
55
|
const policy = cfg.policy ?? "balanced";
|
|
51
|
-
const preInstallScan = cfg.preInstallScan ?? "on";
|
|
52
56
|
const onUnsafe = cfg.onUnsafe ?? "warn";
|
|
53
57
|
const injectSecurityGuidance = cfg.injectSecurityGuidance ?? true;
|
|
54
|
-
const
|
|
55
|
-
const enableHighRiskOperationGuard = cfg.enableHighRiskOperationGuard ?? false;
|
|
58
|
+
const enableBeforeInstallHook = cfg.enableBeforeInstallHook ?? true;
|
|
56
59
|
|
|
57
60
|
api.logger.info("[skills-scanner] ═══════════════════════════════════════");
|
|
58
61
|
api.logger.info("[skills-scanner] Plugin loading...");
|
|
59
62
|
api.logger.info(`[skills-scanner] API URL: ${apiUrl}`);
|
|
60
|
-
api.logger.info(`[skills-scanner]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
api.logger.info(`[skills-scanner] Before-install hook: ${enableBeforeInstallHook ? "✅ ENABLED" : "❌ DISABLED"}`);
|
|
64
|
+
|
|
65
|
+
if (isDebugMode()) {
|
|
66
|
+
api.logger.info("[skills-scanner] 🐛 DEBUG MODE ENABLED");
|
|
67
|
+
debugLog(api.logger, "Full configuration", cfg);
|
|
68
|
+
}
|
|
64
69
|
|
|
65
70
|
// Inject system prompt guidance (can be disabled via config)
|
|
66
71
|
if (injectSecurityGuidance) {
|
|
67
|
-
// Build combined guidance
|
|
68
|
-
const guidanceParts = [SKILLS_SECURITY_GUIDANCE];
|
|
69
|
-
|
|
70
|
-
if (enablePromptInjectionGuard) {
|
|
71
|
-
guidanceParts.push(PROMPT_INJECTION_GUARD);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (enableHighRiskOperationGuard) {
|
|
75
|
-
guidanceParts.push(HIGH_RISK_OPERATION_GUARD);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const combinedGuidance = guidanceParts.join("\n\n");
|
|
79
|
-
|
|
80
72
|
api.on("before_prompt_build", async () => ({
|
|
81
|
-
prependSystemContext:
|
|
73
|
+
prependSystemContext: SKILLS_SECURITY_GUIDANCE,
|
|
82
74
|
}));
|
|
83
|
-
|
|
75
|
+
|
|
84
76
|
api.logger.info("[skills-scanner] ✅ Security guidance injected into system prompt");
|
|
85
|
-
if (enablePromptInjectionGuard) {
|
|
86
|
-
api.logger.info("[skills-scanner] - Prompt injection guard enabled");
|
|
87
|
-
}
|
|
88
|
-
if (enableHighRiskOperationGuard) {
|
|
89
|
-
api.logger.info("[skills-scanner] - High-risk operation guard enabled");
|
|
90
|
-
}
|
|
91
77
|
} else {
|
|
92
78
|
api.logger.info("[skills-scanner] ⏭️ Security guidance injection disabled");
|
|
93
79
|
}
|
|
94
80
|
|
|
81
|
+
// Register before_install hook (CRITICAL SECURITY GATE)
|
|
82
|
+
if (enableBeforeInstallHook) {
|
|
83
|
+
api.on("before_install", async (event: BeforeInstallEvent) => {
|
|
84
|
+
try {
|
|
85
|
+
return await handleBeforeInstall(event, {
|
|
86
|
+
apiUrl,
|
|
87
|
+
behavioral,
|
|
88
|
+
useLLM,
|
|
89
|
+
policy,
|
|
90
|
+
logger: api.logger,
|
|
91
|
+
enabled: enableBeforeInstallHook,
|
|
92
|
+
timeoutMs: cfg.scanTimeoutMs || 30000, // Use configured timeout or 30s default
|
|
93
|
+
});
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
api.logger.error("[skills-scanner] ❌ before_install hook error", {
|
|
96
|
+
error: err.message,
|
|
97
|
+
stack: err.stack,
|
|
98
|
+
sourcePath: event.sourcePath,
|
|
99
|
+
targetType: event.targetType,
|
|
100
|
+
targetName: event.targetName,
|
|
101
|
+
});
|
|
102
|
+
// Return safe default on error - allow installation but log the failure
|
|
103
|
+
return { block: false };
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
api.logger.info("[skills-scanner] 🛡️ before_install hook registered (installation interception active)");
|
|
108
|
+
} else {
|
|
109
|
+
api.logger.warn("[skills-scanner] ⚠️ before_install hook DISABLED - installations will NOT be intercepted!");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 在插件启动时设置系统 crontab 清理任务
|
|
113
|
+
(async () => {
|
|
114
|
+
try {
|
|
115
|
+
await ensureCronJob({
|
|
116
|
+
logger: api.logger,
|
|
117
|
+
config: api.config,
|
|
118
|
+
});
|
|
119
|
+
api.logger.info("[skills-scanner] ✅ System crontab cleanup task ensured");
|
|
120
|
+
} catch (err: any) {
|
|
121
|
+
api.logger.warn("[skills-scanner] ⚠️ Failed to setup system crontab", {
|
|
122
|
+
error: err.message,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
})();
|
|
126
|
+
|
|
127
|
+
// Register config_changed hook for hot reload
|
|
128
|
+
api.on("config_changed", async (newConfig: any) => {
|
|
129
|
+
api.logger.info("[skills-scanner] 🔄 Configuration changed, reloading...");
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const newCfg: ScannerConfig =
|
|
133
|
+
newConfig?.plugins?.entries?.["skills-scanner"]?.config ?? {};
|
|
134
|
+
|
|
135
|
+
// Validate new configuration
|
|
136
|
+
const validation = validateConfig(newCfg, api.logger);
|
|
137
|
+
if (!validation.valid) {
|
|
138
|
+
api.logger.error("[skills-scanner] ❌ Invalid new configuration, keeping old config");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check what changed
|
|
143
|
+
const apiUrlChanged = newCfg.apiUrl !== cfg.apiUrl;
|
|
144
|
+
|
|
145
|
+
if (apiUrlChanged) {
|
|
146
|
+
api.logger.info("[skills-scanner] API URL updated", {
|
|
147
|
+
old: cfg.apiUrl,
|
|
148
|
+
new: newCfg.apiUrl,
|
|
149
|
+
});
|
|
150
|
+
// Update global apiUrl variable
|
|
151
|
+
Object.assign(cfg, { apiUrl: newCfg.apiUrl });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
api.logger.info("[skills-scanner] ✅ Configuration reload completed");
|
|
155
|
+
} catch (err: any) {
|
|
156
|
+
api.logger.error("[skills-scanner] ❌ Configuration reload failed", {
|
|
157
|
+
error: err.message,
|
|
158
|
+
stack: err.stack,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
95
163
|
// Check if first run
|
|
96
|
-
const firstRun = isFirstRun(cfg);
|
|
164
|
+
const firstRun = isFirstRun(cfg, api.runtime);
|
|
97
165
|
if (firstRun) {
|
|
98
166
|
api.logger.info("[skills-scanner] 🎉 First run detected");
|
|
99
167
|
const configGuide = generateConfigGuide(
|
|
100
168
|
cfg,
|
|
101
169
|
apiUrl,
|
|
102
|
-
scanDirs,
|
|
103
170
|
behavioral,
|
|
104
171
|
useLLM,
|
|
105
172
|
policy,
|
|
106
|
-
preInstallScan,
|
|
107
173
|
onUnsafe
|
|
108
174
|
);
|
|
109
175
|
console.log(configGuide);
|
|
110
|
-
markConfigReviewed();
|
|
176
|
+
markConfigReviewed(api.runtime);
|
|
111
177
|
}
|
|
112
178
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
179
|
+
// Health check endpoint
|
|
180
|
+
api.registerHttpRoute({
|
|
181
|
+
method: "GET",
|
|
182
|
+
path: "/health/skills-scanner",
|
|
183
|
+
handler: async (req, res) => {
|
|
184
|
+
try {
|
|
185
|
+
const state = loadState(api.runtime);
|
|
186
|
+
|
|
187
|
+
// Check API availability
|
|
188
|
+
let apiStatus = "unknown";
|
|
189
|
+
try {
|
|
190
|
+
const response = await fetch(`${apiUrl}/health`, {
|
|
191
|
+
signal: AbortSignal.timeout(3000),
|
|
192
|
+
});
|
|
193
|
+
apiStatus = response.ok ? "available" : "unavailable";
|
|
194
|
+
} catch {
|
|
195
|
+
apiStatus = "unavailable";
|
|
120
196
|
}
|
|
121
|
-
})
|
|
122
|
-
.catch((err) => {
|
|
123
|
-
api.logger.error(`[skills-scanner] Dependency installation failed: ${err.message}`);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Helper for watcher alerts
|
|
128
|
-
function persistWatcherAlert(msg: string): void {
|
|
129
|
-
const state = loadState();
|
|
130
|
-
const alerts: string[] = (state as any).pendingAlerts ?? [];
|
|
131
|
-
alerts.push(`[${new Date().toLocaleString("en-US")}] ${msg}`);
|
|
132
|
-
saveState({ ...state, pendingAlerts: alerts } as any);
|
|
133
|
-
api.logger.warn(`[skills-scanner] ${msg}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Service: Install deps + start watcher
|
|
137
|
-
let stopWatcher: (() => void) | null = null;
|
|
138
|
-
|
|
139
|
-
api.registerService({
|
|
140
|
-
id: "skills-scanner-setup",
|
|
141
|
-
start: async () => {
|
|
142
|
-
api.logger.info("[skills-scanner] 🚀 Service starting...");
|
|
143
197
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
198
|
+
// Get performance metrics
|
|
199
|
+
const metrics = getMetricsSummary(STATE_DIR);
|
|
200
|
+
|
|
201
|
+
const health = {
|
|
202
|
+
status: "healthy",
|
|
203
|
+
plugin: {
|
|
204
|
+
version: api.version || "1.0.0",
|
|
205
|
+
id: api.id,
|
|
206
|
+
name: api.name,
|
|
207
|
+
},
|
|
208
|
+
api: {
|
|
209
|
+
url: apiUrl,
|
|
210
|
+
status: apiStatus,
|
|
211
|
+
},
|
|
212
|
+
state: {
|
|
213
|
+
lastScanAt: state.lastScanAt || null,
|
|
214
|
+
lastShutdownAt: state.lastShutdownAt || null,
|
|
215
|
+
pendingAlerts: (state as any).pendingAlerts?.length || 0,
|
|
216
|
+
},
|
|
217
|
+
metrics: {
|
|
218
|
+
totalScans: metrics.totalScans,
|
|
219
|
+
successRate: metrics.successRate.toFixed(2) + "%",
|
|
220
|
+
averageDurationMs: metrics.averageDurationMs,
|
|
221
|
+
lastScanAt: metrics.lastScanAt || null,
|
|
222
|
+
},
|
|
223
|
+
config: {
|
|
224
|
+
policy,
|
|
225
|
+
behavioral,
|
|
226
|
+
useLLM,
|
|
227
|
+
beforeInstallHook: enableBeforeInstallHook,
|
|
228
|
+
},
|
|
229
|
+
timestamp: new Date().toISOString(),
|
|
230
|
+
};
|
|
150
231
|
|
|
151
|
-
|
|
232
|
+
res.status(200).json(health);
|
|
233
|
+
} catch (err: any) {
|
|
234
|
+
api.logger.error("[skills-scanner] Health check failed", {
|
|
235
|
+
error: err.message,
|
|
236
|
+
});
|
|
152
237
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
behavioral,
|
|
159
|
-
apiUrl,
|
|
160
|
-
useLLM,
|
|
161
|
-
policy,
|
|
162
|
-
persistWatcherAlert,
|
|
163
|
-
api.logger,
|
|
164
|
-
PYTHON_CMD,
|
|
165
|
-
SCAN_SCRIPT,
|
|
166
|
-
QUARANTINE_DIR
|
|
167
|
-
);
|
|
168
|
-
api.logger.info("[skills-scanner] ✅ File monitoring started");
|
|
169
|
-
} else {
|
|
170
|
-
api.logger.info("[skills-scanner] ⏭️ Pre-install scan disabled");
|
|
238
|
+
res.status(503).json({
|
|
239
|
+
status: "unhealthy",
|
|
240
|
+
error: err.message,
|
|
241
|
+
timestamp: new Date().toISOString(),
|
|
242
|
+
});
|
|
171
243
|
}
|
|
172
|
-
|
|
173
|
-
// Auto-register cron job
|
|
174
|
-
api.logger.info("[skills-scanner] 🕐 Setting up weekly report cron job...");
|
|
175
|
-
await ensureCronJob(api.logger);
|
|
176
|
-
},
|
|
177
|
-
stop: () => {
|
|
178
|
-
api.logger.info("[skills-scanner] 🛑 Service stopping...");
|
|
179
|
-
stopWatcher?.();
|
|
180
|
-
stopWatcher = null;
|
|
181
244
|
},
|
|
182
245
|
});
|
|
183
246
|
|
|
@@ -185,21 +248,18 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
185
248
|
const handlers = createCommandHandlers(
|
|
186
249
|
cfg,
|
|
187
250
|
apiUrl,
|
|
188
|
-
scanDirs,
|
|
189
251
|
behavioral,
|
|
190
252
|
useLLM,
|
|
191
253
|
policy,
|
|
192
|
-
preInstallScan,
|
|
193
254
|
onUnsafe,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
api.logger
|
|
255
|
+
api.logger,
|
|
256
|
+
api.config
|
|
197
257
|
);
|
|
198
258
|
|
|
199
259
|
// Chat command: /skills-scanner
|
|
200
260
|
api.registerCommand({
|
|
201
261
|
name: "skills-scanner",
|
|
202
|
-
description: "Skills
|
|
262
|
+
description: "Skills 安全扫描工具。用法:/skills-scanner <子命令> [参数]",
|
|
203
263
|
acceptsArgs: true,
|
|
204
264
|
requireAuth: true,
|
|
205
265
|
handler: async (ctx: any) => {
|
|
@@ -212,20 +272,20 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
212
272
|
"",
|
|
213
273
|
"可用命令:",
|
|
214
274
|
"• `/skills-scanner scan <路径> [选项]` - 扫描 Skill",
|
|
275
|
+
"• `/skills-scanner scan clawhub <URL> [选项]` - 扫描 ClawHub Skill",
|
|
215
276
|
"• `/skills-scanner health` - 健康检查",
|
|
216
277
|
"• `/skills-scanner config [操作]` - 配置管理",
|
|
217
|
-
"• `/skills-scanner cron [操作]` - 定时任务管理",
|
|
218
278
|
"",
|
|
219
279
|
"扫描选项:",
|
|
220
280
|
"• `--detailed` - 显示详细发现",
|
|
221
281
|
"• `--behavioral` - 启用行为分析",
|
|
222
282
|
"• `--recursive` - 递归扫描子目录",
|
|
223
|
-
"• `--report` - 生成日报格式",
|
|
224
283
|
"",
|
|
225
284
|
"示例:",
|
|
226
285
|
"```",
|
|
227
286
|
"/skills-scanner scan ~/my-skill",
|
|
228
287
|
"/skills-scanner scan ~/skills --recursive",
|
|
288
|
+
"/skills-scanner scan clawhub https://clawhub.ai/username/project",
|
|
229
289
|
"/skills-scanner health",
|
|
230
290
|
"```",
|
|
231
291
|
"",
|
|
@@ -244,13 +304,11 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
244
304
|
return await handlers.handleHealthCommand();
|
|
245
305
|
} else if (subCommand === "config") {
|
|
246
306
|
return await handlers.handleConfigCommand(subArgs);
|
|
247
|
-
} else if (subCommand === "cron") {
|
|
248
|
-
return await handlers.handleCronCommand(subArgs);
|
|
249
307
|
} else if (subCommand === "help" || subCommand === "--help" || subCommand === "-h") {
|
|
250
308
|
return { text: handlers.getHelpText() };
|
|
251
309
|
} else {
|
|
252
310
|
return {
|
|
253
|
-
text: `❌
|
|
311
|
+
text: `❌ 未知子命令:${subCommand}\n\n使用 \`/skills-scanner help\` 查看帮助`,
|
|
254
312
|
};
|
|
255
313
|
}
|
|
256
314
|
},
|
|
@@ -260,38 +318,25 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
260
318
|
api.registerGatewayMethod("skillsScanner.scan", async ({ respond, params }: any) => {
|
|
261
319
|
const { path: p, mode = "scan", recursive = false, detailed = false } = params ?? {};
|
|
262
320
|
if (!p) return respond(false, { error: "Missing path parameter" });
|
|
263
|
-
if (!isPythonReady(PYTHON_CMD))
|
|
264
|
-
return respond(false, { error: "Python dependencies not ready" });
|
|
265
|
-
const res = await runScan(PYTHON_CMD, SCAN_SCRIPT, mode === "batch" ? "batch" : "scan", expandPath(p), {
|
|
266
|
-
recursive,
|
|
267
|
-
detailed,
|
|
268
|
-
behavioral,
|
|
269
|
-
apiUrl,
|
|
270
|
-
useLLM,
|
|
271
|
-
policy,
|
|
272
|
-
});
|
|
273
|
-
respond(res.exitCode === 0, {
|
|
274
|
-
output: res.output,
|
|
275
|
-
exitCode: res.exitCode,
|
|
276
|
-
is_safe: res.exitCode === 0,
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
321
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
322
|
+
try {
|
|
323
|
+
const res = await runScan(mode === "batch" ? "batch" : "scan", expandPath(p), {
|
|
324
|
+
recursive,
|
|
325
|
+
detailed,
|
|
326
|
+
behavioral,
|
|
327
|
+
apiUrl,
|
|
328
|
+
useLLM,
|
|
329
|
+
policy,
|
|
330
|
+
});
|
|
331
|
+
respond(res.exitCode === 0, {
|
|
332
|
+
output: res.output,
|
|
333
|
+
exitCode: res.exitCode,
|
|
334
|
+
is_safe: res.exitCode === 0,
|
|
335
|
+
data: res.data,
|
|
336
|
+
});
|
|
337
|
+
} catch (err: any) {
|
|
338
|
+
respond(false, { error: err.message });
|
|
339
|
+
}
|
|
295
340
|
});
|
|
296
341
|
|
|
297
342
|
// CLI commands
|
|
@@ -305,7 +350,7 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
305
350
|
.option("--detailed", "显示所有发现")
|
|
306
351
|
.option("--behavioral", "启用行为分析")
|
|
307
352
|
.action(async (p: string, opts: any) => {
|
|
308
|
-
const res = await runScan(
|
|
353
|
+
const res = await runScan("scan", expandPath(p), {
|
|
309
354
|
...opts,
|
|
310
355
|
apiUrl,
|
|
311
356
|
useLLM,
|
|
@@ -322,7 +367,7 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
322
367
|
.option("--detailed", "显示所有发现")
|
|
323
368
|
.option("--behavioral", "启用行为分析")
|
|
324
369
|
.action(async (d: string, opts: any) => {
|
|
325
|
-
const res = await runScan(
|
|
370
|
+
const res = await runScan("batch", expandPath(d), {
|
|
326
371
|
...opts,
|
|
327
372
|
apiUrl,
|
|
328
373
|
useLLM,
|
|
@@ -332,65 +377,18 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
332
377
|
process.exit(res.exitCode);
|
|
333
378
|
});
|
|
334
379
|
|
|
335
|
-
cmd
|
|
336
|
-
.command("report")
|
|
337
|
-
.description("生成日报")
|
|
338
|
-
.action(async () => {
|
|
339
|
-
const report = await buildDailyReport(
|
|
340
|
-
scanDirs,
|
|
341
|
-
behavioral,
|
|
342
|
-
apiUrl,
|
|
343
|
-
useLLM,
|
|
344
|
-
policy,
|
|
345
|
-
console,
|
|
346
|
-
PYTHON_CMD,
|
|
347
|
-
SCAN_SCRIPT
|
|
348
|
-
);
|
|
349
|
-
console.log(report);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
380
|
cmd
|
|
353
381
|
.command("health")
|
|
354
382
|
.description("检查 API 服务健康状态")
|
|
355
383
|
.action(async () => {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
const { exec } = await import("node:child_process");
|
|
363
|
-
const { promisify } = await import("node:util");
|
|
364
|
-
const execAsync = promisify(exec);
|
|
365
|
-
|
|
366
|
-
const cmd = `"${PYTHON_CMD}" "${SCAN_SCRIPT}" --api-url "${apiUrl}" health`;
|
|
367
|
-
const env = { ...process.env };
|
|
368
|
-
delete env.http_proxy;
|
|
369
|
-
delete env.https_proxy;
|
|
370
|
-
delete env.HTTP_PROXY;
|
|
371
|
-
delete env.HTTPS_PROXY;
|
|
372
|
-
delete env.all_proxy;
|
|
373
|
-
delete env.ALL_PROXY;
|
|
374
|
-
|
|
375
|
-
const { stdout, stderr } = await execAsync(cmd, { timeout: 5000, env });
|
|
376
|
-
const output = (stdout + stderr).trim();
|
|
377
|
-
console.log(output);
|
|
378
|
-
|
|
379
|
-
if (output.includes("✓") || output.includes("OK")) {
|
|
380
|
-
process.exit(0);
|
|
381
|
-
} else {
|
|
382
|
-
process.exit(1);
|
|
383
|
-
}
|
|
384
|
-
} catch (err: any) {
|
|
385
|
-
console.error(`❌ 连接失败: ${err.message}`);
|
|
386
|
-
console.error(`\n💡 请确保 skill-scanner-api 服务正在运行:`);
|
|
387
|
-
console.error(` skill-scanner-api`);
|
|
388
|
-
process.exit(1);
|
|
389
|
-
}
|
|
384
|
+
const res = await runScan("health", "", { apiUrl });
|
|
385
|
+
console.log(res.output);
|
|
386
|
+
process.exit(res.exitCode);
|
|
390
387
|
});
|
|
391
388
|
},
|
|
392
389
|
{ commands: ["skills-scanner"] }
|
|
393
390
|
);
|
|
394
391
|
|
|
395
392
|
api.logger.info("[skills-scanner] ✅ Plugin registered");
|
|
396
|
-
}
|
|
393
|
+
},
|
|
394
|
+
});
|