@poolzin/pool-bot 2026.3.6 → 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.
- package/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/pi-tools.js +32 -2
- package/dist/agents/skills/security.js +217 -0
- package/dist/auto-reply/reply/get-reply.js +6 -0
- package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/banner.js +20 -1
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/security-cli.js +211 -2
- package/dist/cli/tagline.js +7 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/config/types.cli.js +1 -0
- package/dist/config/types.security.js +33 -0
- package/dist/config/zod-schema.js +15 -0
- package/dist/config/zod-schema.providers-core.js +1 -0
- package/dist/config/zod-schema.security.js +113 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- package/dist/hooks/fire-and-forget.js +6 -0
- package/dist/hooks/internal-hooks.js +64 -19
- package/dist/hooks/message-hook-mappers.js +179 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/capability-guards.js +89 -0
- package/dist/security/capability-manager.js +76 -0
- package/dist/security/capability.js +147 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/index.js +7 -0
- package/dist/security/middleware.js +105 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/slack/monitor/context.js +1 -0
- package/dist/slack/monitor/message-handler/dispatch.js +14 -1
- package/dist/slack/monitor/provider.js +2 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## v2026.3.9 (2026-03-09)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- **Modular Skills System:** new comprehensive skills management system in `src/skills/` — 8 core modules providing SKILL.md parsing, security scanning, registry management, progressive disclosure loading, and CLI commands (`poolbot mods`)
|
|
5
|
+
- `types.ts`: Type definitions compatible with agentskills.io specification
|
|
6
|
+
- `parser.ts`: YAML frontmatter parser for SKILL.md files with validation
|
|
7
|
+
- `registry.ts`: EventEmitter-based skills registry with lifecycle management
|
|
8
|
+
- `loader.ts`: Progressive disclosure loader with dependency resolution
|
|
9
|
+
- `security.ts`: Security scanner for prompt injection and path traversal detection
|
|
10
|
+
- `commands.ts`: CLI commands for skill management
|
|
11
|
+
- `index.ts`: Public API exports
|
|
12
|
+
- Comprehensive test suite with 52 passing tests
|
|
13
|
+
- **Integration Plans:** detailed implementation plans for external project integrations (Page Agent, HexStrike AI, xyOps) via Gateway Node protocol
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
1
17
|
## v2026.3.6 (2026-03-06)
|
|
2
18
|
|
|
3
19
|
### Features
|
package/dist/.buildstamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1773025806569
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Classification System
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive error classification for LLM/AI operations.
|
|
5
|
+
* Detects specific error types like context overflow, rate limits, etc.
|
|
6
|
+
*/
|
|
7
|
+
// OpenAI error patterns
|
|
8
|
+
const OPENAI_PATTERNS = {
|
|
9
|
+
context_overflow: [
|
|
10
|
+
/context length exceeded/i,
|
|
11
|
+
/maximum context length/i,
|
|
12
|
+
/token limit exceeded/i,
|
|
13
|
+
/too many tokens/i,
|
|
14
|
+
/rate_limit_exceeded.*context/i,
|
|
15
|
+
],
|
|
16
|
+
rate_limit: [
|
|
17
|
+
/rate limit exceeded/i,
|
|
18
|
+
/too many requests/i,
|
|
19
|
+
/ratelimit/i,
|
|
20
|
+
],
|
|
21
|
+
authentication: [
|
|
22
|
+
/invalid api key/i,
|
|
23
|
+
/incorrect api key/i,
|
|
24
|
+
/authentication/i,
|
|
25
|
+
/unauthorized/i,
|
|
26
|
+
],
|
|
27
|
+
invalid_request: [
|
|
28
|
+
/invalid_request_error/i,
|
|
29
|
+
/bad request/i,
|
|
30
|
+
/invalid parameter/i,
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
// Anthropic error patterns
|
|
34
|
+
const ANTHROPIC_PATTERNS = {
|
|
35
|
+
context_overflow: [
|
|
36
|
+
/context window exceeded/i,
|
|
37
|
+
/maximum token count/i,
|
|
38
|
+
/too many tokens/i,
|
|
39
|
+
],
|
|
40
|
+
rate_limit: [
|
|
41
|
+
/rate limit/i,
|
|
42
|
+
/too many requests/i,
|
|
43
|
+
],
|
|
44
|
+
authentication: [
|
|
45
|
+
/invalid api key/i,
|
|
46
|
+
/authentication failed/i,
|
|
47
|
+
],
|
|
48
|
+
authorization: [
|
|
49
|
+
/forbidden/i,
|
|
50
|
+
/access denied/i,
|
|
51
|
+
/unauthorized.*resource/i,
|
|
52
|
+
/insufficient.*permission/i,
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
// Google/Gemini error patterns
|
|
56
|
+
const GEMINI_PATTERNS = {
|
|
57
|
+
context_overflow: [
|
|
58
|
+
/token limit exceeded/i,
|
|
59
|
+
/maximum input size/i,
|
|
60
|
+
/context too long/i,
|
|
61
|
+
],
|
|
62
|
+
rate_limit: [
|
|
63
|
+
/rate limit exceeded/i,
|
|
64
|
+
/quota exceeded/i,
|
|
65
|
+
/too many requests/i,
|
|
66
|
+
],
|
|
67
|
+
quota_exceeded: [
|
|
68
|
+
/quota exceeded/i,
|
|
69
|
+
/billing limit/i,
|
|
70
|
+
/project quota/i,
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
// Generic patterns
|
|
74
|
+
const GENERIC_PATTERNS = {
|
|
75
|
+
context_overflow: [
|
|
76
|
+
/context.*overflow/i,
|
|
77
|
+
/context.*exceeded/i,
|
|
78
|
+
/token.*limit/i,
|
|
79
|
+
/maximum.*context/i,
|
|
80
|
+
/message too long/i,
|
|
81
|
+
/input too long/i,
|
|
82
|
+
],
|
|
83
|
+
rate_limit: [
|
|
84
|
+
/rate.?limit/i,
|
|
85
|
+
/too many requests/i,
|
|
86
|
+
/throttled/i,
|
|
87
|
+
/429/i,
|
|
88
|
+
],
|
|
89
|
+
timeout: [
|
|
90
|
+
/timeout/i,
|
|
91
|
+
/timed out/i,
|
|
92
|
+
/etimedout/i,
|
|
93
|
+
],
|
|
94
|
+
network_error: [
|
|
95
|
+
/network error/i,
|
|
96
|
+
/econnreset/i,
|
|
97
|
+
/econnrefused/i,
|
|
98
|
+
/enotfound/i,
|
|
99
|
+
/network/i,
|
|
100
|
+
],
|
|
101
|
+
server_error: [
|
|
102
|
+
/server error/i,
|
|
103
|
+
/internal error/i,
|
|
104
|
+
/500/i,
|
|
105
|
+
/502/i,
|
|
106
|
+
/503/i,
|
|
107
|
+
/504/i,
|
|
108
|
+
],
|
|
109
|
+
compaction_failure: [
|
|
110
|
+
/compaction failed/i,
|
|
111
|
+
/summarization failed/i,
|
|
112
|
+
/failed to compact/i,
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Classify an error based on message patterns
|
|
117
|
+
*/
|
|
118
|
+
export function classifyError(error) {
|
|
119
|
+
if (!(error instanceof Error)) {
|
|
120
|
+
return {
|
|
121
|
+
type: "unknown",
|
|
122
|
+
retryable: false,
|
|
123
|
+
message: String(error),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const message = error.message.toLowerCase();
|
|
127
|
+
// Check context overflow first (highest priority for LLM errors)
|
|
128
|
+
for (const pattern of [...OPENAI_PATTERNS.context_overflow, ...ANTHROPIC_PATTERNS.context_overflow, ...GEMINI_PATTERNS.context_overflow, ...GENERIC_PATTERNS.context_overflow]) {
|
|
129
|
+
if (pattern.test(message)) {
|
|
130
|
+
return {
|
|
131
|
+
type: "context_overflow",
|
|
132
|
+
retryable: false,
|
|
133
|
+
message: error.message,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Check quota exceeded (before rate limit to avoid false positives)
|
|
138
|
+
for (const pattern of GEMINI_PATTERNS.quota_exceeded) {
|
|
139
|
+
if (pattern.test(message)) {
|
|
140
|
+
return {
|
|
141
|
+
type: "quota_exceeded",
|
|
142
|
+
retryable: false,
|
|
143
|
+
message: error.message,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Check rate limit
|
|
148
|
+
for (const pattern of [...OPENAI_PATTERNS.rate_limit, ...ANTHROPIC_PATTERNS.rate_limit, ...GEMINI_PATTERNS.rate_limit, ...GENERIC_PATTERNS.rate_limit]) {
|
|
149
|
+
if (pattern.test(message)) {
|
|
150
|
+
return {
|
|
151
|
+
type: "rate_limit",
|
|
152
|
+
retryable: true,
|
|
153
|
+
message: error.message,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Check authentication
|
|
158
|
+
for (const pattern of [...OPENAI_PATTERNS.authentication, ...ANTHROPIC_PATTERNS.authentication]) {
|
|
159
|
+
if (pattern.test(message)) {
|
|
160
|
+
return {
|
|
161
|
+
type: "authentication",
|
|
162
|
+
retryable: false,
|
|
163
|
+
message: error.message,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Check authorization (403-like errors)
|
|
168
|
+
for (const pattern of ANTHROPIC_PATTERNS.authorization) {
|
|
169
|
+
if (pattern.test(message)) {
|
|
170
|
+
return {
|
|
171
|
+
type: "authorization",
|
|
172
|
+
retryable: false,
|
|
173
|
+
message: error.message,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Check timeout
|
|
178
|
+
for (const pattern of GENERIC_PATTERNS.timeout) {
|
|
179
|
+
if (pattern.test(message)) {
|
|
180
|
+
return {
|
|
181
|
+
type: "timeout",
|
|
182
|
+
retryable: true,
|
|
183
|
+
message: error.message,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Check network errors
|
|
188
|
+
for (const pattern of GENERIC_PATTERNS.network_error) {
|
|
189
|
+
if (pattern.test(message)) {
|
|
190
|
+
return {
|
|
191
|
+
type: "network_error",
|
|
192
|
+
retryable: true,
|
|
193
|
+
message: error.message,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Check server errors
|
|
198
|
+
for (const pattern of GENERIC_PATTERNS.server_error) {
|
|
199
|
+
if (pattern.test(message)) {
|
|
200
|
+
return {
|
|
201
|
+
type: "server_error",
|
|
202
|
+
retryable: true,
|
|
203
|
+
message: error.message,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Check compaction failure
|
|
208
|
+
for (const pattern of GENERIC_PATTERNS.compaction_failure) {
|
|
209
|
+
if (pattern.test(message)) {
|
|
210
|
+
return {
|
|
211
|
+
type: "compaction_failure",
|
|
212
|
+
retryable: true,
|
|
213
|
+
message: error.message,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Default to unknown
|
|
218
|
+
return {
|
|
219
|
+
type: "unknown",
|
|
220
|
+
retryable: false,
|
|
221
|
+
message: error.message,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Check if error is a context overflow error
|
|
226
|
+
*/
|
|
227
|
+
export function isContextOverflowError(error) {
|
|
228
|
+
const classification = classifyError(error);
|
|
229
|
+
return classification.type === "context_overflow";
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check if error is a rate limit error
|
|
233
|
+
*/
|
|
234
|
+
export function isRateLimitError(error) {
|
|
235
|
+
const classification = classifyError(error);
|
|
236
|
+
return classification.type === "rate_limit";
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check if error is a compaction failure
|
|
240
|
+
*/
|
|
241
|
+
export function isCompactionFailureError(error) {
|
|
242
|
+
const classification = classifyError(error);
|
|
243
|
+
return classification.type === "compaction_failure";
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check if error is likely a context overflow (heuristic)
|
|
247
|
+
*/
|
|
248
|
+
export function isLikelyContextOverflowError(error) {
|
|
249
|
+
if (!(error instanceof Error)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const message = error.message.toLowerCase();
|
|
253
|
+
// Check for common indicators
|
|
254
|
+
const indicators = [
|
|
255
|
+
"context",
|
|
256
|
+
"token",
|
|
257
|
+
"length",
|
|
258
|
+
"exceeded",
|
|
259
|
+
"maximum",
|
|
260
|
+
"limit",
|
|
261
|
+
"too long",
|
|
262
|
+
"too many",
|
|
263
|
+
];
|
|
264
|
+
const score = indicators.reduce((acc, indicator) => {
|
|
265
|
+
return acc + (message.includes(indicator) ? 1 : 0);
|
|
266
|
+
}, 0);
|
|
267
|
+
// If 3+ indicators present, likely context overflow
|
|
268
|
+
return score >= 3;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get retry delay for error type
|
|
272
|
+
*/
|
|
273
|
+
export function getRetryDelayForError(error, attempt) {
|
|
274
|
+
const classification = classifyError(error);
|
|
275
|
+
switch (classification.type) {
|
|
276
|
+
case "rate_limit":
|
|
277
|
+
// Rate limits: start with 2s, double each attempt
|
|
278
|
+
return Math.min(2000 * 2 ** attempt, 60000);
|
|
279
|
+
case "timeout":
|
|
280
|
+
case "network_error":
|
|
281
|
+
// Network issues: start with 500ms
|
|
282
|
+
return Math.min(500 * 2 ** attempt, 30000);
|
|
283
|
+
case "server_error":
|
|
284
|
+
// Server errors: start with 1s
|
|
285
|
+
return Math.min(1000 * 2 ** attempt, 30000);
|
|
286
|
+
case "compaction_failure":
|
|
287
|
+
// Compaction: shorter delays
|
|
288
|
+
return Math.min(500 * 2 ** attempt, 5000);
|
|
289
|
+
default:
|
|
290
|
+
return 1000;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Format error for logging (sanitized)
|
|
295
|
+
*/
|
|
296
|
+
export function formatErrorForLogging(error) {
|
|
297
|
+
if (error instanceof Error) {
|
|
298
|
+
const classification = classifyError(error);
|
|
299
|
+
return `[${classification.type}] ${classification.message}`;
|
|
300
|
+
}
|
|
301
|
+
return `[unknown] ${String(error)}`;
|
|
302
|
+
}
|
package/dist/agents/pi-tools.js
CHANGED
|
@@ -16,8 +16,10 @@ import { assertRequiredParams, CLAUDE_PARAM_GROUPS, createPoolbotReadTool, creat
|
|
|
16
16
|
import { cleanToolSchemaForGemini, normalizeToolParameters } from "./pi-tools.schema.js";
|
|
17
17
|
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
|
18
18
|
import { applyToolPolicyPipeline, buildDefaultToolPolicyPipelineSteps, } from "./tool-policy-pipeline.js";
|
|
19
|
-
import { applyOwnerOnlyToolPolicy, collectExplicitAllowlist, mergeAlsoAllowPolicy, resolveToolProfilePolicy, } from "./tool-policy.js";
|
|
19
|
+
import { applyOwnerOnlyToolPolicy, collectExplicitAllowlist, mergeAlsoAllowPolicy, normalizeToolName, resolveToolProfilePolicy, } from "./tool-policy.js";
|
|
20
20
|
import { resolveWorkspaceRoot } from "./workspace-dir.js";
|
|
21
|
+
import { CapabilityError } from "../security/capability-guards.js";
|
|
22
|
+
import { createDefaultSecurityMiddleware } from "../security/middleware.js";
|
|
21
23
|
function isOpenAIProvider(provider) {
|
|
22
24
|
const normalized = provider?.trim().toLowerCase();
|
|
23
25
|
return normalized === "openai" || normalized === "openai-codex";
|
|
@@ -339,8 +341,36 @@ export function createPoolbotCodingTools(options) {
|
|
|
339
341
|
const withAbort = options?.abortSignal
|
|
340
342
|
? withHooks.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal))
|
|
341
343
|
: withHooks;
|
|
344
|
+
// Apply capability-based security middleware if enabled
|
|
345
|
+
const withCapabilities = options?.config?.security?.enabled && agentId
|
|
346
|
+
? withAbort.map((tool) => wrapToolWithCapabilityCheck(tool, agentId))
|
|
347
|
+
: withAbort;
|
|
342
348
|
// NOTE: Keep canonical (lowercase) tool names here.
|
|
343
349
|
// pi-ai's Anthropic OAuth transport remaps tool names to Claude Code-style names
|
|
344
350
|
// on the wire and maps them back for tool dispatch.
|
|
345
|
-
return
|
|
351
|
+
return withCapabilities;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Wraps a tool with capability-based security checks.
|
|
355
|
+
* This enforces fine-grained permissions for tool invocation.
|
|
356
|
+
*/
|
|
357
|
+
function wrapToolWithCapabilityCheck(tool, agentId) {
|
|
358
|
+
const middleware = createDefaultSecurityMiddleware();
|
|
359
|
+
return {
|
|
360
|
+
...tool,
|
|
361
|
+
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
362
|
+
const ctx = { agentId };
|
|
363
|
+
const toolId = normalizeToolName(tool.name);
|
|
364
|
+
const result = await middleware(ctx, toolId, (args ?? {}), async () => {
|
|
365
|
+
if (!tool.execute) {
|
|
366
|
+
throw new CapabilityError(`Tool ${tool.name} has no execute function`, agentId, {
|
|
367
|
+
type: "tool:invoke",
|
|
368
|
+
toolId,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return await tool.execute(toolCallId, args, signal, onUpdate);
|
|
372
|
+
});
|
|
373
|
+
return result;
|
|
374
|
+
},
|
|
375
|
+
};
|
|
346
376
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Security Scanner
|
|
3
|
+
*
|
|
4
|
+
* Security scanning for PoolBot skills.
|
|
5
|
+
* Detects potential security issues in skill files.
|
|
6
|
+
*
|
|
7
|
+
* @module agents/skills/security
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const SCANNER_VERSION = "1.0.0";
|
|
13
|
+
// Patterns that indicate potential security issues
|
|
14
|
+
const PATTERNS = [
|
|
15
|
+
// Prompt injection attempts
|
|
16
|
+
{
|
|
17
|
+
type: "prompt_injection",
|
|
18
|
+
severity: "critical",
|
|
19
|
+
pattern: /ignore\s+(?:previous|above|prior)|disregard\s+(?:instructions?|prompt)|system\s*:\s*you\s+are|new\s+instructions?\s*:/i,
|
|
20
|
+
description: "Potential prompt injection attempt detected",
|
|
21
|
+
remediation: "Review skill content for malicious instruction overrides",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "prompt_injection",
|
|
25
|
+
severity: "high",
|
|
26
|
+
pattern: /\[\s*system\s*\]|\(\s*system\s*\)|\{\s*system\s*\}|\bDAN\b|do\s+anything\s+now/i,
|
|
27
|
+
description: "Suspicious system role reference",
|
|
28
|
+
remediation: "Verify skill doesn't attempt to override system behavior",
|
|
29
|
+
},
|
|
30
|
+
// Command injection
|
|
31
|
+
{
|
|
32
|
+
type: "command_injection",
|
|
33
|
+
severity: "critical",
|
|
34
|
+
pattern: /(?:bash|sh|zsh|cmd|powershell)\s+-c\s+["']|exec\s*\(|eval\s*\(|system\s*\(/i,
|
|
35
|
+
description: "Potential command injection pattern",
|
|
36
|
+
remediation: "Avoid executing arbitrary shell commands from skill content",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: "command_injection",
|
|
40
|
+
severity: "high",
|
|
41
|
+
pattern: /`[^`]*(?:rm|del|format|mkfs|dd|wget|curl|fetch)[^`]*`|\$\([^)]*(?:rm|del|wget|curl)[^)]*\)/i,
|
|
42
|
+
description: "Dangerous command in template literal",
|
|
43
|
+
remediation: "Review shell command usage for safety",
|
|
44
|
+
},
|
|
45
|
+
// Path traversal
|
|
46
|
+
{
|
|
47
|
+
type: "path_traversal",
|
|
48
|
+
severity: "high",
|
|
49
|
+
pattern: /\.\.[/\\]|\.\.%2f|\.\.%5c|%2e%2e[/\\]/i,
|
|
50
|
+
description: "Path traversal attempt detected",
|
|
51
|
+
remediation: "Validate and sanitize all file paths",
|
|
52
|
+
},
|
|
53
|
+
// Suspicious patterns
|
|
54
|
+
{
|
|
55
|
+
type: "suspicious_pattern",
|
|
56
|
+
severity: "medium",
|
|
57
|
+
pattern: /(?:password|secret|token|key|credential)\s*=\s*["'][^"']{8,}["']/i,
|
|
58
|
+
description: "Hardcoded credential-like pattern",
|
|
59
|
+
remediation: "Use environment variables or secure secret storage",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: "suspicious_pattern",
|
|
63
|
+
severity: "medium",
|
|
64
|
+
pattern: /base64\s*\(\s*["'][^"']{20,}["']\s*\)|atob\s*\(|btoa\s*\(/i,
|
|
65
|
+
description: "Suspicious encoding/decoding pattern",
|
|
66
|
+
remediation: "Verify encoding is not used to obfuscate malicious content",
|
|
67
|
+
},
|
|
68
|
+
// External dependencies
|
|
69
|
+
{
|
|
70
|
+
type: "external_dependency",
|
|
71
|
+
severity: "low",
|
|
72
|
+
pattern: /(?:npm|pip|gem|cargo|go\s+get)\s+install/i,
|
|
73
|
+
description: "External package installation mentioned",
|
|
74
|
+
remediation: "Verify all external dependencies are trustworthy",
|
|
75
|
+
},
|
|
76
|
+
// Data exfiltration
|
|
77
|
+
{
|
|
78
|
+
type: "data_exfiltration",
|
|
79
|
+
severity: "high",
|
|
80
|
+
pattern: /(?:https?:\/\/|ftp:\/\/)[^\s"']+(?:webhook|callback|exfil|collect|steal|send)/i,
|
|
81
|
+
description: "Potential data exfiltration endpoint",
|
|
82
|
+
remediation: "Verify all external URLs are legitimate",
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Scanner
|
|
87
|
+
// ============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* Scan skill content for security issues
|
|
90
|
+
*/
|
|
91
|
+
export function scanSkill(skillName, content) {
|
|
92
|
+
const findings = [];
|
|
93
|
+
const lines = content.split("\n");
|
|
94
|
+
for (const { type, severity, pattern, description, remediation } of PATTERNS) {
|
|
95
|
+
for (let i = 0; i < lines.length; i++) {
|
|
96
|
+
const line = lines[i];
|
|
97
|
+
const matches = line.matchAll(pattern);
|
|
98
|
+
for (const match of matches) {
|
|
99
|
+
if (match.index !== undefined) {
|
|
100
|
+
findings.push({
|
|
101
|
+
type,
|
|
102
|
+
severity,
|
|
103
|
+
line: i + 1,
|
|
104
|
+
column: match.index + 1,
|
|
105
|
+
match: match[0].slice(0, 100), // Limit match length
|
|
106
|
+
description,
|
|
107
|
+
remediation,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Sort by severity
|
|
114
|
+
const severityOrder = [
|
|
115
|
+
"critical",
|
|
116
|
+
"high",
|
|
117
|
+
"medium",
|
|
118
|
+
"low",
|
|
119
|
+
"info",
|
|
120
|
+
];
|
|
121
|
+
findings.sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));
|
|
122
|
+
return {
|
|
123
|
+
skillName,
|
|
124
|
+
scannerVersion: SCANNER_VERSION,
|
|
125
|
+
scannedAt: new Date(),
|
|
126
|
+
findings,
|
|
127
|
+
passed: !findings.some((f) => f.severity === "critical" || f.severity === "high"),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Quick security check - returns true if skill passes basic security
|
|
132
|
+
*/
|
|
133
|
+
export function quickSecurityCheck(skillName, content) {
|
|
134
|
+
const report = scanSkill(skillName, content);
|
|
135
|
+
return report.passed;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get security summary for display
|
|
139
|
+
*/
|
|
140
|
+
export function getSecuritySummary(report) {
|
|
141
|
+
const counts = {
|
|
142
|
+
critical: 0,
|
|
143
|
+
high: 0,
|
|
144
|
+
medium: 0,
|
|
145
|
+
low: 0,
|
|
146
|
+
info: 0,
|
|
147
|
+
};
|
|
148
|
+
for (const finding of report.findings) {
|
|
149
|
+
counts[finding.severity]++;
|
|
150
|
+
}
|
|
151
|
+
const hasCritical = counts.critical > 0;
|
|
152
|
+
const hasHigh = counts.high > 0;
|
|
153
|
+
const hasMedium = counts.medium > 0;
|
|
154
|
+
const hasLow = counts.low > 0;
|
|
155
|
+
if (hasCritical) {
|
|
156
|
+
return {
|
|
157
|
+
status: "Failed",
|
|
158
|
+
color: "red",
|
|
159
|
+
summary: `${counts.critical} critical, ${counts.high} high severity issues`,
|
|
160
|
+
counts,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (hasHigh) {
|
|
164
|
+
return {
|
|
165
|
+
status: "Warning",
|
|
166
|
+
color: "yellow",
|
|
167
|
+
summary: `${counts.high} high severity issues`,
|
|
168
|
+
counts,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (hasMedium || hasLow) {
|
|
172
|
+
return {
|
|
173
|
+
status: "Passed",
|
|
174
|
+
color: "yellow",
|
|
175
|
+
summary: `${counts.medium} medium, ${counts.low} low severity issues`,
|
|
176
|
+
counts,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
status: "Passed",
|
|
181
|
+
color: "green",
|
|
182
|
+
summary: "No security issues found",
|
|
183
|
+
counts,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Format findings for display
|
|
188
|
+
*/
|
|
189
|
+
export function formatFindings(findings) {
|
|
190
|
+
if (findings.length === 0) {
|
|
191
|
+
return ["No security issues found."];
|
|
192
|
+
}
|
|
193
|
+
const lines = [];
|
|
194
|
+
const bySeverity = {
|
|
195
|
+
critical: [],
|
|
196
|
+
high: [],
|
|
197
|
+
medium: [],
|
|
198
|
+
low: [],
|
|
199
|
+
info: [],
|
|
200
|
+
};
|
|
201
|
+
for (const finding of findings) {
|
|
202
|
+
bySeverity[finding.severity].push(finding);
|
|
203
|
+
}
|
|
204
|
+
for (const severity of ["critical", "high", "medium", "low", "info"]) {
|
|
205
|
+
const items = bySeverity[severity];
|
|
206
|
+
if (items.length === 0)
|
|
207
|
+
continue;
|
|
208
|
+
lines.push(`\n${severity.toUpperCase()} (${items.length}):`);
|
|
209
|
+
for (const finding of items) {
|
|
210
|
+
lines.push(` [${finding.type}] Line ${finding.line}: ${finding.description}`);
|
|
211
|
+
if (finding.remediation) {
|
|
212
|
+
lines.push(` → ${finding.remediation}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return lines;
|
|
217
|
+
}
|
|
@@ -14,6 +14,7 @@ import { resolveReplyDirectives } from "./get-reply-directives.js";
|
|
|
14
14
|
import { handleInlineActions } from "./get-reply-inline-actions.js";
|
|
15
15
|
import { runPreparedReply } from "./get-reply-run.js";
|
|
16
16
|
import { finalizeInboundContext } from "./inbound-context.js";
|
|
17
|
+
import { emitPreAgentMessageHooks } from "./message-preprocess-hooks.js";
|
|
17
18
|
import { applyResetModelOverride } from "./session-reset-model.js";
|
|
18
19
|
import { initSessionState } from "./session.js";
|
|
19
20
|
import { stageSandboxMedia } from "./stage-sandbox-media.js";
|
|
@@ -110,6 +111,11 @@ export async function getReplyFromConfig(ctx, opts, configOverride) {
|
|
|
110
111
|
cfg,
|
|
111
112
|
});
|
|
112
113
|
}
|
|
114
|
+
emitPreAgentMessageHooks({
|
|
115
|
+
ctx: finalized,
|
|
116
|
+
cfg,
|
|
117
|
+
isFastTestEnv,
|
|
118
|
+
});
|
|
113
119
|
const commandAuthorized = finalized.CommandAuthorized;
|
|
114
120
|
resolveCommandAuthorization({
|
|
115
121
|
ctx: finalized,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fireAndForgetHook } from "../../hooks/fire-and-forget.js";
|
|
2
|
+
import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
|
|
3
|
+
import { deriveInboundMessageHookContext, toInternalMessagePreprocessedContext, toInternalMessageTranscribedContext, } from "../../hooks/message-hook-mappers.js";
|
|
4
|
+
export function emitPreAgentMessageHooks(params) {
|
|
5
|
+
if (params.isFastTestEnv) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const sessionKey = params.ctx.SessionKey?.trim();
|
|
9
|
+
if (!sessionKey) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const canonical = deriveInboundMessageHookContext(params.ctx);
|
|
13
|
+
if (canonical.transcript) {
|
|
14
|
+
fireAndForgetHook(triggerInternalHook(createInternalHookEvent("message", "transcribed", sessionKey, toInternalMessageTranscribedContext(canonical, params.cfg))), "get-reply: message:transcribed internal hook failed");
|
|
15
|
+
}
|
|
16
|
+
fireAndForgetHook(triggerInternalHook(createInternalHookEvent("message", "preprocessed", sessionKey, toInternalMessagePreprocessedContext(canonical, params.cfg))), "get-reply: message:preprocessed internal hook failed");
|
|
17
|
+
}
|
package/dist/build-info.json
CHANGED
package/dist/cli/banner.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
import { loadConfig } from "../config/config.js";
|
|
1
2
|
import { resolveCommitHash } from "../infra/git-commit.js";
|
|
2
3
|
import { visibleWidth } from "../terminal/ansi.js";
|
|
3
4
|
import { isRich, theme } from "../terminal/theme.js";
|
|
4
5
|
import { hasRootVersionAlias } from "./argv.js";
|
|
5
6
|
import { pickTagline } from "./tagline.js";
|
|
6
7
|
import { resolveCliName } from "./cli-name.js";
|
|
8
|
+
function parseTaglineMode(value) {
|
|
9
|
+
if (value === "random" || value === "default" || value === "off") {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
function resolveTaglineMode(options) {
|
|
15
|
+
const explicit = parseTaglineMode(options.mode);
|
|
16
|
+
if (explicit) {
|
|
17
|
+
return explicit;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return parseTaglineMode(loadConfig().cli?.banner?.taglineMode);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
7
26
|
let bannerEmitted = false;
|
|
8
27
|
const graphemeSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl
|
|
9
28
|
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
@@ -23,7 +42,7 @@ const hasVersionFlag = (argv) => argv.some((arg) => arg === "--version" || arg =
|
|
|
23
42
|
export function formatCliBannerLine(version, options = {}) {
|
|
24
43
|
const commit = options.commit ?? resolveCommitHash({ env: options.env });
|
|
25
44
|
const commitLabel = commit ?? "unknown";
|
|
26
|
-
const tagline = pickTagline(options);
|
|
45
|
+
const tagline = pickTagline({ ...options, mode: resolveTaglineMode(options) });
|
|
27
46
|
const rich = options.richTty ?? isRich();
|
|
28
47
|
const cliName = resolveCliName(options.argv ?? process.argv);
|
|
29
48
|
const title = cliName === "poolbot" ? "🎱 Pool Bot" : "🎱 Pool Bot";
|