@poolzin/pool-bot 2026.3.11 → 2026.3.14

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 (195) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/agents/checkpoint-manager.js +291 -0
  4. package/dist/agents/poolbot-tools.js +5 -0
  5. package/dist/agents/subagent-announce-reliability.js +160 -0
  6. package/dist/agents/tool-result-truncation.js +299 -0
  7. package/dist/agents/tools/nodes-file-tool.js +197 -0
  8. package/dist/build-info.json +3 -3
  9. package/dist/cli/config-cli.js +60 -0
  10. package/dist/cron/cron-improvements.js +195 -0
  11. package/dist/discord/discord-improvements.js +167 -0
  12. package/dist/gateway/auth-rate-limit.js +19 -0
  13. package/dist/gateway/auth.js +41 -0
  14. package/dist/gateway/gateway-improvements.js +294 -0
  15. package/dist/gateway/node-command-policy.js +7 -2
  16. package/dist/infra/net/ssrf.js +15 -2
  17. package/dist/infra/shell-security.js +201 -0
  18. package/dist/memory/memory-improvements.js +239 -0
  19. package/dist/node-host/runner.js +146 -79
  20. package/dist/security/prototype-pollution.js +141 -0
  21. package/dist/security/webhook-security.js +253 -0
  22. package/dist/shared/net/ip.js +52 -1
  23. package/dist/slack/slack-improvements.js +225 -0
  24. package/dist/telegram/telegram-improvements.js +220 -0
  25. package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
  26. package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
  27. package/docs/competitive-analysis.md +421 -0
  28. package/docs/implementation-analysis.md +393 -0
  29. package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
  30. package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
  31. package/docs/refactor/plugin-development-guide.md +281 -0
  32. package/extensions/agency-agents/README.md +301 -0
  33. package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
  34. package/extensions/agency-agents/agents/README.md +602 -0
  35. package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
  36. package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
  37. package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
  38. package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
  39. package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
  40. package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
  41. package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
  42. package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
  43. package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
  44. package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
  45. package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
  46. package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
  47. package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
  48. package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
  49. package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
  50. package/extensions/agency-agents/agents/examples/README.md +48 -0
  51. package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
  52. package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
  53. package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
  54. package/extensions/agency-agents/agents/integrations/README.md +117 -0
  55. package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
  56. package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
  57. package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
  58. package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
  59. package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
  60. package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
  61. package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
  62. package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
  63. package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
  64. package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
  65. package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
  66. package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
  67. package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
  68. package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
  69. package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
  70. package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
  71. package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
  72. package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
  73. package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
  74. package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
  75. package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
  76. package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
  77. package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
  78. package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
  79. package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
  80. package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
  81. package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
  82. package/extensions/agency-agents/agents/scripts/install.sh +465 -0
  83. package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
  84. package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
  85. package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
  86. package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
  87. package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
  88. package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
  89. package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
  90. package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
  91. package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
  92. package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
  93. package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
  94. package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
  95. package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
  96. package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
  97. package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
  98. package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
  99. package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
  100. package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
  101. package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
  102. package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
  103. package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
  104. package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
  105. package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
  106. package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
  107. package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
  108. package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
  109. package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
  110. package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
  111. package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
  112. package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
  113. package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
  114. package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
  115. package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
  116. package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
  117. package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
  118. package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
  119. package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
  120. package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
  121. package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
  122. package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
  123. package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
  124. package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
  125. package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
  126. package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
  127. package/extensions/agency-agents/index.ts +733 -0
  128. package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
  129. package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
  130. package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
  131. package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
  132. package/extensions/agency-agents/node_modules/.bin/vite +21 -0
  133. package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
  134. package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
  135. package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  136. package/extensions/agency-agents/package.json +25 -0
  137. package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
  138. package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
  139. package/extensions/agency-agents/src/types.ts +147 -0
  140. package/extensions/agency-agents/vitest.config.ts +8 -0
  141. package/extensions/hexstrike-ai/README.md +98 -0
  142. package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
  143. package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
  144. package/extensions/hexstrike-ai/package.json +29 -0
  145. package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
  146. package/extensions/hexstrike-ai/src/client.ts +91 -0
  147. package/extensions/hexstrike-ai/src/index.ts +170 -0
  148. package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
  149. package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
  150. package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
  151. package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
  152. package/extensions/hexstrike-ai/tsconfig.json +20 -0
  153. package/extensions/hexstrike-bridge/package.json +1 -1
  154. package/extensions/hexstrike-bridge/poolbot.plugin.json +23 -0
  155. package/extensions/mcp-server/poolbot.plugin.json +10 -0
  156. package/extensions/page-agent/README.md +159 -0
  157. package/extensions/page-agent/index.ts +595 -0
  158. package/extensions/page-agent/node_modules/.bin/jiti +21 -0
  159. package/extensions/page-agent/node_modules/.bin/playwright +21 -0
  160. package/extensions/page-agent/node_modules/.bin/tsc +21 -0
  161. package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
  162. package/extensions/page-agent/node_modules/.bin/tsx +21 -0
  163. package/extensions/page-agent/node_modules/.bin/vitest +21 -0
  164. package/extensions/page-agent/node_modules/.bin/yaml +21 -0
  165. package/extensions/page-agent/package.json +43 -0
  166. package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
  167. package/extensions/page-agent/src/PageAgentService.ts +636 -0
  168. package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
  169. package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
  170. package/extensions/page-agent/src/index.ts +20 -0
  171. package/extensions/page-agent/src/tools.test.ts +231 -0
  172. package/extensions/page-agent/src/tools.ts +167 -0
  173. package/extensions/page-agent/src/types.ts +198 -0
  174. package/extensions/template/README.md +101 -0
  175. package/extensions/template/index.ts +38 -0
  176. package/extensions/template/package.json +15 -0
  177. package/extensions/template/poolbot.plugin.json +10 -0
  178. package/extensions/xyops/README.md +227 -0
  179. package/extensions/xyops/index.ts +342 -0
  180. package/extensions/xyops/node_modules/.bin/jiti +21 -0
  181. package/extensions/xyops/node_modules/.bin/tsc +21 -0
  182. package/extensions/xyops/node_modules/.bin/tsserver +21 -0
  183. package/extensions/xyops/node_modules/.bin/tsx +21 -0
  184. package/extensions/xyops/node_modules/.bin/vitest +21 -0
  185. package/extensions/xyops/node_modules/.bin/yaml +21 -0
  186. package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  187. package/extensions/xyops/package.json +39 -0
  188. package/extensions/xyops/src/client.test.ts +467 -0
  189. package/extensions/xyops/src/client.ts +157 -0
  190. package/extensions/xyops/src/types.ts +147 -0
  191. package/extensions/xyops/vitest.config.ts +8 -0
  192. package/package.json +1 -1
  193. package/extensions/mavalie/README.md +0 -97
  194. package/extensions/mavalie/package.json +0 -15
  195. package/extensions/mavalie/src/index.ts +0 -62
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Webhook Security Hardening
3
+ *
4
+ * Implements:
5
+ * - Auth before body parsing (fail-closed)
6
+ * - Rate limiting per-path
7
+ * - Replay dedupe with signature timestamps
8
+ * - Constant-time secret comparison
9
+ *
10
+ * OpenClaw #32384, #30951
11
+ */
12
+ import { createHmac, timingSafeEqual } from "node:crypto";
13
+ /**
14
+ * Webhook signature header names
15
+ */
16
+ export const WEBHOOK_SIGNATURE_HEADERS = {
17
+ SIGNATURE: "x-webhook-signature",
18
+ TIMESTAMP: "x-webhook-timestamp",
19
+ ID: "x-webhook-id",
20
+ };
21
+ /**
22
+ * Replay protection window in milliseconds (5 minutes)
23
+ */
24
+ export const REPLAY_PROTECTION_WINDOW_MS = 5 * 60 * 1000;
25
+ /**
26
+ * Rate limit for webhooks per path per minute
27
+ */
28
+ export const WEBHOOK_RATE_LIMIT_PER_MINUTE = 60;
29
+ /**
30
+ * Cache for seen webhook IDs (replay protection)
31
+ */
32
+ const seenWebhookIds = new Map();
33
+ /**
34
+ * Prune old webhook IDs from cache every 10 minutes
35
+ */
36
+ setInterval(() => {
37
+ const now = Date.now();
38
+ for (const [id, timestamp] of seenWebhookIds.entries()) {
39
+ if (now - timestamp > REPLAY_PROTECTION_WINDOW_MS * 2) {
40
+ seenWebhookIds.delete(id);
41
+ }
42
+ }
43
+ }, 10 * 60 * 1000).unref();
44
+ /**
45
+ * Compute HMAC-SHA256 signature for webhook payload.
46
+ */
47
+ export function computeWebhookSignature(params) {
48
+ const { payload, secret, timestamp } = params;
49
+ const payloadStr = typeof payload === "string" ? payload : payload.toString("utf8");
50
+ const signedContent = `${timestamp}.${payloadStr}`;
51
+ return createHmac("sha256", secret).update(signedContent).digest("hex");
52
+ }
53
+ /**
54
+ * Verify webhook signature with constant-time comparison.
55
+ * OpenClaw #32384 - prevents timing attacks on signature validation.
56
+ */
57
+ export function verifyWebhookSignature(params) {
58
+ const { signature, payload, secret, timestamp, toleranceMs = 5000 } = params;
59
+ // Check timestamp tolerance (prevent old replay attacks)
60
+ const now = Date.now();
61
+ const timestampAge = Math.abs(now - timestamp);
62
+ if (timestampAge > toleranceMs) {
63
+ return {
64
+ valid: false,
65
+ reason: `Signature timestamp outside tolerance (${timestampAge}ms > ${toleranceMs}ms)`,
66
+ };
67
+ }
68
+ // Compute expected signature
69
+ const expectedSignature = computeWebhookSignature({ payload, secret, timestamp });
70
+ // Constant-time comparison to prevent timing attacks
71
+ try {
72
+ const signatureBuffer = Buffer.from(signature, "hex");
73
+ const expectedBuffer = Buffer.from(expectedSignature, "hex");
74
+ if (signatureBuffer.length !== expectedBuffer.length) {
75
+ return { valid: false, reason: "Signature length mismatch" };
76
+ }
77
+ if (!timingSafeEqual(signatureBuffer, expectedBuffer)) {
78
+ return { valid: false, reason: "Signature mismatch" };
79
+ }
80
+ return { valid: true };
81
+ }
82
+ catch {
83
+ return { valid: false, reason: "Invalid signature format" };
84
+ }
85
+ }
86
+ /**
87
+ * Extract webhook signature from request headers.
88
+ */
89
+ export function extractWebhookSignature(req) {
90
+ const signatureHeader = req.headers[WEBHOOK_SIGNATURE_HEADERS.SIGNATURE.toLowerCase()];
91
+ const timestampHeader = req.headers[WEBHOOK_SIGNATURE_HEADERS.TIMESTAMP.toLowerCase()];
92
+ const idHeader = req.headers[WEBHOOK_SIGNATURE_HEADERS.ID.toLowerCase()];
93
+ if (!signatureHeader || !timestampHeader) {
94
+ return null;
95
+ }
96
+ const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
97
+ const timestampStr = Array.isArray(timestampHeader) ? timestampHeader[0] : timestampHeader;
98
+ const timestamp = Number.parseInt(timestampStr, 10);
99
+ if (isNaN(timestamp)) {
100
+ return null;
101
+ }
102
+ const id = idHeader ? (Array.isArray(idHeader) ? idHeader[0] : idHeader) : undefined;
103
+ return { signature, timestamp, id };
104
+ }
105
+ /**
106
+ * Check for webhook replay attacks using signature ID and timestamp.
107
+ * OpenClaw #32384
108
+ */
109
+ export function checkWebhookReplay(params) {
110
+ const { id, timestamp } = params;
111
+ // If no ID provided, use timestamp as fallback (less secure)
112
+ const replayKey = id ?? `ts:${timestamp}`;
113
+ const now = Date.now();
114
+ // Check if we've seen this webhook before
115
+ const seenAt = seenWebhookIds.get(replayKey);
116
+ if (seenAt !== undefined) {
117
+ return {
118
+ valid: false,
119
+ reason: `Duplicate webhook detected (ID: ${replayKey}, first seen: ${new Date(seenAt).toISOString()})`,
120
+ };
121
+ }
122
+ // Check timestamp window
123
+ if (now - timestamp > REPLAY_PROTECTION_WINDOW_MS) {
124
+ return {
125
+ valid: false,
126
+ reason: `Webhook timestamp too old (${Math.floor((now - timestamp) / 1000)}s > ${REPLAY_PROTECTION_WINDOW_MS / 1000}s)`,
127
+ };
128
+ }
129
+ // Mark as seen
130
+ seenWebhookIds.set(replayKey, now);
131
+ return { valid: true };
132
+ }
133
+ /**
134
+ * Rate limiter for webhooks (per-path sliding window)
135
+ */
136
+ const webhookRateLimits = new Map();
137
+ /**
138
+ * Check webhook rate limit for a given path.
139
+ */
140
+ export function checkWebhookRateLimit(params) {
141
+ const { path, limit = WEBHOOK_RATE_LIMIT_PER_MINUTE, windowMs = 60_000 } = params;
142
+ const now = Date.now();
143
+ const timestamps = webhookRateLimits.get(path) ?? [];
144
+ const recentTimestamps = timestamps.filter((ts) => now - ts < windowMs);
145
+ if (recentTimestamps.length >= limit) {
146
+ const oldestTimestamp = recentTimestamps[0];
147
+ const retryAfterMs = windowMs - (now - oldestTimestamp);
148
+ return { allowed: false, retryAfterMs };
149
+ }
150
+ recentTimestamps.push(now);
151
+ webhookRateLimits.set(path, recentTimestamps);
152
+ return { allowed: true };
153
+ }
154
+ /**
155
+ * Complete webhook authentication middleware.
156
+ * Implements auth-before-body parsing (fail-closed).
157
+ * OpenClaw #32384
158
+ */
159
+ export async function authenticateWebhook(params) {
160
+ const { req, secret, path, toleranceMs } = params;
161
+ // Step 1: Check rate limit FIRST (before any parsing)
162
+ const rateLimitResult = checkWebhookRateLimit({ path });
163
+ if (!rateLimitResult.allowed) {
164
+ return {
165
+ ok: false,
166
+ reason: `Rate limit exceeded (${WEBHOOK_RATE_LIMIT_PER_MINUTE} req/min)`,
167
+ retryAfterMs: rateLimitResult.retryAfterMs,
168
+ };
169
+ }
170
+ // Step 2: Extract signature (headers only, no body parsing yet)
171
+ const sigData = extractWebhookSignature(req);
172
+ if (!sigData) {
173
+ return {
174
+ ok: false,
175
+ reason: "Missing webhook signature headers",
176
+ };
177
+ }
178
+ // Step 3: Check for replay attacks
179
+ const replayCheck = checkWebhookReplay({
180
+ id: sigData.id,
181
+ timestamp: sigData.timestamp,
182
+ });
183
+ if (!replayCheck.valid) {
184
+ return {
185
+ ok: false,
186
+ reason: replayCheck.reason,
187
+ };
188
+ }
189
+ // Step 4: Read body (now that auth checks passed)
190
+ // Note: Caller must ensure body is read as raw Buffer, not parsed JSON
191
+ const body = await readRawBody(req);
192
+ if (!body) {
193
+ return {
194
+ ok: false,
195
+ reason: "Empty webhook body",
196
+ };
197
+ }
198
+ // Step 5: Verify signature with constant-time comparison
199
+ const sigResult = verifyWebhookSignature({
200
+ signature: sigData.signature,
201
+ payload: body,
202
+ secret,
203
+ timestamp: sigData.timestamp,
204
+ toleranceMs,
205
+ });
206
+ if (!sigResult.valid) {
207
+ return {
208
+ ok: false,
209
+ reason: sigResult.reason,
210
+ };
211
+ }
212
+ return { ok: true, method: "signature", path };
213
+ }
214
+ /**
215
+ * Read raw body from IncomingMessage as Buffer.
216
+ * Must be called before any body parsing middleware.
217
+ */
218
+ function readRawBody(req) {
219
+ return new Promise((resolve) => {
220
+ const chunks = [];
221
+ req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
222
+ req.on("end", () => resolve(Buffer.concat(chunks)));
223
+ req.on("error", () => resolve(Buffer.alloc(0)));
224
+ });
225
+ }
226
+ /**
227
+ * Middleware factory for Express-style frameworks.
228
+ * Enforces auth-before-body parsing.
229
+ */
230
+ export function createWebhookAuthMiddleware(params) {
231
+ const { secret, toleranceMs } = params;
232
+ return async (req, res, next) => {
233
+ const url = req.url ?? "/";
234
+ const path = url.split("?")[0];
235
+ const result = await authenticateWebhook({
236
+ req,
237
+ secret,
238
+ path,
239
+ toleranceMs,
240
+ });
241
+ if (!result.ok) {
242
+ res.statusCode = result.retryAfterMs ? 429 : 401;
243
+ if (result.retryAfterMs) {
244
+ res.setHeader("Retry-After", String(Math.ceil(result.retryAfterMs / 1000)));
245
+ }
246
+ res.setHeader("Content-Type", "application/json");
247
+ res.end(JSON.stringify({ error: result.reason }));
248
+ return;
249
+ }
250
+ // Auth passed - continue to handler
251
+ next();
252
+ };
253
+ }
@@ -22,6 +22,34 @@ const BLOCKED_IPV6_SPECIAL_USE_RANGES = new Set([
22
22
  "uniqueLocal",
23
23
  "multicast",
24
24
  ]);
