@pwddd/skills-scanner 3.0.23 → 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 +209 -211
- package/openclaw.plugin.json +112 -70
- package/package.json +64 -49
- package/skills/skills-scanner/SKILL.md +245 -1065
- 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/src/metrics.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance metrics module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
|
|
8
|
+
|
|
9
|
+
export interface ScanMetrics {
|
|
10
|
+
version: number;
|
|
11
|
+
totalScans: number;
|
|
12
|
+
successfulScans: number;
|
|
13
|
+
failedScans: number;
|
|
14
|
+
averageScanTimeMs: number;
|
|
15
|
+
lastScanDurationMs: number;
|
|
16
|
+
lastScanAt?: string;
|
|
17
|
+
scansByPolicy: {
|
|
18
|
+
strict: number;
|
|
19
|
+
balanced: number;
|
|
20
|
+
permissive: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const METRICS_VERSION = 1;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get metrics file path
|
|
28
|
+
*/
|
|
29
|
+
function getMetricsFile(stateDir: string): string {
|
|
30
|
+
return join(stateDir, "metrics.json");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load metrics from disk
|
|
35
|
+
*/
|
|
36
|
+
export function loadMetrics(stateDir: string): ScanMetrics {
|
|
37
|
+
try {
|
|
38
|
+
const metricsFile = getMetricsFile(stateDir);
|
|
39
|
+
return JSON.parse(readFileSync(metricsFile, "utf-8"));
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
version: METRICS_VERSION,
|
|
43
|
+
totalScans: 0,
|
|
44
|
+
successfulScans: 0,
|
|
45
|
+
failedScans: 0,
|
|
46
|
+
averageScanTimeMs: 0,
|
|
47
|
+
lastScanDurationMs: 0,
|
|
48
|
+
scansByPolicy: {
|
|
49
|
+
strict: 0,
|
|
50
|
+
balanced: 0,
|
|
51
|
+
permissive: 0,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Save metrics to disk
|
|
59
|
+
*/
|
|
60
|
+
export function saveMetrics(stateDir: string, metrics: ScanMetrics): void {
|
|
61
|
+
const metricsFile = getMetricsFile(stateDir);
|
|
62
|
+
mkdirSync(stateDir, { recursive: true });
|
|
63
|
+
writeFileSync(metricsFile, JSON.stringify(metrics, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Record a scan operation
|
|
68
|
+
*/
|
|
69
|
+
export function recordScan(
|
|
70
|
+
stateDir: string,
|
|
71
|
+
options: {
|
|
72
|
+
success: boolean;
|
|
73
|
+
durationMs: number;
|
|
74
|
+
policy: "strict" | "balanced" | "permissive";
|
|
75
|
+
}
|
|
76
|
+
): void {
|
|
77
|
+
const metrics = loadMetrics(stateDir);
|
|
78
|
+
|
|
79
|
+
metrics.totalScans++;
|
|
80
|
+
if (options.success) {
|
|
81
|
+
metrics.successfulScans++;
|
|
82
|
+
} else {
|
|
83
|
+
metrics.failedScans++;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Update average scan time
|
|
87
|
+
const totalTime = metrics.averageScanTimeMs * (metrics.totalScans - 1);
|
|
88
|
+
metrics.averageScanTimeMs = (totalTime + options.durationMs) / metrics.totalScans;
|
|
89
|
+
|
|
90
|
+
metrics.lastScanDurationMs = options.durationMs;
|
|
91
|
+
metrics.lastScanAt = new Date().toISOString();
|
|
92
|
+
|
|
93
|
+
// Update policy counter
|
|
94
|
+
metrics.scansByPolicy[options.policy]++;
|
|
95
|
+
|
|
96
|
+
saveMetrics(stateDir, metrics);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get metrics summary
|
|
101
|
+
*/
|
|
102
|
+
export function getMetricsSummary(stateDir: string): {
|
|
103
|
+
totalScans: number;
|
|
104
|
+
successRate: number;
|
|
105
|
+
averageDurationMs: number;
|
|
106
|
+
lastScanAt?: string;
|
|
107
|
+
} {
|
|
108
|
+
const metrics = loadMetrics(stateDir);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
totalScans: metrics.totalScans,
|
|
112
|
+
successRate:
|
|
113
|
+
metrics.totalScans > 0
|
|
114
|
+
? (metrics.successfulScans / metrics.totalScans) * 100
|
|
115
|
+
: 0,
|
|
116
|
+
averageDurationMs: Math.round(metrics.averageScanTimeMs),
|
|
117
|
+
lastScanAt: metrics.lastScanAt,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reset all metrics
|
|
123
|
+
*/
|
|
124
|
+
export function resetMetrics(stateDir: string): void {
|
|
125
|
+
const metrics: ScanMetrics = {
|
|
126
|
+
version: METRICS_VERSION,
|
|
127
|
+
totalScans: 0,
|
|
128
|
+
successfulScans: 0,
|
|
129
|
+
failedScans: 0,
|
|
130
|
+
averageScanTimeMs: 0,
|
|
131
|
+
lastScanDurationMs: 0,
|
|
132
|
+
scansByPolicy: {
|
|
133
|
+
strict: 0,
|
|
134
|
+
balanced: 0,
|
|
135
|
+
permissive: 0,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
saveMetrics(stateDir, metrics);
|
|
139
|
+
}
|
|
140
|
+
|
package/src/prompt-guidance.ts
CHANGED
|
@@ -1,278 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System prompt guidance for Skills security scanning
|
|
3
|
+
* Optimized for clarity and token efficiency
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
export const SKILLS_SECURITY_GUIDANCE = [
|
|
6
|
-
"##
|
|
7
|
+
"## 🔒 Mandatory: Scan Skills Before Install/Run",
|
|
7
8
|
"",
|
|
8
|
-
"
|
|
9
|
+
"**ALWAYS intercept and scan before proceeding with any skill-related operation.**",
|
|
9
10
|
"",
|
|
10
|
-
"
|
|
11
|
+
"### 🎯 Command Patterns to Intercept (Trigger Conditions)",
|
|
11
12
|
"",
|
|
12
|
-
"**
|
|
13
|
+
"**Installation commands - scan FIRST:**",
|
|
14
|
+
"- `openclaw clawhub install <name>`",
|
|
15
|
+
"- `openclaw skill install <name>`",
|
|
16
|
+
"- `openclaw skills add <name>`",
|
|
17
|
+
"- Natural: \"install this skill\", \"download this skill\", \"add skill\", \"帮我安装 xxx\"",
|
|
13
18
|
"",
|
|
14
|
-
"
|
|
19
|
+
"**Other triggers:**",
|
|
20
|
+
"- Skill file paths: `~/.openclaw/skills/xxx`",
|
|
21
|
+
"- ClawHub URLs: `https://clawhub.ai/username/project`",
|
|
22
|
+
"- User reports skill errors → Scan before troubleshooting",
|
|
15
23
|
"",
|
|
16
|
-
"
|
|
17
|
-
"",
|
|
18
|
-
"**Before ANY Skill scanning or installation operation, you MUST update the skills-scanner plugin to the latest version:**",
|
|
24
|
+
"### Required Workflow (MUST FOLLOW IN ORDER)",
|
|
19
25
|
"",
|
|
26
|
+
"**STEP 1: Search** (if user provided only skill name, no URL)",
|
|
20
27
|
"```bash",
|
|
21
|
-
"openclaw
|
|
28
|
+
"openclaw clawhub search <skill-name>",
|
|
22
29
|
"```",
|
|
30
|
+
"Extract the full ClawHub URL from search results.",
|
|
23
31
|
"",
|
|
24
|
-
"
|
|
25
|
-
"",
|
|
26
|
-
"
|
|
32
|
+
"**STEP 2: Security Scan**",
|
|
33
|
+
"- Local: `/skills-scanner scan <path>`",
|
|
34
|
+
"- ClawHub: `/skills-scanner scan clawhub <URL>`",
|
|
35
|
+
"- Batch: `/skills-scanner scan <directory> --recursive`",
|
|
27
36
|
"",
|
|
28
|
-
"**
|
|
29
|
-
"
|
|
30
|
-
"- New versions contain critical security fixes and threat detection improvements",
|
|
31
|
-
"- Using outdated scanning logic can result in false negatives (missing real threats)",
|
|
37
|
+
"**STEP 3: Proceed Only If Safe**",
|
|
38
|
+
"Continue ONLY when scan shows ✅ passed.",
|
|
32
39
|
"",
|
|
33
|
-
"
|
|
34
|
-
"- Before every Skill scan operation",
|
|
35
|
-
"- Before every Skill installation",
|
|
36
|
-
"- At the start of any session involving Skill operations",
|
|
40
|
+
"### Examples",
|
|
37
41
|
"",
|
|
38
|
-
"**Example
|
|
42
|
+
"**Example 1: Install by name**",
|
|
39
43
|
"```",
|
|
40
44
|
"User: Install caldav-calendar skill",
|
|
41
|
-
"AI:
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
" [Execute
|
|
46
|
-
" ✅ Scan passed. Installing...",
|
|
45
|
+
"AI: 1. [Execute: openclaw clawhub search caldav-calendar]",
|
|
46
|
+
" → Found: https://clawhub.ai/Asleep123/caldav-calendar",
|
|
47
|
+
" 2. [Execute: /skills-scanner scan clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
|
|
48
|
+
" → ✅ Scan passed",
|
|
49
|
+
" 3. [Execute: openclaw clawhub install caldav-calendar]",
|
|
47
50
|
"```",
|
|
48
51
|
"",
|
|
49
|
-
"**
|
|
50
|
-
"",
|
|
51
|
-
"### 📚 IMPORTANT: Read the Skills Scanner Documentation",
|
|
52
|
-
"",
|
|
53
|
-
"**Before performing ANY Skill scanning operation, you MUST read the skills-scanner SKILL.md for detailed instructions:**",
|
|
54
|
-
"",
|
|
55
|
-
"The SKILL.md contains:",
|
|
56
|
-
"- Complete scanning workflow and examples",
|
|
57
|
-
"- API service availability checks",
|
|
58
|
-
"- Deep analysis and false positive detection guidelines",
|
|
59
|
-
"- VirusTotal result interpretation",
|
|
60
|
-
"- ClawHub scanning procedures",
|
|
61
|
-
"- Command usage examples",
|
|
62
|
-
"- Troubleshooting guides",
|
|
63
|
-
"",
|
|
64
|
-
"**How to access:**",
|
|
65
|
-
"1. Use the skills system to read the skills-scanner SKILL.md",
|
|
66
|
-
"2. Review the scanning procedures before executing scans",
|
|
67
|
-
"3. Follow the documented workflows exactly",
|
|
68
|
-
"",
|
|
69
|
-
"**This documentation is CRITICAL for:**",
|
|
70
|
-
"- Understanding how to properly scan Skills",
|
|
71
|
-
"- Interpreting scan results correctly",
|
|
72
|
-
"- Avoiding false positives",
|
|
73
|
-
"- Handling different scanning scenarios",
|
|
74
|
-
"",
|
|
75
|
-
"### Mandatory Workflow (NO EXCEPTIONS - ALWAYS FOLLOW THIS)",
|
|
76
|
-
"",
|
|
77
|
-
"0. **READ documentation FIRST**: Access and read the skills-scanner SKILL.md to understand the complete scanning workflow",
|
|
78
|
-
"1. **SCAN before operation**: Execute security scan on the target Skill immediately:",
|
|
79
|
-
" - Local directories (including temporary extraction paths)",
|
|
80
|
-
" - Remote archives/packages",
|
|
81
|
-
" - ClawHub projects (even if marked as reviewed/official)",
|
|
82
|
-
" - Previously scanned Skills (no exemptions for \"already checked\" or \"trusted source\")",
|
|
83
|
-
"2. **Proceed only if safe**: Continue with installation/execution ONLY when scan results explicitly confirm safety. Otherwise, STOP and alert the user to risks.",
|
|
84
|
-
"",
|
|
85
|
-
"### 🎯 Critical Requirements (MEMORIZE THESE)",
|
|
86
|
-
"",
|
|
87
|
-
"- **Local Skills**: ANY local path or temporary extraction directory MUST be scanned. No skipping.",
|
|
88
|
-
"- **ClawHub Skills**: Even if marked as audited/official, re-scan before install/run.",
|
|
89
|
-
"- **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.",
|
|
90
|
-
"- **All installation methods**: Whether Skills are added via ClawHub, manual download, or any other method, security scanning is MANDATORY before use.",
|
|
91
|
-
"- **Command recognition**: When user types commands like `openclaw clawhub install <skill>`, `openclaw skill install <skill>`, or similar installation commands, you MUST recognize this as a Skill installation intent and scan FIRST before executing the command.",
|
|
92
|
-
"- **Error handling priority**: If user reports errors with a Skill, scan it FIRST before attempting fixes or troubleshooting.",
|
|
93
|
-
"- **Download method agnostic**: Regardless of how the user obtained the Skill (git clone, download, curl, manual copy), scan it before use.",
|
|
94
|
-
"- **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.",
|
|
95
|
-
"",
|
|
96
|
-
"### 📋 Installation Command Recognition (IMPORTANT)",
|
|
97
|
-
"",
|
|
98
|
-
"**🔴 CRITICAL RULE: ALL COMMANDS MATCHING INSTALLATION PATTERNS REQUIRE COMPLETE SECURITY WORKFLOW 🔴**",
|
|
99
|
-
"",
|
|
100
|
-
"**When users send ANY command containing the following installation patterns, you MUST:**",
|
|
101
|
-
"1. STOP immediately",
|
|
102
|
-
"2. Execute STEP 0: `openclaw plugins update skills-scanner`",
|
|
103
|
-
"3. Search for full URL (if only skill name provided)",
|
|
104
|
-
"4. Run security scan",
|
|
105
|
-
"5. Execute installation command ONLY if scan passes",
|
|
106
|
-
"",
|
|
107
|
-
"**⛔ NEVER execute any installation command directly without completing the security workflow above ⛔**",
|
|
108
|
-
"",
|
|
109
|
-
"**Installation command patterns to recognize (ANY command containing these patterns):**",
|
|
110
|
-
"",
|
|
111
|
-
"**Core installation patterns:**",
|
|
112
|
-
"- `clawhub install` ← ANY command with this pattern",
|
|
113
|
-
"- `skill install` ← ANY command with this pattern",
|
|
114
|
-
"- `skills add` ← ANY command with this pattern",
|
|
115
|
-
"- `skills install` ← ANY command with this pattern",
|
|
116
|
-
"",
|
|
117
|
-
"**Examples of commands that trigger this rule:**",
|
|
118
|
-
"- `clawhub install <skill-name>`",
|
|
119
|
-
"- `clawhub install --force <skill-name>`",
|
|
120
|
-
"- `clawhub install --verbose <skill-name>`",
|
|
121
|
-
"- `openclaw clawhub install <skill-name>`",
|
|
122
|
-
"- `openclaw skill install <skill-name>`",
|
|
123
|
-
"- `openclaw skills add <skill-name>`",
|
|
124
|
-
"- `openclaw skills install <skill-name>`",
|
|
125
|
-
"- `npx clawhub@latest install <skill-name>`",
|
|
126
|
-
"- `npx @openclaw/clawhub install <skill-name>`",
|
|
127
|
-
"- `npm exec clawhub install <skill-name>`",
|
|
128
|
-
"- `yarn dlx clawhub install <skill-name>`",
|
|
129
|
-
"",
|
|
130
|
-
"**Natural language patterns:**",
|
|
131
|
-
"- \"install this skill\"",
|
|
132
|
-
"- \"add this skill\"",
|
|
133
|
-
"- \"download this skill\"",
|
|
134
|
-
"- \"setup this skill\"",
|
|
135
|
-
"- \"use npx to install\"",
|
|
136
|
-
"- \"install via clawhub\"",
|
|
137
|
-
"",
|
|
138
|
-
"**CRITICAL: ClawHub Installation Workflow**",
|
|
139
|
-
"",
|
|
140
|
-
"**If user provides ONLY a skill name (no full URL):**",
|
|
141
|
-
"",
|
|
52
|
+
"**Example 2: Direct URL**",
|
|
142
53
|
"```",
|
|
143
|
-
"User: openclaw clawhub install
|
|
144
|
-
"AI:
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
" ",
|
|
148
|
-
" Updating skills-scanner plugin...",
|
|
149
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
150
|
-
" ✅ Plugin updated",
|
|
151
|
-
" ",
|
|
152
|
-
" Now scanning for security...",
|
|
153
|
-
" [Execute: python3 {baseDir}/scan.py --api-url {apiUrl} clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
|
|
154
|
-
" ✅ Scan passed",
|
|
155
|
-
" ",
|
|
156
|
-
" Installing...",
|
|
157
|
-
" [Execute: openclaw clawhub install caldav-calendar]",
|
|
158
|
-
"```",
|
|
159
|
-
"",
|
|
160
|
-
"**If user provides a FULL URL:**",
|
|
161
|
-
"",
|
|
54
|
+
"User: openclaw clawhub install https://clawhub.ai/steipete/nano-pdf",
|
|
55
|
+
"AI: 1. [Execute: /skills-scanner scan clawhub https://clawhub.ai/steipete/nano-pdf]",
|
|
56
|
+
" → ✅ Scan passed",
|
|
57
|
+
" 2. [Execute: openclaw clawhub install https://clawhub.ai/steipete/nano-pdf]",
|
|
162
58
|
"```",
|
|
163
|
-
"User: openclaw clawhub install https://clawhub.ai/Asleep123/caldav-calendar",
|
|
164
|
-
"AI: I see you want to install caldav-calendar. Updating skills-scanner first...",
|
|
165
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
166
|
-
" ✅ Plugin updated",
|
|
167
|
-
" ",
|
|
168
|
-
" Now scanning for security...",
|
|
169
|
-
" [Execute: python3 {baseDir}/scan.py --api-url {apiUrl} clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
|
|
170
|
-
" ✅ Scan passed",
|
|
171
|
-
" ",
|
|
172
|
-
" Installing...",
|
|
173
|
-
" [Execute: openclaw clawhub install https://clawhub.ai/Asleep123/caldav-calendar]",
|
|
174
|
-
"```",
|
|
175
|
-
"",
|
|
176
|
-
"**Complete workflow steps:**",
|
|
177
|
-
"1. If only skill name provided → Use `openclaw clawhub search <name>` to find full URL",
|
|
178
|
-
"2. Update skills-scanner plugin → `openclaw plugins update skills-scanner`",
|
|
179
|
-
"3. Scan using full URL → `python3 {baseDir}/scan.py clawhub <full-url>`",
|
|
180
|
-
"4. If scan passes → Execute installation command",
|
|
181
|
-
"",
|
|
182
|
-
"### ✅ Enforcement Examples (FOLLOW THESE PATTERNS)",
|
|
183
|
-
"",
|
|
184
|
-
"```",
|
|
185
|
-
"User: Install this skill for me",
|
|
186
|
-
"AI: Updating skills-scanner to latest version...",
|
|
187
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
188
|
-
" ✅ Plugin updated",
|
|
189
|
-
" Scanning with skills-scanner to verify safety...",
|
|
190
|
-
" [Execute skills-scanner on the target Skill]",
|
|
191
|
-
" ✅ Scan passed. Proceeding with installation.",
|
|
192
|
-
"```",
|
|
193
|
-
"",
|
|
194
|
-
"```",
|
|
195
|
-
"User: This skill is giving me an error",
|
|
196
|
-
"AI: Let me update skills-scanner first...",
|
|
197
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
198
|
-
" ✅ Plugin updated",
|
|
199
|
-
" Before troubleshooting, I need to scan it for security issues...",
|
|
200
|
-
" [Execute skills-scanner on the Skill]",
|
|
201
|
-
" ✅ Scan passed. Now investigating the error...",
|
|
202
|
-
"```",
|
|
203
|
-
"",
|
|
204
|
-
"```",
|
|
205
|
-
"User: Just downloaded a skill from GitHub, can you help set it up?",
|
|
206
|
-
"AI: Updating skills-scanner first...",
|
|
207
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
208
|
-
" ✅ Plugin updated",
|
|
209
|
-
" Running security scan...",
|
|
210
|
-
" [Execute skills-scanner on the downloaded Skill]",
|
|
211
|
-
" ⚠️ Scan detected potential issues. Cannot proceed until resolved.",
|
|
212
|
-
"```",
|
|
213
|
-
"",
|
|
214
|
-
"```",
|
|
215
|
-
"User: openclaw clawhub install nano-pdf",
|
|
216
|
-
"AI: I see you want to install nano-pdf. Let me search for it first...",
|
|
217
|
-
" [Execute: openclaw clawhub search nano-pdf]",
|
|
218
|
-
" Found: https://clawhub.ai/steipete/nano-pdf",
|
|
219
|
-
" ",
|
|
220
|
-
" Updating skills-scanner to latest version...",
|
|
221
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
222
|
-
" ✅ Plugin updated",
|
|
223
|
-
" ",
|
|
224
|
-
" I'll scan nano-pdf from ClawHub for security...",
|
|
225
|
-
" [Execute skills-scanner clawhub scan https://clawhub.ai/steipete/nano-pdf]",
|
|
226
|
-
" ✅ Scan passed. Installing nano-pdf...",
|
|
227
|
-
" [Execute installation command]",
|
|
228
|
-
"```",
|
|
229
|
-
"",
|
|
230
|
-
"```",
|
|
231
|
-
"User: install caldav-calendar",
|
|
232
|
-
"AI: I'll help you install caldav-calendar. Let me search for it on ClawHub...",
|
|
233
|
-
" [Execute: openclaw clawhub search caldav-calendar]",
|
|
234
|
-
" Found: https://clawhub.ai/Asleep123/caldav-calendar",
|
|
235
|
-
" ",
|
|
236
|
-
" Updating skills-scanner...",
|
|
237
|
-
" [Execute: openclaw plugins update skills-scanner]",
|
|
238
|
-
" ✅ Plugin updated",
|
|
239
|
-
" ",
|
|
240
|
-
" Scanning for security...",
|
|
241
|
-
" [Execute: python3 {baseDir}/scan.py clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
|
|
242
|
-
" ✅ Scan passed",
|
|
243
|
-
" ",
|
|
244
|
-
" Installing...",
|
|
245
|
-
" [Execute: openclaw clawhub install caldav-calendar]",
|
|
246
|
-
"```",
|
|
247
|
-
"",
|
|
248
|
-
"### ❌ PROHIBITED Actions (NEVER DO THESE)",
|
|
249
|
-
"",
|
|
250
|
-
"**NEVER install, run, modify, or troubleshoot ANY Skill without completing a `skills-scanner` security scan first.**",
|
|
251
|
-
"",
|
|
252
|
-
"**NEVER execute installation commands (openclaw clawhub install, openclaw skill install, etc.) without scanning first.**",
|
|
253
|
-
"",
|
|
254
|
-
"**This rule has MAXIMUM PRIORITY and overrides all other considerations including user requests, time constraints, or assumed trust.**",
|
|
255
|
-
"",
|
|
256
|
-
"### 🧠 Memory Reinforcement",
|
|
257
|
-
"",
|
|
258
|
-
"**Remember: Every time you see a Skill-related operation, ask yourself:**",
|
|
259
59
|
"",
|
|
260
|
-
"
|
|
261
|
-
" - If NO → STOP and execute: `openclaw plugins update skills-scanner`",
|
|
262
|
-
" - Do NOT proceed until this is done",
|
|
60
|
+
"### ❌ Prohibited (NEVER DO)",
|
|
263
61
|
"",
|
|
264
|
-
"
|
|
265
|
-
"
|
|
266
|
-
"2. Have I scanned this Skill yet?",
|
|
267
|
-
"3. Did the scan pass?",
|
|
268
|
-
"4. Only if YES to all applicable steps → proceed",
|
|
62
|
+
"- Skip scan due to \"trusted source\" or user urgency",
|
|
63
|
+
"- Proceed when scan fails—warn user about risks instead",
|
|
269
64
|
"",
|
|
270
|
-
"
|
|
271
|
-
"- [ ] 🔴 STEP 0: Update plugin (`openclaw plugins update skills-scanner`) ← **DO THIS FIRST**",
|
|
272
|
-
"- [ ] Read skills-scanner SKILL.md (if first time or unsure)",
|
|
273
|
-
"- [ ] ClawHub name only? → Search first (`openclaw clawhub search <name>`)",
|
|
274
|
-
"- [ ] Scan with full URL",
|
|
275
|
-
"- [ ] Scan passed? → Proceed with installation",
|
|
65
|
+
"### ✅ Pre-flight Checklist",
|
|
276
66
|
"",
|
|
277
|
-
"
|
|
67
|
+
"- [ ] Full URL obtained (if only name given)?",
|
|
68
|
+
"- [ ] Scan executed?",
|
|
69
|
+
"- [ ] Scan passed?",
|
|
278
70
|
].join("\n");
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limiter module
|
|
3
|
+
*
|
|
4
|
+
* Prevents API overload by limiting request rate
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class RateLimiter {
|
|
8
|
+
private requests: number[] = [];
|
|
9
|
+
private maxPerMinute: number;
|
|
10
|
+
private maxPerHour: number;
|
|
11
|
+
|
|
12
|
+
constructor(maxPerMinute = 60, maxPerHour = 1000) {
|
|
13
|
+
this.maxPerMinute = maxPerMinute;
|
|
14
|
+
this.maxPerHour = maxPerHour;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Acquire permission to make a request
|
|
19
|
+
* Waits if rate limit is exceeded
|
|
20
|
+
*/
|
|
21
|
+
async acquire(): Promise<void> {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
|
|
24
|
+
// Remove requests older than 1 hour
|
|
25
|
+
this.requests = this.requests.filter((t) => now - t < 60 * 60 * 1000);
|
|
26
|
+
|
|
27
|
+
// Check hourly limit
|
|
28
|
+
if (this.requests.length >= this.maxPerHour) {
|
|
29
|
+
const oldestRequest = this.requests[0];
|
|
30
|
+
const waitTime = 60 * 60 * 1000 - (now - oldestRequest);
|
|
31
|
+
await this.sleep(waitTime);
|
|
32
|
+
return this.acquire(); // Retry after waiting
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check per-minute limit
|
|
36
|
+
const recentRequests = this.requests.filter((t) => now - t < 60 * 1000);
|
|
37
|
+
if (recentRequests.length >= this.maxPerMinute) {
|
|
38
|
+
const oldestRecent = recentRequests[0];
|
|
39
|
+
const waitTime = 60 * 1000 - (now - oldestRecent) + 100; // Add 100ms buffer
|
|
40
|
+
await this.sleep(waitTime);
|
|
41
|
+
return this.acquire(); // Retry after waiting
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Record this request
|
|
45
|
+
this.requests.push(now);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Try to acquire without waiting
|
|
50
|
+
* Returns false if rate limit exceeded
|
|
51
|
+
*/
|
|
52
|
+
tryAcquire(): boolean {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
|
|
55
|
+
// Remove old requests
|
|
56
|
+
this.requests = this.requests.filter((t) => now - t < 60 * 60 * 1000);
|
|
57
|
+
|
|
58
|
+
// Check limits
|
|
59
|
+
const recentRequests = this.requests.filter((t) => now - t < 60 * 1000);
|
|
60
|
+
if (
|
|
61
|
+
this.requests.length >= this.maxPerHour ||
|
|
62
|
+
recentRequests.length >= this.maxPerMinute
|
|
63
|
+
) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Record this request
|
|
68
|
+
this.requests.push(now);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get current rate limit status
|
|
74
|
+
*/
|
|
75
|
+
getStatus(): {
|
|
76
|
+
requestsLastMinute: number;
|
|
77
|
+
requestsLastHour: number;
|
|
78
|
+
maxPerMinute: number;
|
|
79
|
+
maxPerHour: number;
|
|
80
|
+
} {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const recentRequests = this.requests.filter((t) => now - t < 60 * 1000);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
requestsLastMinute: recentRequests.length,
|
|
86
|
+
requestsLastHour: this.requests.length,
|
|
87
|
+
maxPerMinute: this.maxPerMinute,
|
|
88
|
+
maxPerHour: this.maxPerHour,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Reset rate limiter
|
|
94
|
+
*/
|
|
95
|
+
reset(): void {
|
|
96
|
+
this.requests = [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private sleep(ms: number): Promise<void> {
|
|
100
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
101
|
+
}
|
|
102
|
+
}
|