@poolzin/pool-bot 2026.3.7 → 2026.3.9

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 (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/agents/error-classifier.js +302 -0
  4. package/dist/agents/skills/security.js +217 -0
  5. package/dist/build-info.json +3 -3
  6. package/dist/cli/lazy-commands.example.js +113 -0
  7. package/dist/cli/lazy-commands.js +329 -0
  8. package/dist/cli/program/command-registry.js +13 -0
  9. package/dist/cli/program/register.skills.js +4 -0
  10. package/dist/config/config.js +1 -0
  11. package/dist/config/secrets-integration.js +88 -0
  12. package/dist/context-engine/index.js +33 -0
  13. package/dist/context-engine/legacy.js +181 -0
  14. package/dist/context-engine/registry.js +86 -0
  15. package/dist/context-engine/summarizing.js +293 -0
  16. package/dist/context-engine/types.js +7 -0
  17. package/dist/infra/abort-pattern.js +106 -0
  18. package/dist/infra/retry.js +94 -0
  19. package/dist/secrets/index.js +28 -0
  20. package/dist/secrets/resolver.js +185 -0
  21. package/dist/secrets/runtime.js +142 -0
  22. package/dist/secrets/types.js +11 -0
  23. package/dist/security/dangerous-tools.js +80 -0
  24. package/dist/security/types.js +12 -0
  25. package/dist/skills/commands.js +351 -0
  26. package/dist/skills/index.js +167 -0
  27. package/dist/skills/loader.js +282 -0
  28. package/dist/skills/parser.js +461 -0
  29. package/dist/skills/registry.js +397 -0
  30. package/dist/skills/security.js +318 -0
  31. package/dist/skills/types.js +21 -0
  32. package/dist/test-utils/index.js +219 -0
  33. package/dist/tui/index.js +595 -0
  34. package/docs/INTEGRATION_PLAN.md +475 -0
  35. package/docs/INTEGRATION_SUMMARY.md +215 -0
  36. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  37. package/docs/integrations/INTEGRATION_PLAN.md +424 -0
  38. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  39. package/docs/integrations/XYOPS_PLAN.md +978 -0
  40. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  41. package/docs/skills/SKILL.md +524 -0
  42. package/docs/skills.md +405 -0
  43. package/package.json +1 -1
  44. package/skills/example-skill/SKILL.md +195 -0
@@ -0,0 +1,318 @@
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 [
292
+ "critical",
293
+ "high",
294
+ "medium",
295
+ "low",
296
+ "info",
297
+ ]) {
298
+ const group = bySeverity[severity];
299
+ if (group.length === 0)
300
+ continue;
301
+ lines.push(`\n${severity.toUpperCase()} (${group.length}):`);
302
+ for (const finding of group) {
303
+ lines.push(` [${finding.type}] ${finding.description}`);
304
+ lines.push(` Location: ${finding.location}`);
305
+ if (finding.evidence) {
306
+ lines.push(` Evidence: ${finding.evidence}`);
307
+ }
308
+ if (finding.remediation) {
309
+ lines.push(` Fix: ${finding.remediation}`);
310
+ }
311
+ }
312
+ }
313
+ return lines;
314
+ }
315
+ // ============================================================================
316
+ // Export patterns for testing
317
+ // ============================================================================
318
+ export { PATTERNS };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Skill types and interfaces for PoolBot Skills System
3
+ * Compatible with agentskills.io ecosystem
4
+ *
5
+ * @module skills/types
6
+ */
7
+ /**
8
+ * Skill system error
9
+ */
10
+ export class SkillError extends Error {
11
+ code;
12
+ skillId;
13
+ cause;
14
+ constructor(code, message, skillId, cause) {
15
+ super(message);
16
+ this.code = code;
17
+ this.skillId = skillId;
18
+ this.cause = cause;
19
+ this.name = "SkillError";
20
+ }
21
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Test Utilities for PoolBot
3
+ *
4
+ * Shared testing helpers and fixtures for consistent testing.
5
+ */
6
+ import { afterEach } from "vitest";
7
+ /**
8
+ * Frozen time utility for deterministic time-based tests
9
+ */
10
+ export class FrozenTime {
11
+ originalDateNow;
12
+ currentTime;
13
+ constructor(startTime = 1700000000000) {
14
+ this.originalDateNow = Date.now;
15
+ this.currentTime = startTime;
16
+ }
17
+ /**
18
+ * Freeze time at a specific timestamp
19
+ */
20
+ freeze(timestamp) {
21
+ if (timestamp !== undefined) {
22
+ this.currentTime = timestamp;
23
+ }
24
+ Date.now = () => this.currentTime;
25
+ }
26
+ /**
27
+ * Advance time by milliseconds
28
+ */
29
+ advance(ms) {
30
+ this.currentTime += ms;
31
+ }
32
+ /**
33
+ * Get current frozen time
34
+ */
35
+ now() {
36
+ return this.currentTime;
37
+ }
38
+ /**
39
+ * Restore original Date.now
40
+ */
41
+ restore() {
42
+ Date.now = this.originalDateNow;
43
+ }
44
+ /**
45
+ * Create ISO timestamp from current frozen time
46
+ */
47
+ toISOString() {
48
+ return new Date(this.currentTime).toISOString();
49
+ }
50
+ }
51
+ /**
52
+ * Create a test fixture with isolated state
53
+ */
54
+ export function createFixture(factory, options = {}) {
55
+ let instance = null;
56
+ const get = () => {
57
+ if (!instance) {
58
+ instance = factory();
59
+ }
60
+ return instance;
61
+ };
62
+ const reset = () => {
63
+ instance = null;
64
+ };
65
+ if (options.autoCleanup !== false) {
66
+ afterEach(reset);
67
+ }
68
+ return { get, reset };
69
+ }
70
+ /**
71
+ * Type-safe test case builder
72
+ */
73
+ export function typedCases(cases) {
74
+ return cases;
75
+ }
76
+ /**
77
+ * Temporary directory helper
78
+ */
79
+ export class TempDir {
80
+ dirs = [];
81
+ /**
82
+ * Create a temporary directory
83
+ */
84
+ create(prefix = "poolbot-test-") {
85
+ const dir = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
86
+ this.dirs.push(dir);
87
+ return dir;
88
+ }
89
+ /**
90
+ * Clean up all created directories
91
+ */
92
+ cleanup() {
93
+ this.dirs = [];
94
+ }
95
+ }
96
+ /**
97
+ * Mock utilities for common dependencies
98
+ */
99
+ export class MockFactory {
100
+ /**
101
+ * Create a mock function with tracking
102
+ */
103
+ static fn(implementation) {
104
+ const calls = [];
105
+ const mockFn = ((...args) => {
106
+ const result = implementation ? implementation(...args) : undefined;
107
+ calls.push({ args, result });
108
+ return result;
109
+ });
110
+ mockFn.calls = calls;
111
+ mockFn.mockClear = () => {
112
+ calls.length = 0;
113
+ };
114
+ return mockFn;
115
+ }
116
+ /**
117
+ * Create a mock logger
118
+ */
119
+ static logger() {
120
+ return {
121
+ info: MockFactory.fn(),
122
+ warn: MockFactory.fn(),
123
+ error: MockFactory.fn(),
124
+ debug: MockFactory.fn(),
125
+ logs: [],
126
+ };
127
+ }
128
+ }
129
+ /**
130
+ * Assertion helpers
131
+ */
132
+ export class Assert {
133
+ /**
134
+ * Assert that a promise resolves within a timeout
135
+ */
136
+ static async resolvesWithin(promise, timeoutMs, message) {
137
+ const timeout = new Promise((_, reject) => {
138
+ setTimeout(() => reject(new Error(message ?? `Promise did not resolve within ${timeoutMs}ms`)), timeoutMs);
139
+ });
140
+ return Promise.race([promise, timeout]);
141
+ }
142
+ /**
143
+ * Assert that an error is thrown
144
+ */
145
+ static async throws(fn, expectedMessage) {
146
+ try {
147
+ await fn();
148
+ throw new Error("Expected function to throw but it did not");
149
+ }
150
+ catch (error) {
151
+ if (error instanceof Error) {
152
+ if (expectedMessage) {
153
+ if (expectedMessage instanceof RegExp) {
154
+ if (!expectedMessage.test(error.message)) {
155
+ throw new Error(`Expected error message to match ${expectedMessage}, got: ${error.message}`);
156
+ }
157
+ }
158
+ else if (!error.message.includes(expectedMessage)) {
159
+ throw new Error(`Expected error message to include "${expectedMessage}", got: ${error.message}`);
160
+ }
161
+ }
162
+ return error;
163
+ }
164
+ throw error;
165
+ }
166
+ }
167
+ }
168
+ /**
169
+ * Data generators for tests
170
+ */
171
+ export class Generators {
172
+ /**
173
+ * Generate a UUID v4
174
+ */
175
+ static uuid() {
176
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
177
+ const r = (Math.random() * 16) | 0;
178
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
179
+ return v.toString(16);
180
+ });
181
+ }
182
+ /**
183
+ * Generate a random session ID
184
+ */
185
+ static sessionId() {
186
+ return `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
187
+ }
188
+ /**
189
+ * Generate a timestamp with optional offset
190
+ */
191
+ static timestamp(offsetMs = 0) {
192
+ return Date.now() + offsetMs;
193
+ }
194
+ /**
195
+ * Generate random text of specified length
196
+ */
197
+ static text(length = 100) {
198
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ";
199
+ let result = "";
200
+ for (let i = 0; i < length; i++) {
201
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
202
+ }
203
+ return result;
204
+ }
205
+ }
206
+ /**
207
+ * Setup helper for common test patterns
208
+ */
209
+ export function setupTestEnvironment() {
210
+ const frozenTime = new FrozenTime();
211
+ const tempDir = new TempDir();
212
+ frozenTime.freeze();
213
+ const cleanup = () => {
214
+ frozenTime.restore();
215
+ tempDir.cleanup();
216
+ };
217
+ afterEach(cleanup);
218
+ return { frozenTime, tempDir, cleanup };
219
+ }