25
+ // IPv6 transition mechanisms that embed IPv4 addresses (SSRF bypass vectors)
26
+ const IPV6_TRANSITION_RANGES = [
27
+ {
28
+ name: "NAT64",
29
+ prefix: [ipaddr.IPv6.parse("64:ff9b::"), 96],
30
+ description: "NAT64 prefix (RFC 6146)",
31
+ },
32
+ {
33
+ name: "NAT64-1",
34
+ prefix: [ipaddr.IPv6.parse("64:ff9b:1::"), 48],
35
+ description: "NAT64 prefix variant (RFC 8215)",
36
+ },
37
+ {
38
+ name: "6to4",
39
+ prefix: [ipaddr.IPv6.parse("2002::"), 16],
40
+ description: "6to4 transition (RFC 3056)",
41
+ },
42
+ {
43
+ name: "Teredo",
44
+ prefix: [ipaddr.IPv6.parse("2001::"), 32],
45
+ description: "Teredo tunneling (RFC 4380)",
46
+ },
47
+ {
48
+ name: "ISATAP",
49
+ prefix: [ipaddr.IPv6.parse("fe80::"), 10],
50
+ description: "ISATAP link-local (RFC 5214)",
51
+ },
52
+ ];
25
53
  const RFC2544_BENCHMARK_PREFIX = [ipaddr.IPv4.parse("198.18.0.0"), 15];
