@poolzin/pool-bot 2026.3.7 → 2026.3.10

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.
Files changed (150) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +147 -69
  3. package/dist/.buildstamp +1 -1
  4. package/dist/agents/error-classifier.js +251 -0
  5. package/dist/agents/skills/security.js +211 -0
  6. package/dist/build-info.json +3 -3
  7. package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
  8. package/dist/cli/cron-cli/register.js +2 -0
  9. package/dist/cli/errors.js +187 -0
  10. package/dist/cli/lazy-commands.example.js +113 -0
  11. package/dist/cli/lazy-commands.js +329 -0
  12. package/dist/cli/program/command-registry.js +26 -0
  13. package/dist/cli/program/register.maintenance.js +21 -0
  14. package/dist/cli/program/register.skills.js +4 -0
  15. package/dist/cli/program/register.subclis.js +9 -0
  16. package/dist/cli/swarm-cli/register.js +8 -0
  17. package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
  18. package/dist/cli/telemetry-cli/register.js +10 -0
  19. package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
  20. package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
  21. package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
  22. package/dist/commands/doctor-checks.js +498 -0
  23. package/dist/config/config.js +1 -0
  24. package/dist/config/secrets-integration.js +88 -0
  25. package/dist/context-engine/index.js +33 -0
  26. package/dist/context-engine/legacy.js +179 -0
  27. package/dist/context-engine/registry.js +86 -0
  28. package/dist/context-engine/summarizing.js +290 -0
  29. package/dist/context-engine/types.js +7 -0
  30. package/dist/cron/service/timer.js +18 -0
  31. package/dist/gateway/protocol/index.js +5 -2
  32. package/dist/gateway/protocol/schema/error-codes.js +1 -0
  33. package/dist/gateway/protocol/schema/swarm.js +80 -0
  34. package/dist/gateway/protocol/schema.js +1 -0
  35. package/dist/gateway/server-close.js +4 -0
  36. package/dist/gateway/server-constants.js +1 -0
  37. package/dist/gateway/server-cron.js +29 -0
  38. package/dist/gateway/server-maintenance.js +35 -2
  39. package/dist/gateway/server-methods/swarm.js +58 -0
  40. package/dist/gateway/server-methods/telemetry.js +71 -0
  41. package/dist/gateway/server-methods-list.js +8 -0
  42. package/dist/gateway/server-methods.js +9 -2
  43. package/dist/gateway/server.impl.js +33 -16
  44. package/dist/infra/abort-pattern.js +106 -0
  45. package/dist/infra/retry.js +96 -0
  46. package/dist/secrets/index.js +28 -0
  47. package/dist/secrets/resolver.js +185 -0
  48. package/dist/secrets/runtime.js +142 -0
  49. package/dist/secrets/types.js +11 -0
  50. package/dist/security/dangerous-tools.js +80 -0
  51. package/dist/security/types.js +12 -0
  52. package/dist/skills/commands.js +333 -0
  53. package/dist/skills/index.js +164 -0
  54. package/dist/skills/loader.js +282 -0
  55. package/dist/skills/parser.js +446 -0
  56. package/dist/skills/registry.js +394 -0
  57. package/dist/skills/security.js +312 -0
  58. package/dist/skills/types.js +21 -0
  59. package/dist/swarm/service.js +247 -0
  60. package/dist/telemetry/alert-engine.js +258 -0
  61. package/dist/telemetry/cron-instrumentation.js +49 -0
  62. package/dist/telemetry/gateway-instrumentation.js +80 -0
  63. package/dist/telemetry/instrumentation.js +66 -0
  64. package/dist/telemetry/service.js +345 -0
  65. package/dist/test-utils/index.js +219 -0
  66. package/dist/tui/components/assistant-message.js +6 -2
  67. package/dist/tui/components/hyperlink-markdown.js +32 -0
  68. package/dist/tui/components/searchable-select-list.js +12 -1
  69. package/dist/tui/components/user-message.js +6 -2
  70. package/dist/tui/index.js +611 -0
  71. package/dist/tui/theme/theme-detection.js +226 -0
  72. package/dist/tui/tui-command-handlers.js +20 -0
  73. package/dist/tui/tui-formatters.js +4 -3
  74. package/dist/tui/utils/ctrl-c-handler.js +67 -0
  75. package/dist/tui/utils/osc8-hyperlinks.js +208 -0
  76. package/dist/tui/utils/safe-stop.js +180 -0
  77. package/dist/tui/utils/session-key-utils.js +81 -0
  78. package/dist/tui/utils/text-sanitization.js +284 -0
  79. package/dist/utils/lru-cache.js +116 -0
  80. package/dist/utils/performance.js +199 -0
  81. package/dist/utils/retry.js +240 -0
  82. package/docs/INTEGRATION_PLAN.md +475 -0
  83. package/docs/INTEGRATION_SUMMARY.md +215 -0
  84. package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
  85. package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
  86. package/docs/PLANO_ACAO_TUI.md +357 -0
  87. package/docs/PROGRESSO_TUI.md +66 -0
  88. package/docs/RELATORIO_FINAL.md +217 -0
  89. package/docs/diagnostico-shell-completion.md +265 -0
  90. package/docs/features/advanced-memory.md +585 -0
  91. package/docs/features/discord-components-v2.md +277 -0
  92. package/docs/features/swarm.md +100 -0
  93. package/docs/features/telemetry.md +284 -0
  94. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  95. package/docs/integrations/INTEGRATION_PLAN.md +744 -0
  96. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  97. package/docs/integrations/XYOPS_PLAN.md +978 -0
  98. package/docs/models/provider-infrastructure.md +400 -0
  99. package/docs/security/exec-approvals.md +294 -0
  100. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  101. package/docs/skills/SKILL.md +524 -0
  102. package/docs/skills.md +405 -0
  103. package/extensions/bluebubbles/package.json +1 -1
  104. package/extensions/copilot-proxy/package.json +1 -1
  105. package/extensions/diagnostics-otel/package.json +1 -1
  106. package/extensions/discord/package.json +1 -1
  107. package/extensions/feishu/package.json +1 -1
  108. package/extensions/google-antigravity-auth/package.json +1 -1
  109. package/extensions/google-gemini-cli-auth/package.json +1 -1
  110. package/extensions/googlechat/package.json +1 -1
  111. package/extensions/hexstrike-bridge/README.md +119 -0
  112. package/extensions/hexstrike-bridge/index.test.ts +247 -0
  113. package/extensions/hexstrike-bridge/index.ts +487 -0
  114. package/extensions/hexstrike-bridge/package.json +17 -0
  115. package/extensions/imessage/package.json +1 -1
  116. package/extensions/irc/package.json +1 -1
  117. package/extensions/line/package.json +1 -1
  118. package/extensions/llm-task/package.json +1 -1
  119. package/extensions/lobster/package.json +1 -1
  120. package/extensions/matrix/CHANGELOG.md +5 -0
  121. package/extensions/matrix/package.json +1 -1
  122. package/extensions/mattermost/package.json +1 -1
  123. package/extensions/mcp-server/index.ts +14 -0
  124. package/extensions/mcp-server/package.json +11 -0
  125. package/extensions/mcp-server/src/service.ts +540 -0
  126. package/extensions/memory-core/package.json +1 -1
  127. package/extensions/memory-lancedb/package.json +1 -1
  128. package/extensions/minimax-portal-auth/package.json +1 -1
  129. package/extensions/msteams/CHANGELOG.md +5 -0
  130. package/extensions/msteams/package.json +1 -1
  131. package/extensions/nextcloud-talk/package.json +1 -1
  132. package/extensions/nostr/CHANGELOG.md +5 -0
  133. package/extensions/nostr/package.json +1 -1
  134. package/extensions/open-prose/package.json +1 -1
  135. package/extensions/openai-codex-auth/package.json +1 -1
  136. package/extensions/signal/package.json +1 -1
  137. package/extensions/slack/package.json +1 -1
  138. package/extensions/telegram/package.json +1 -1
  139. package/extensions/tlon/package.json +1 -1
  140. package/extensions/twitch/CHANGELOG.md +5 -0
  141. package/extensions/twitch/package.json +1 -1
  142. package/extensions/voice-call/CHANGELOG.md +5 -0
  143. package/extensions/voice-call/package.json +1 -1
  144. package/extensions/whatsapp/package.json +1 -1
  145. package/extensions/zalo/CHANGELOG.md +5 -0
  146. package/extensions/zalo/package.json +1 -1
  147. package/extensions/zalouser/CHANGELOG.md +5 -0
  148. package/extensions/zalouser/package.json +1 -1
  149. package/package.json +8 -1
  150. package/skills/example-skill/SKILL.md +195 -0
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Skills registry
3
+ * Manages skill discovery, storage, and lifecycle
4
+ *
5
+ * @module skills/registry
6
+ */
7
+ import { readdir } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { EventEmitter } from "node:events";
10
+ import { SkillError, } from "./types.js";
11
+ import { parseSkill, isSkillFile } from "./parser.js";
12
+ import { scanSkill } from "./security.js";
13
+ // ============================================================================
14
+ // Default Configuration
15
+ // ============================================================================
16
+ const DEFAULT_CONFIG = {
17
+ localPaths: [],
18
+ autoScan: true,
19
+ strictSecurity: false,
20
+ cacheTtlMs: 5 * 60 * 1000, // 5 minutes
21
+ };
22
+ // ============================================================================
23
+ // Skills Registry
24
+ // ============================================================================
25
+ export class SkillsRegistry extends EventEmitter {
26
+ skills = new Map();
27
+ config;
28
+ scanCache = new Map();
29
+ constructor(config = {}) {
30
+ super();
31
+ this.config = { ...DEFAULT_CONFIG, ...config };
32
+ }
33
+ // ========================================================================
34
+ // Event Handling
35
+ // ========================================================================
36
+ emitEvent(type, skillId, data) {
37
+ const event = {
38
+ type,
39
+ skillId,
40
+ timestamp: new Date(),
41
+ data,
42
+ };
43
+ this.emit(type, event);
44
+ }
45
+ onSkillEvent(type, handler) {
46
+ this.on(type, handler);
47
+ }
48
+ // ========================================================================
49
+ // Skill Discovery
50
+ // ========================================================================
51
+ /**
52
+ * Scan all configured paths for skills
53
+ */
54
+ async scan() {
55
+ const errors = [];
56
+ let loaded = 0;
57
+ for (const path of this.config.localPaths) {
58
+ try {
59
+ const result = await this.scanPath(path);
60
+ loaded += result.loaded;
61
+ errors.push(...result.errors);
62
+ }
63
+ catch (error) {
64
+ errors.push(error);
65
+ }
66
+ }
67
+ return { loaded, errors };
68
+ }
69
+ /**
70
+ * Scan a single path for skills
71
+ */
72
+ async scanPath(basePath) {
73
+ const errors = [];
74
+ let loaded = 0;
75
+ try {
76
+ const entries = await readdir(basePath, { withFileTypes: true });
77
+ for (const entry of entries) {
78
+ if (!entry.isDirectory())
79
+ continue;
80
+ const skillPath = join(basePath.toString(), entry.name, "SKILL.md");
81
+ try {
82
+ if (await isSkillFile(skillPath)) {
83
+ const skill = await parseSkill(skillPath);
84
+ // Run security scan if auto-scan enabled
85
+ if (this.config.autoScan) {
86
+ skill.securityReport = await scanSkill(skill);
87
+ skill.verification = skill.securityReport.result;
88
+ // Auto-disable if strict security and failed
89
+ if (this.config.strictSecurity && skill.verification === "failed") {
90
+ skill.enabled = false;
91
+ skill.status = "disabled";
92
+ }
93
+ }
94
+ this.skills.set(skill.metadata.id, skill);
95
+ this.emitEvent("skill:loaded", skill.metadata.id);
96
+ loaded++;
97
+ }
98
+ }
99
+ catch (error) {
100
+ errors.push(new SkillError("LOAD_ERROR", `Failed to load skill from ${skillPath}: ${error}`, entry.name, error));
101
+ }
102
+ }
103
+ }
104
+ catch (error) {
105
+ errors.push(error);
106
+ }
107
+ return { loaded, errors };
108
+ }
109
+ /**
110
+ * Watch for skill changes (placeholder for future implementation)
111
+ */
112
+ async watch() {
113
+ // TODO: Implement file watching for hot-reload
114
+ // This would use fs.watch or chokidar
115
+ throw new SkillError("CONFIG_ERROR", "Watch mode not yet implemented");
116
+ }
117
+ // ========================================================================
118
+ // Skill CRUD
119
+ // ========================================================================
120
+ /**
121
+ * Get a skill by ID
122
+ */
123
+ get(id) {
124
+ return this.skills.get(id);
125
+ }
126
+ /**
127
+ * Get all skills
128
+ */
129
+ getAll() {
130
+ return Array.from(this.skills.values());
131
+ }
132
+ /**
133
+ * Get skill reference (lightweight)
134
+ */
135
+ getRef(id) {
136
+ const skill = this.skills.get(id);
137
+ if (!skill)
138
+ return undefined;
139
+ return {
140
+ id: skill.metadata.id,
141
+ name: skill.metadata.name,
142
+ description: skill.metadata.description,
143
+ category: skill.metadata.category,
144
+ tags: skill.metadata.tags,
145
+ version: skill.metadata.version,
146
+ status: skill.status,
147
+ verification: skill.verification,
148
+ enabled: skill.enabled,
149
+ };
150
+ }
151
+ /**
152
+ * Get all skill references
153
+ */
154
+ getAllRefs() {
155
+ return this.getAll().map((skill) => ({
156
+ id: skill.metadata.id,
157
+ name: skill.metadata.name,
158
+ description: skill.metadata.description,
159
+ category: skill.metadata.category,
160
+ tags: skill.metadata.tags,
161
+ version: skill.metadata.version,
162
+ status: skill.status,
163
+ verification: skill.verification,
164
+ enabled: skill.enabled,
165
+ }));
166
+ }
167
+ /**
168
+ * Add or update a skill
169
+ */
170
+ async add(skill) {
171
+ const exists = this.skills.has(skill.metadata.id);
172
+ this.skills.set(skill.metadata.id, skill);
173
+ this.emitEvent(exists ? "skill:updated" : "skill:loaded", skill.metadata.id);
174
+ }
175
+ /**
176
+ * Remove a skill
177
+ */
178
+ remove(id) {
179
+ const existed = this.skills.delete(id);
180
+ if (existed) {
181
+ this.emitEvent("skill:unloaded", id);
182
+ }
183
+ return existed;
184
+ }
185
+ /**
186
+ * Check if skill exists
187
+ */
188
+ has(id) {
189
+ return this.skills.has(id);
190
+ }
191
+ // ========================================================================
192
+ // Skill State Management
193
+ // ========================================================================
194
+ /**
195
+ * Enable a skill
196
+ */
197
+ enable(id) {
198
+ const skill = this.skills.get(id);
199
+ if (!skill)
200
+ return false;
201
+ if (skill.verification === "failed" && this.config.strictSecurity) {
202
+ throw new SkillError("SECURITY_VIOLATION", `Cannot enable skill ${id}: failed security scan`, id);
203
+ }
204
+ skill.enabled = true;
205
+ skill.status = "installed";
206
+ this.emitEvent("skill:enabled", id);
207
+ return true;
208
+ }
209
+ /**
210
+ * Disable a skill
211
+ */
212
+ disable(id) {
213
+ const skill = this.skills.get(id);
214
+ if (!skill)
215
+ return false;
216
+ skill.enabled = false;
217
+ skill.status = "disabled";
218
+ this.emitEvent("skill:disabled", id);
219
+ return true;
220
+ }
221
+ /**
222
+ * Toggle skill enabled state
223
+ */
224
+ toggle(id) {
225
+ const skill = this.skills.get(id);
226
+ if (!skill)
227
+ return false;
228
+ return skill.enabled ? this.disable(id) : this.enable(id);
229
+ }
230
+ // ========================================================================
231
+ // Search
232
+ // ========================================================================
233
+ /**
234
+ * Search skills with filters
235
+ */
236
+ search(filters = {}, page = 1, pageSize = 20) {
237
+ let results = this.getAll();
238
+ // Apply filters
239
+ if (filters.category) {
240
+ results = results.filter((s) => s.metadata.category === filters.category);
241
+ }
242
+ if (filters.tags?.length) {
243
+ results = results.filter((s) => filters.tags.some((tag) => s.metadata.tags.includes(tag)));
244
+ }
245
+ if (filters.status) {
246
+ results = results.filter((s) => s.status === filters.status);
247
+ }
248
+ if (filters.verification) {
249
+ results = results.filter((s) => s.verification === filters.verification);
250
+ }
251
+ if (filters.enabledOnly) {
252
+ results = results.filter((s) => s.enabled);
253
+ }
254
+ if (filters.query) {
255
+ const query = filters.query.toLowerCase();
256
+ results = results.filter((s) => s.metadata.name.toLowerCase().includes(query) ||
257
+ s.metadata.description.toLowerCase().includes(query) ||
258
+ s.metadata.tags.some((t) => t.toLowerCase().includes(query)));
259
+ }
260
+ // Pagination
261
+ const total = results.length;
262
+ const start = (page - 1) * pageSize;
263
+ const paginated = results.slice(start, start + pageSize);
264
+ return {
265
+ skills: paginated.map((s) => ({
266
+ id: s.metadata.id,
267
+ name: s.metadata.name,
268
+ description: s.metadata.description,
269
+ category: s.metadata.category,
270
+ tags: s.metadata.tags,
271
+ version: s.metadata.version,
272
+ status: s.status,
273
+ verification: s.verification,
274
+ enabled: s.enabled,
275
+ })),
276
+ total,
277
+ page,
278
+ pageSize,
279
+ };
280
+ }
281
+ /**
282
+ * Find skills by tag
283
+ */
284
+ findByTag(tag) {
285
+ return this.getAllRefs().filter((s) => s.tags.includes(tag));
286
+ }
287
+ /**
288
+ * Find skills by category
289
+ */
290
+ findByCategory(category) {
291
+ return this.getAllRefs().filter((s) => s.category === category);
292
+ }
293
+ // ========================================================================
294
+ // Statistics
295
+ // ========================================================================
296
+ /**
297
+ * Get registry statistics
298
+ */
299
+ getStats() {
300
+ const skills = this.getAll();
301
+ const byCategory = {};
302
+ const byVerification = {};
303
+ for (const skill of skills) {
304
+ // Count by category
305
+ byCategory[skill.metadata.category] = (byCategory[skill.metadata.category] || 0) + 1;
306
+ // Count by verification
307
+ byVerification[skill.verification] = (byVerification[skill.verification] || 0) + 1;
308
+ }
309
+ return {
310
+ total: skills.length,
311
+ enabled: skills.filter((s) => s.enabled).length,
312
+ disabled: skills.filter((s) => !s.enabled).length,
313
+ byCategory,
314
+ byVerification,
315
+ };
316
+ }
317
+ // ========================================================================
318
+ // Import/Export
319
+ // ========================================================================
320
+ /**
321
+ * Export all skills to JSON
322
+ */
323
+ export() {
324
+ const data = this.getAll().map((skill) => ({
325
+ metadata: skill.metadata,
326
+ status: skill.status,
327
+ verification: skill.verification,
328
+ enabled: skill.enabled,
329
+ userConfig: skill.userConfig,
330
+ }));
331
+ return JSON.stringify(data, null, 2);
332
+ }
333
+ /**
334
+ * Import skills from JSON
335
+ */
336
+ async import(json) {
337
+ const data = JSON.parse(json);
338
+ for (const partial of data) {
339
+ if (!partial.metadata?.id)
340
+ continue;
341
+ // Try to reload from source
342
+ const existing = this.skills.get(partial.metadata.id);
343
+ if (existing) {
344
+ // Update state only
345
+ if (partial.enabled !== undefined) {
346
+ existing.enabled = partial.enabled;
347
+ }
348
+ if (partial.userConfig) {
349
+ existing.userConfig = partial.userConfig;
350
+ }
351
+ }
352
+ }
353
+ }
354
+ // ========================================================================
355
+ // Cleanup
356
+ // ========================================================================
357
+ /**
358
+ * Clear all skills
359
+ */
360
+ clear() {
361
+ for (const id of this.skills.keys()) {
362
+ this.emitEvent("skill:unloaded", id);
363
+ }
364
+ this.skills.clear();
365
+ this.scanCache.clear();
366
+ }
367
+ /**
368
+ * Dispose of registry resources
369
+ */
370
+ dispose() {
371
+ this.clear();
372
+ this.removeAllListeners();
373
+ }
374
+ }
375
+ // ============================================================================
376
+ // Singleton Instance
377
+ // ============================================================================
378
+ let globalRegistry = null;
379
+ /**
380
+ * Get or create global registry instance
381
+ */
382
+ export function getRegistry(config) {
383
+ if (!globalRegistry) {
384
+ globalRegistry = new SkillsRegistry(config);
385
+ }
386
+ return globalRegistry;
387
+ }
388
+ /**
389
+ * Reset global registry (useful for testing)
390
+ */
391
+ export function resetRegistry() {
392
+ globalRegistry?.dispose();
393
+ globalRegistry = null;
394
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Skill security scanner
3
+ * Detects potential security issues in skill files
4
+ *
5
+ * @module skills/security
6
+ */
7
+ // ============================================================================
8
+ // Constants
9
+ // ============================================================================
10
+ const SCANNER_VERSION = "1.0.0";
11
+ // Patterns that indicate potential security issues
12
+ const PATTERNS = [
13
+ // Prompt injection attempts
14
+ {
15
+ type: "prompt_injection",
16
+ severity: "critical",
17
+ pattern: /ignore\s+(?:previous|above|prior)|disregard\s+(?:instructions?|prompt)|system\s*:\s*you\s+are|new\s+instructions?\s*:/i,
18
+ description: "Potential prompt injection attempt detected",
19
+ remediation: "Review skill content for malicious instruction overrides",
20
+ },
21
+ {
22
+ type: "prompt_injection",
23
+ severity: "high",
24
+ pattern: /\[\s*system\s*\]|\(\s*system\s*\)|\{\s*system\s*\}|\bDAN\b|do\s+anything\s+now/i,
25
+ description: "Suspicious system role reference",
26
+ remediation: "Verify skill doesn't attempt to override system behavior",
27
+ },
28
+ // Command injection
29
+ {
30
+ type: "command_injection",
31
+ severity: "critical",
32
+ pattern: /(?:bash|sh|zsh|cmd|powershell)\s+-c\s+["']|exec\s*\(|eval\s*\(|system\s*\(/i,
33
+ description: "Potential command injection pattern",
34
+ remediation: "Avoid executing arbitrary shell commands from skill content",
35
+ },
36
+ {
37
+ type: "command_injection",
38
+ severity: "high",
39
+ pattern: /`[^`]*(?:rm|del|format|mkfs|dd|wget|curl|fetch)[^`]*`|\$\([^)]*(?:rm|del|wget|curl)[^)]*\)/i,
40
+ description: "Dangerous command in template literal",
41
+ remediation: "Review shell command usage for safety",
42
+ },
43
+ // Path traversal
44
+ {
45
+ type: "path_traversal",
46
+ severity: "high",
47
+ pattern: /\.\.[/\\]|\.\.%2f|\.\.%5c|%2e%2e[/\\]/i,
48
+ description: "Path traversal attempt detected",
49
+ remediation: "Validate and sanitize all file paths",
50
+ },
51
+ // Suspicious patterns
52
+ {
53
+ type: "suspicious_pattern",
54
+ severity: "medium",
55
+ pattern: /(?:password|secret|token|key|credential)\s*=\s*["'][^"']{8,}["']/i,
56
+ description: "Hardcoded credential-like pattern",
57
+ remediation: "Use environment variables or secure secret storage",
58
+ },
59
+ {
60
+ type: "suspicious_pattern",
61
+ severity: "medium",
62
+ pattern: /base64\s*\(\s*["'][^"']{20,}["']\s*\)|atob\s*\(|btoa\s*\(/i,
63
+ description: "Suspicious encoding/decoding pattern",
64
+ remediation: "Verify encoding is not used to obfuscate malicious content",
65
+ },
66
+ // External dependencies
67
+ {
68
+ type: "external_dependency",
69
+ severity: "low",
70
+ pattern: /(?:npm|pip|gem|cargo|go\s+get)\s+install/i,
71
+ description: "External package installation mentioned",
72
+ remediation: "Verify all external dependencies are trustworthy",
73
+ },
74
+ // Permission requests
75
+ {
76
+ type: "permission_request",
77
+ severity: "medium",
78
+ pattern: /(?:sudo|administrator|root|elevated|privileged)\s*(?:access|permission|required|needed)/i,
79
+ description: "Elevated permissions mentioned",
80
+ remediation: "Ensure elevated permissions are truly necessary",
81
+ },
82
+ // Network access
83
+ {
84
+ type: "network_access",
85
+ severity: "low",
86
+ pattern: /(?:http|https|ftp|ssh|telnet):\/\/|(?:fetch|axios|request|curl)\s*\(/i,
87
+ description: "Network access pattern detected",
88
+ remediation: "Verify all network requests are legitimate",
89
+ },
90
+ // File system access
91
+ {
92
+ type: "file_system_access",
93
+ severity: "low",
94
+ pattern: /(?:fs\.|file|readFile|writeFile|appendFile|unlink|mkdir|rmdir)\s*\(/i,
95
+ description: "File system access pattern detected",
96
+ remediation: "Validate file operations are scoped appropriately",
97
+ },
98
+ ];
99
+ // Content limits
100
+ const MAX_CONTENT_LENGTH = 1024 * 1024; // 1MB
101
+ const MAX_LINE_LENGTH = 1000;
102
+ // ============================================================================
103
+ // Scanner Implementation
104
+ // ============================================================================
105
+ /**
106
+ * Scan skill content for security issues
107
+ */
108
+ export async function scanSkill(skill) {
109
+ const startTime = Date.now();
110
+ const findings = [];
111
+ // Combine all content for scanning
112
+ const contentToScan = [
113
+ skill.metadata.name,
114
+ skill.metadata.description,
115
+ skill.content.usage,
116
+ skill.content.quickstart,
117
+ skill.content.configuration,
118
+ skill.content.environment,
119
+ skill.content.examples,
120
+ skill.content.api,
121
+ skill.content.troubleshooting,
122
+ ]
123
+ .filter(Boolean)
124
+ .join("\n\n");
125
+ // Check content size
126
+ if (contentToScan.length > MAX_CONTENT_LENGTH) {
127
+ findings.push({
128
+ type: "suspicious_pattern",
129
+ severity: "medium",
130
+ description: `Skill content exceeds maximum size (${contentToScan.length} > ${MAX_CONTENT_LENGTH})`,
131
+ location: "content",
132
+ remediation: "Consider splitting large skills into smaller modules",
133
+ });
134
+ }
135
+ // Scan line by line for better location reporting
136
+ const lines = contentToScan.split("\n");
137
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
138
+ const line = lines[lineNum];
139
+ // Check line length
140
+ if (line.length > MAX_LINE_LENGTH) {
141
+ findings.push({
142
+ type: "suspicious_pattern",
143
+ severity: "low",
144
+ description: `Line ${lineNum + 1} exceeds maximum length`,
145
+ location: `line:${lineNum + 1}`,
146
+ remediation: "Break long lines for readability and safety",
147
+ });
148
+ }
149
+ // Check against patterns
150
+ for (const { type, severity, pattern, description, remediation } of PATTERNS) {
151
+ if (pattern.test(line)) {
152
+ findings.push({
153
+ type,
154
+ severity,
155
+ description,
156
+ location: `line:${lineNum + 1}`,
157
+ remediation,
158
+ evidence: line.trim().slice(0, 100), // First 100 chars
159
+ });
160
+ }
161
+ }
162
+ }
163
+ // Check linked files
164
+ for (const linkedFile of skill.linkedFiles) {
165
+ // Flag executable files
166
+ if (/\.(exe|bat|cmd|sh|bin|app)$/i.test(linkedFile.path)) {
167
+ findings.push({
168
+ type: "suspicious_pattern",
169
+ severity: "high",
170
+ description: `Linked executable file detected: ${linkedFile.path}`,
171
+ location: `linked:${linkedFile.path}`,
172
+ remediation: "Review executable content before execution",
173
+ });
174
+ }
175
+ // Flag scripts
176
+ if (/\.(js|ts|py|rb|pl|php)$/i.test(linkedFile.path)) {
177
+ findings.push({
178
+ type: "external_dependency",
179
+ severity: "low",
180
+ description: `Linked script file: ${linkedFile.path}`,
181
+ location: `linked:${linkedFile.path}`,
182
+ remediation: "Review script content for safety",
183
+ });
184
+ }
185
+ }
186
+ // Determine overall result
187
+ const hasCritical = findings.some((f) => f.severity === "critical");
188
+ const hasHigh = findings.some((f) => f.severity === "high");
189
+ const hasWarnings = findings.some((f) => f.severity === "medium" || f.severity === "low");
190
+ let result;
191
+ if (hasCritical) {
192
+ result = "failed";
193
+ }
194
+ else if (hasHigh) {
195
+ result = "failed";
196
+ }
197
+ else if (hasWarnings) {
198
+ result = "warning";
199
+ }
200
+ else {
201
+ result = "verified";
202
+ }
203
+ const durationMs = Date.now() - startTime;
204
+ return {
205
+ scannedAt: new Date(),
206
+ result,
207
+ findings,
208
+ durationMs,
209
+ scannerVersion: SCANNER_VERSION,
210
+ };
211
+ }
212
+ /**
213
+ * Quick scan for critical issues only
214
+ */
215
+ export async function quickSecurityCheck(skill) {
216
+ const criticalPatterns = PATTERNS.filter((p) => p.severity === "critical" || p.severity === "high");
217
+ const contentToScan = [
218
+ skill.metadata.name,
219
+ skill.metadata.description,
220
+ skill.content.usage,
221
+ skill.content.quickstart,
222
+ ]
223
+ .filter(Boolean)
224
+ .join("\n");
225
+ for (const { pattern } of criticalPatterns) {
226
+ if (pattern.test(contentToScan)) {
227
+ return false;
228
+ }
229
+ }
230
+ return true;
231
+ }
232
+ /**
233
+ * Get security summary for display
234
+ */
235
+ export function getSecuritySummary(report) {
236
+ const counts = {
237
+ critical: 0,
238
+ high: 0,
239
+ medium: 0,
240
+ low: 0,
241
+ info: 0,
242
+ };
243
+ for (const finding of report.findings) {
244
+ counts[finding.severity]++;
245
+ }
246
+ let status;
247
+ let color;
248
+ let summary;
249
+ switch (report.result) {
250
+ case "verified":
251
+ status = "✓ Verified";
252
+ color = "green";
253
+ summary = "No security issues found";
254
+ break;
255
+ case "warning":
256
+ status = "⚠ Warning";
257
+ color = "yellow";
258
+ summary = `${counts.medium + counts.low} warning(s) found`;
259
+ break;
260
+ case "failed":
261
+ status = "✗ Failed";
262
+ color = "red";
263
+ summary = `${counts.critical + counts.high} critical issue(s) found`;
264
+ break;
265
+ default:
266
+ status = "? Unverified";
267
+ color = "gray";
268
+ summary = "Not yet scanned";
269
+ }
270
+ return { status, color, summary, counts };
271
+ }
272
+ /**
273
+ * Format security findings for display
274
+ */
275
+ export function formatFindings(findings) {
276
+ if (findings.length === 0) {
277
+ return ["No security issues found."];
278
+ }
279
+ const lines = [];
280
+ // Group by severity
281
+ const bySeverity = {
282
+ critical: [],
283
+ high: [],
284
+ medium: [],
285
+ low: [],
286
+ info: [],
287
+ };
288
+ for (const finding of findings) {
289
+ bySeverity[finding.severity].push(finding);
290
+ }
291
+ for (const severity of ["critical", "high", "medium", "low", "info"]) {
292
+ const group = bySeverity[severity];
293
+ if (group.length === 0)
294
+ continue;
295
+ lines.push(`\n${severity.toUpperCase()} (${group.length}):`);
296
+ for (const finding of group) {
297
+ lines.push(` [${finding.type}] ${finding.description}`);
298
+ lines.push(` Location: ${finding.location}`);
299
+ if (finding.evidence) {
300
+ lines.push(` Evidence: ${finding.evidence}`);
301
+ }
302
+ if (finding.remediation) {
303
+ lines.push(` Fix: ${finding.remediation}`);
304
+ }
305
+ }
306
+ }
307
+ return lines;
308
+ }
309
+ // ============================================================================
310
+ // Export patterns for testing
311
+ // ============================================================================
312
+ export { PATTERNS };