@pwddd/skills-scanner 3.0.10 → 3.0.12

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/index.ts CHANGED
@@ -171,7 +171,8 @@ export default function register(api: OpenClawPluginApi) {
171
171
  // Register cron job (only in Gateway mode)
172
172
  const isGatewayMode = !!(api as any).runtime;
173
173
  if (isGatewayMode) {
174
- await ensureCronJob(api.logger);
174
+ const runtime = (api as any).runtime;
175
+ await ensureCronJob(api.logger, runtime);
175
176
  }
176
177
 
177
178
  api.logger.info("[skills-scanner] ─────────────────────────────────────");
@@ -396,6 +397,3 @@ export default function register(api: OpenClawPluginApi) {
396
397
 
397
398
  api.logger.info("[skills-scanner] ✅ Plugin registered");
398
399
  }
399
-
400
-
401
-
@@ -2,7 +2,7 @@
2
2
  "id": "skills-scanner",
3
3
  "name": "Skills Scanner",
4
4
  "description": "Security scanner for OpenClaw Skills to detect potential threats",
5
- "version": "3.0.10",
5
+ "version": "3.0.12",
6
6
  "author": "pwddd",
7
7
  "skills": ["./skills"],
8
8
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pwddd/skills-scanner",
3
- "version": "3.0.10",
3
+ "version": "3.0.12",
4
4
  "description": "OpenClaw Skills security scanner plugin - detect malicious code, data exfiltration, and prompt injection",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/src/commands.ts CHANGED
@@ -225,7 +225,7 @@ export function createCommandHandlers(
225
225
  }
226
226
 
227
227
  saveState({ ...state, cronJobId: undefined });
228
- await ensureCronJob(logger);
228
+ await ensureCronJob(logger, undefined);
229
229
 
230
230
  const newState = loadState() as any;
231
231
  if (newState.cronJobId) {
package/src/cron.ts CHANGED
@@ -9,16 +9,124 @@ const CRON_JOB_NAME = "skills-daily-report";
9
9
  const CRON_SCHEDULE = "0 8 * * *";
10
10
  const CRON_TIMEZONE = "Asia/Shanghai";
11
11
 
12
- export async function ensureCronJob(logger: any): Promise<void> {
13
- const state = loadState() as any;
12
+ /**
13
+ * Detect the correct OpenClaw command (openclaw vs npx openclaw)
14
+ */
15
+ function getOpenClawCommand(): string {
16
+ // 1. Check environment variable
17
+ if (process.env.OPENCLAW_CLI_PATH) {
18
+ return process.env.OPENCLAW_CLI_PATH;
19
+ }
14
20
 
15
- logger.info("[skills-scanner] ─────────────────────────────────────");
16
- logger.info("[skills-scanner] 🕐 Checking cron job...");
21
+ // 2. Check if running via npx
22
+ const argv1 = process.argv[1];
23
+ if (argv1?.includes("npx") || argv1?.includes("_npx")) {
24
+ return "npx openclaw";
25
+ }
26
+
27
+ if (process.env.npm_execpath?.includes("npx")) {
28
+ return "npx openclaw";
29
+ }
30
+
31
+ // 3. Try global openclaw command
32
+ try {
33
+ execSync("openclaw --version", {
34
+ encoding: "utf-8",
35
+ timeout: 3000,
36
+ stdio: "pipe"
37
+ });
38
+ return "openclaw";
39
+ } catch {
40
+ // openclaw command not available
41
+ }
42
+
43
+ // 4. Try npx as fallback
44
+ try {
45
+ execSync("npx openclaw --version", {
46
+ encoding: "utf-8",
47
+ timeout: 5000,
48
+ stdio: "pipe"
49
+ });
50
+ return "npx openclaw";
51
+ } catch {
52
+ // npx also not available
53
+ }
54
+
55
+ // 5. Default to openclaw (will fail with clear error)
56
+ return "openclaw";
57
+ }
58
+
59
+ /**
60
+ * Create cron job via Gateway API (most reliable)
61
+ */
62
+ async function ensureCronJobViaAPI(runtime: any, logger: any): Promise<boolean> {
63
+ if (!runtime?.cron) {
64
+ logger.debug("[skills-scanner] Cron API not available");
65
+ return false;
66
+ }
67
+
68
+ try {
69
+ const state = loadState() as any;
70
+
71
+ // Check if job already exists
72
+ const jobs = await runtime.cron.list({ includeDisabled: true });
73
+ const existing = jobs.find((j: any) => j.name === CRON_JOB_NAME);
74
+
75
+ if (existing) {
76
+ logger.info(`[skills-scanner] ✅ Job already exists: ${existing.id}`);
77
+ if (state.cronJobId !== existing.id) {
78
+ saveState({ ...state, cronJobId: existing.id });
79
+ }
80
+ return true;
81
+ }
82
+
83
+ // Create new job
84
+ logger.info("[skills-scanner] 📝 Creating cron job via API...");
85
+ const job = await runtime.cron.add({
86
+ name: CRON_JOB_NAME,
87
+ enabled: true,
88
+ schedule: {
89
+ kind: "cron",
90
+ expr: CRON_SCHEDULE,
91
+ tz: CRON_TIMEZONE
92
+ },
93
+ payload: {
94
+ kind: "agentTurn",
95
+ message: "Please run /skills-scanner scan --report and send results to this channel"
96
+ },
97
+ delivery: {
98
+ mode: "announce",
99
+ channel: "last"
100
+ }
101
+ });
102
+
103
+ saveState({ ...state, cronJobId: job.id });
104
+ logger.info(`[skills-scanner] ✅ Job created successfully via API: ${job.id}`);
105
+ logger.info(
106
+ `[skills-scanner] 📅 Schedule: Daily at ${CRON_SCHEDULE.split(" ")[1]}:${CRON_SCHEDULE.split(" ")[0]} (${CRON_TIMEZONE})`
107
+ );
108
+ logger.info("[skills-scanner] 📬 Reports will be delivered to the last active channel");
109
+ return true;
110
+ } catch (err: any) {
111
+ logger.warn(`[skills-scanner] API creation failed: ${err.message}`);
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Create cron job via CLI (fallback)
118
+ */
119
+ async function ensureCronJobViaCLI(logger: any): Promise<void> {
120
+ const openclawCmd = getOpenClawCommand();
121
+ logger.info(`[skills-scanner] Using CLI command: ${openclawCmd}`);
122
+
123
+ const state = loadState() as any;
124
+ const state = loadState() as any;
17
125
 
18
126
  try {
19
127
  let jobs: any[] = [];
20
128
  try {
21
- const listResult = execSync("openclaw cron list --format json", {
129
+ const listResult = execSync(`${openclawCmd} cron list --format json`, {
22
130
  encoding: "utf-8",
23
131
  timeout: 5000,
24
132
  });
@@ -26,7 +134,7 @@ export async function ensureCronJob(logger: any): Promise<void> {
26
134
  } catch (listErr: any) {
27
135
  logger.debug("[skills-scanner] JSON format not supported, trying text parsing");
28
136
  try {
29
- const listResult = execSync("openclaw cron list", {
137
+ const listResult = execSync(`${openclawCmd} cron list`, {
30
138
  encoding: "utf-8",
31
139
  timeout: 5000,
32
140
  });
@@ -57,7 +165,7 @@ export async function ensureCronJob(logger: any): Promise<void> {
57
165
  for (let i = 1; i < existingJobs.length; i++) {
58
166
  const jobId = existingJobs[i].id || existingJobs[i].jobId;
59
167
  try {
60
- execSync(`openclaw cron remove ${jobId}`, {
168
+ execSync(`${openclawCmd} cron remove ${jobId}`, {
61
169
  encoding: "utf-8",
62
170
  timeout: 5000,
63
171
  });
@@ -82,7 +190,7 @@ export async function ensureCronJob(logger: any): Promise<void> {
82
190
  if (needsUpdate) {
83
191
  logger.info(`[skills-scanner] 🔄 Job config changed, updating...`);
84
192
  try {
85
- execSync(`openclaw cron remove ${jobId}`, {
193
+ execSync(`${openclawCmd} cron remove ${jobId}`, {
86
194
  encoding: "utf-8",
87
195
  timeout: 5000,
88
196
  });
@@ -106,12 +214,12 @@ export async function ensureCronJob(logger: any): Promise<void> {
106
214
  }
107
215
  }
108
216
 
109
- logger.info("[skills-scanner] 📝 Creating cron job...");
217
+ logger.info("[skills-scanner] 📝 Creating cron job via CLI...");
110
218
 
111
219
  // Create cron job with --announce and --channel last
112
220
  // This will deliver to the last place the agent replied
113
221
  const cronCmd = [
114
- "openclaw cron add",
222
+ `${openclawCmd} cron add`,
115
223
  `--name "${CRON_JOB_NAME}"`,
116
224
  `--cron "${CRON_SCHEDULE}"`,
117
225
  `--tz "${CRON_TIMEZONE}"`,
@@ -151,11 +259,11 @@ export async function ensureCronJob(logger: any): Promise<void> {
151
259
  err.message.includes("command not found") ||
152
260
  err.message.includes("ENOENT")
153
261
  ) {
154
- logger.error("[skills-scanner] ❌ openclaw command not found, please check installation");
262
+ logger.error(`[skills-scanner] ❌ ${openclawCmd} command not found, please check installation`);
155
263
  } else {
156
264
  logger.info("[skills-scanner] 💡 Please manually register cron job:");
157
265
  logger.info("[skills-scanner]");
158
- logger.info("[skills-scanner] openclaw cron add \\");
266
+ logger.info(`[skills-scanner] ${openclawCmd} cron add \\`);
159
267
  logger.info(`[skills-scanner] --name "${CRON_JOB_NAME}" \\`);
160
268
  logger.info(`[skills-scanner] --cron "${CRON_SCHEDULE}" \\`);
161
269
  logger.info(`[skills-scanner] --tz "${CRON_TIMEZONE}" \\`);
@@ -172,3 +280,24 @@ export async function ensureCronJob(logger: any): Promise<void> {
172
280
  }
173
281
  }
174
282
  }
283
+
284
+ /**
285
+ * Ensure cron job exists (tries API first, falls back to CLI)
286
+ */
287
+ export async function ensureCronJob(logger: any, runtime?: any): Promise<void> {
288
+ logger.info("[skills-scanner] ─────────────────────────────────────");
289
+ logger.info("[skills-scanner] 🕐 Checking cron job...");
290
+
291
+ // Try API first (most reliable)
292
+ if (runtime) {
293
+ const success = await ensureCronJobViaAPI(runtime, logger);
294
+ if (success) {
295
+ logger.info("[skills-scanner] ✅ Cron job configured via API");
296
+ return;
297
+ }
298
+ logger.info("[skills-scanner] Falling back to CLI method...");
299
+ }
300
+
301
+ // Fallback to CLI
302
+ await ensureCronJobViaCLI(logger);
303
+ }
@@ -21,6 +21,8 @@ export const SKILLS_SECURITY_GUIDANCE = [
21
21
  "",
22
22
  "- **Local Skills**: ANY local path or temporary extraction directory MUST be scanned. No skipping.",
23
23
  "- **ClawHub Skills**: Even if marked as audited/official, re-scan before install/run.",
24
+ "- **Manual downloads**: When user manually downloads a Skill (via git clone, curl, browser download, etc.), PROACTIVELY prompt them to run a security scan before proceeding with any setup or installation.",
25
+ "- **All installation methods**: Whether Skills are added via ClawHub, manual download, or any other method, security scanning is MANDATORY before use.",
24
26
  "- **Error handling priority**: If user reports errors with a Skill, scan it FIRST before attempting fixes or troubleshooting.",
25
27
  "- **Download method agnostic**: Regardless of how the user obtained the Skill (git clone, download, curl, manual copy), scan it before use.",
26
28
  "- **No bypass justifications**: Never skip scanning due to \"user insistence\", \"time pressure\", \"trusted source\", or \"previous scan\". If scanning is impossible, REFUSE to proceed and explain why.",