26
54
  const EMBEDDED_IPV4_SENTINEL_RULES = [
27
55
  {
@@ -201,10 +229,26 @@ export function isPrivateOrLoopbackIpAddress(raw) {
201
229
  }
202
230
  return isBlockedSpecialUseIpv6Address(normalized);
203
231
  }
232
+ /**
233
+ * Check if an IPv6 address uses transition mechanisms that embed IPv4 addresses.
234
+ * These are SSRF bypass vectors (NAT64, 6to4, Teredo, ISATAP).
235
+ */
236
+ export function isIpv6TransitionAddress(address) {
237
+ for (const range of IPV6_TRANSITION_RANGES) {
238
+ if (address.match(range.prefix)) {
239
+ return true;
240
+ }
241
+ }
242
+ return false;
243
+ }
204
244
  export function isBlockedSpecialUseIpv6Address(address) {
205
245
  if (BLOCKED_IPV6_SPECIAL_USE_RANGES.has(address.range())) {
206
246
  return true;
207
247
  }
248
+ // Block IPv6 transition mechanisms (SSRF bypass vectors)
249
+ if (isIpv6TransitionAddress(address)) {
250
+ return true;
251
+ }
208
252
  // ipaddr.js does not classify deprecated site-local fec0::/10 as private.
209
253
  return (address.parts[0] & 0xffc0) === 0xfec0;
210
254
  }
@@ -222,8 +266,15 @@ export function isCarrierGradeNatIpv4Address(raw) {
222
266
  }
223
267
  return parsed.range() === "carrierGradeNat";
224
268
  }
269
+ /**
270
+ * Check if an IPv4 address is in the RFC2544 benchmark range (198.18.0.0/15).
271
+ * This range is used for network device benchmarking and should be blocked by default.
272
+ */
273
+ export function isRfc2544BenchmarkAddress(address) {
274
+ return address.match(RFC2544_BENCHMARK_PREFIX);
275
+ }
225
276
  export function isBlockedSpecialUseIpv4Address(address, options = {}) {
226
- const inRfc2544BenchmarkRange = address.match(RFC2544_BENCHMARK_PREFIX);
277
+ const inRfc2544BenchmarkRange = isRfc2544BenchmarkAddress(address);
227
278
  if (inRfc2544BenchmarkRange && options.allowRfc2544BenchmarkRange === true) {
228
279
  return false;
229
280
  }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Slack Channel Improvements
3
+ *
4
+ * Implements OpenClaw improvements:
5
+ * - Native streaming (chat.startStream/appendStream/stopStream)
6
+ * - Thread session isolation
7
+ * - User-token resolution for multi-account
8
+ * - Socket Mode reconnect reliability
9
+ *
10
+ * OpenClaw #32384, #30951
11
+ */
12
+ export const DEFAULT_SLACK_STREAMING_CONFIG = {
13
+ enabled: true,
14
+ streamInThreads: true,
15
+ streamInChannels: false, // Don't spam channels by default
16
+ chunkDelayMs: 300,
17
+ minChunkSize: 100,
18
+ maxStreamDurationMs: 5 * 60 * 1000, // 5 minutes
19
+ };
20
+ const streamingSessions = new Map();
21
+ /**
22
+ * Start a new streaming session
23
+ */
24
+ export function startSlackStreamingSession(params) {
25
+ const { channelId, threadTs } = params;
26
+ const key = threadTs ? `${channelId}:${threadTs}` : channelId;
27
+ const session = {
28
+ channelId,
29
+ threadTs,
30
+ startedAt: Date.now(),
31
+ lastChunkAt: Date.now(),
32
+ chunkCount: 0,
33
+ };
34
+ streamingSessions.set(key, session);
35
+ return session;
36
+ }
37
+ /**
38
+ * Update streaming session with new chunk
39
+ */
40
+ export function appendSlackStreamingChunk(params) {
41
+ const { channelId, threadTs } = params;
42
+ const key = threadTs ? `${channelId}:${threadTs}` : channelId;
43
+ const session = streamingSessions.get(key);
44
+ if (!session) {
45
+ return null;
46
+ }
47
+ session.chunkCount += 1;
48
+ session.lastChunkAt = Date.now();
49
+ return session;
50
+ }
51
+ /**
52
+ * End streaming session
53
+ */
54
+ export function endSlackStreamingSession(params) {
55
+ const { channelId, threadTs } = params;
56
+ const key = threadTs ? `${channelId}:${threadTs}` : channelId;
57
+ const session = streamingSessions.get(key);
58
+ if (!session) {
59
+ return null;
60
+ }
61
+ streamingSessions.delete(key);
62
+ return session;
63
+ }
64
+ /**
65
+ * Get active streaming session
66
+ */
67
+ export function getSlackStreamingSession(params) {
68
+ const { channelId, threadTs } = params;
69
+ const key = threadTs ? `${channelId}:${threadTs}` : channelId;
70
+ return streamingSessions.get(key) ?? null;
71
+ }
72
+ export const DEFAULT_THREAD_SESSION_ISOLATION_CONFIG = {
73
+ enabled: true,
74
+ includeChannelId: true,
75
+ includeThreadTs: true,
76
+ separator: ":",
77
+ };
78
+ /**
79
+ * Build session key with thread isolation
80
+ */
81
+ export function buildSlackSessionKey(params) {
82
+ const { channelId, threadTs, userId, config } = params;
83
+ const parts = [];
84
+ if (config.includeChannelId) {
85
+ parts.push(channelId);
86
+ }
87
+ if (config.includeThreadTs && threadTs) {
88
+ parts.push(config.separator, "thread", config.separator, threadTs);
89
+ }
90
+ if (userId) {
91
+ parts.push(config.separator, "user", config.separator, userId);
92
+ }
93
+ return parts.join("");
94
+ }
95
+ /**
96
+ * Parse session key to extract thread info
97
+ */
98
+ export function parseSlackSessionKey(sessionKey) {
99
+ const parts = sessionKey.split(":");
100
+ const result = {
101
+ channelId: parts[0],
102
+ };
103
+ for (let i = 1; i < parts.length; i += 2) {
104
+ const key = parts[i];
105
+ const value = parts[i + 1];
106
+ if (key === "thread") {
107
+ result.threadTs = value;
108
+ }
109
+ else if (key === "user") {
110
+ result.userId = value;
111
+ }
112
+ }
113
+ return result;
114
+ }
115
+ const userTokenCache = new Map();
116
+ /**
117
+ * Cache user token resolution
118
+ */
119
+ export function cacheUserTokenResolution(resolution) {
120
+ const key = `${resolution.userId}:${resolution.teamId}`;
121
+ userTokenCache.set(key, resolution);
122
+ }
123
+ /**
124
+ * Get cached user token resolution
125
+ */
126
+ export function getCachedUserTokenResolution(params) {
127
+ const { userId, teamId } = params;
128
+ const key = `${userId}:${teamId}`;
129
+ const cached = userTokenCache.get(key);
130
+ if (!cached) {
131
+ return null;
132
+ }
133
+ // Check if expired
134
+ if (cached.expiresAt && Date.now() > cached.expiresAt) {
135
+ userTokenCache.delete(key);
136
+ return null;
137
+ }
138
+ return cached;
139
+ }
140
+ /**
141
+ * Clear user token cache
142
+ */
143
+ export function clearUserTokenCache() {
144
+ userTokenCache.clear();
145
+ }
146
+ export const DEFAULT_SOCKET_MODE_RECONNECT_CONFIG = {
147
+ enabled: true,
148
+ maxReconnectAttempts: 10,
149
+ baseReconnectDelayMs: 1000,
150
+ maxReconnectDelayMs: 60_000, // 1 minute
151
+ backoffMultiplier: 2,
152
+ reconnectOnErrorCodes: [
153
+ 1000, // Normal closure
154
+ 1001, // Going away
155
+ 1006, // Abnormal closure
156
+ 1012, // Service restart
157
+ 1013, // Try again later
158
+ 1014, // Bad gateway
159
+ ],
160
+ };
161
+ const socketModeStates = new Map();
162
+ /**
163
+ * Get or create Socket Mode state
164
+ */
165
+ export function getOrCreateSocketModeState(appId) {
166
+ const existing = socketModeStates.get(appId);
167
+ if (existing) {
168
+ return existing;
169
+ }
170
+ const newState = {
171
+ attempt: 0,
172
+ reconnecting: false,
173
+ connected: false,
174
+ };
175
+ socketModeStates.set(appId, newState);
176
+ return newState;
177
+ }
178
+ /**
179
+ * Record Socket Mode disconnection
180
+ */
181
+ export function recordSocketModeDisconnect(params) {
182
+ const { appId, errorCode, errorMessage, config } = params;
183
+ const state = getOrCreateSocketModeState(appId);
184
+ state.connected = false;
185
+ state.lastErrorCode = errorCode;
186
+ state.lastErrorMessage = errorMessage;
187
+ if (!config.enabled) {
188
+ return { canReconnect: false, delayMs: 0, shouldGiveUp: false };
189
+ }
190
+ // Check if error code warrants reconnection
191
+ if (errorCode && !config.reconnectOnErrorCodes.includes(errorCode)) {
192
+ state.reconnecting = false;
193
+ return { canReconnect: false, delayMs: 0, shouldGiveUp: true };
194
+ }
195
+ state.attempt += 1;
196
+ if (state.attempt > config.maxReconnectAttempts) {
197
+ state.reconnecting = false;
198
+ return { canReconnect: false, delayMs: 0, shouldGiveUp: true };
199
+ }
200
+ state.reconnecting = true;
201
+ // Calculate exponential backoff delay
202
+ const exponentialDelay = config.baseReconnectDelayMs * Math.pow(config.backoffMultiplier, state.attempt - 1);
203
+ const delayMs = Math.min(exponentialDelay, config.maxReconnectDelayMs);
204
+ return { canReconnect: true, delayMs, shouldGiveUp: false };
205
+ }
206
+ /**
207
+ * Record Socket Mode reconnection success
208
+ */
209
+ export function recordSocketModeReconnect(appId) {
210
+ const state = socketModeStates.get(appId);
211
+ if (state) {
212
+ state.attempt = 0;
213
+ state.reconnecting = false;
214
+ state.connected = true;
215
+ state.lastErrorCode = undefined;
216
+ state.lastErrorMessage = undefined;
217
+ }
218
+ }
219
+ /**
220
+ * Check if Socket Mode is connected
221
+ */
222
+ export function isSocketModeConnected(appId) {
223
+ const state = socketModeStates.get(appId);
224
+ return state?.connected ?? false;
225
+ }