@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.
- package/CHANGELOG.md +121 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/checkpoint-manager.js +291 -0
- package/dist/agents/poolbot-tools.js +5 -0
- package/dist/agents/subagent-announce-reliability.js +160 -0
- package/dist/agents/tool-result-truncation.js +299 -0
- package/dist/agents/tools/nodes-file-tool.js +197 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/config-cli.js +60 -0
- package/dist/cron/cron-improvements.js +195 -0
- package/dist/discord/discord-improvements.js +167 -0
- package/dist/gateway/auth-rate-limit.js +19 -0
- package/dist/gateway/auth.js +41 -0
- package/dist/gateway/gateway-improvements.js +294 -0
- package/dist/gateway/node-command-policy.js +7 -2
- package/dist/infra/net/ssrf.js +15 -2
- package/dist/infra/shell-security.js +201 -0
- package/dist/memory/memory-improvements.js +239 -0
- package/dist/node-host/runner.js +146 -79
- package/dist/security/prototype-pollution.js +141 -0
- package/dist/security/webhook-security.js +253 -0
- package/dist/shared/net/ip.js +52 -1
- package/dist/slack/slack-improvements.js +225 -0
- package/dist/telegram/telegram-improvements.js +220 -0
- package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
- package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
- package/docs/competitive-analysis.md +421 -0
- package/docs/implementation-analysis.md +393 -0
- package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
- package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
- package/docs/refactor/plugin-development-guide.md +281 -0
- package/extensions/agency-agents/README.md +301 -0
- package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
- package/extensions/agency-agents/agents/README.md +602 -0
- package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
- package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
- package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
- package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
- package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
- package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
- package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
- package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
- package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
- package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
- package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
- package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
- package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
- package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
- package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
- package/extensions/agency-agents/agents/examples/README.md +48 -0
- package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
- package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
- package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
- package/extensions/agency-agents/agents/integrations/README.md +117 -0
- package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
- package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
- package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
- package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
- package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
- package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
- package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
- package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
- package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
- package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
- package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
- package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
- package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
- package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
- package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
- package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
- package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
- package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
- package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
- package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
- package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
- package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
- package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
- package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
- package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
- package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
- package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
- package/extensions/agency-agents/agents/scripts/install.sh +465 -0
- package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
- package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
- package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
- package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
- package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
- package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
- package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
- package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
- package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
- package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
- package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
- package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
- package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
- package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
- package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
- package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
- package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
- package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
- package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
- package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
- package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
- package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
- package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
- package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
- package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
- package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
- package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
- package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
- package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
- package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
- package/extensions/agency-agents/index.ts +733 -0
- package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +21 -0
- package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
- package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
- package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/agency-agents/package.json +25 -0
- package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
- package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
- package/extensions/agency-agents/src/types.ts +147 -0
- package/extensions/agency-agents/vitest.config.ts +8 -0
- package/extensions/hexstrike-ai/README.md +98 -0
- package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
- package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
- package/extensions/hexstrike-ai/package.json +29 -0
- package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
- package/extensions/hexstrike-ai/src/client.ts +91 -0
- package/extensions/hexstrike-ai/src/index.ts +170 -0
- package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
- package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
- package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
- package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
- package/extensions/hexstrike-ai/tsconfig.json +20 -0
- package/extensions/hexstrike-bridge/package.json +1 -1
- package/extensions/hexstrike-bridge/poolbot.plugin.json +23 -0
- package/extensions/mcp-server/poolbot.plugin.json +10 -0
- package/extensions/page-agent/README.md +159 -0
- package/extensions/page-agent/index.ts +595 -0
- package/extensions/page-agent/node_modules/.bin/jiti +21 -0
- package/extensions/page-agent/node_modules/.bin/playwright +21 -0
- package/extensions/page-agent/node_modules/.bin/tsc +21 -0
- package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
- package/extensions/page-agent/node_modules/.bin/tsx +21 -0
- package/extensions/page-agent/node_modules/.bin/vitest +21 -0
- package/extensions/page-agent/node_modules/.bin/yaml +21 -0
- package/extensions/page-agent/package.json +43 -0
- package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
- package/extensions/page-agent/src/PageAgentService.ts +636 -0
- package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
- package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
- package/extensions/page-agent/src/index.ts +20 -0
- package/extensions/page-agent/src/tools.test.ts +231 -0
- package/extensions/page-agent/src/tools.ts +167 -0
- package/extensions/page-agent/src/types.ts +198 -0
- package/extensions/template/README.md +101 -0
- package/extensions/template/index.ts +38 -0
- package/extensions/template/package.json +15 -0
- package/extensions/template/poolbot.plugin.json +10 -0
- package/extensions/xyops/README.md +227 -0
- package/extensions/xyops/index.ts +342 -0
- package/extensions/xyops/node_modules/.bin/jiti +21 -0
- package/extensions/xyops/node_modules/.bin/tsc +21 -0
- package/extensions/xyops/node_modules/.bin/tsserver +21 -0
- package/extensions/xyops/node_modules/.bin/tsx +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +21 -0
- package/extensions/xyops/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/xyops/package.json +39 -0
- package/extensions/xyops/src/client.test.ts +467 -0
- package/extensions/xyops/src/client.ts +157 -0
- package/extensions/xyops/src/types.ts +147 -0
- package/extensions/xyops/vitest.config.ts +8 -0
- package/package.json +1 -1
- package/extensions/mavalie/README.md +0 -97
- package/extensions/mavalie/package.json +0 -15
- package/extensions/mavalie/src/index.ts +0 -62
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway & Auth Upgrades
|
|
3
|
+
*
|
|
4
|
+
* Implements OpenClaw improvements:
|
|
5
|
+
* - Device-auth v2 with nonce-based signing
|
|
6
|
+
* - Trusted-proxy mode with loopback allowance
|
|
7
|
+
* - Control UI CORS with wildcard support
|
|
8
|
+
* - Security headers (HSTS, Permissions-Policy)
|
|
9
|
+
* - Rate limiting for sensitive endpoints
|
|
10
|
+
*
|
|
11
|
+
* OpenClaw #32384, #30951
|
|
12
|
+
*/
|
|
13
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
14
|
+
export const DEFAULT_DEVICE_AUTH_V2_CONFIG = {
|
|
15
|
+
enabled: true,
|
|
16
|
+
nonceLength: 32,
|
|
17
|
+
nonceTtlMs: 5 * 60 * 1000, // 5 minutes
|
|
18
|
+
signatureAlgorithm: "sha256",
|
|
19
|
+
requireNonce: false, // Allow v1 for backward compatibility
|
|
20
|
+
allowV1Fallback: true,
|
|
21
|
+
};
|
|
22
|
+
const nonceStore = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Cleanup expired nonces every 10 minutes
|
|
25
|
+
*/
|
|
26
|
+
setInterval(() => {
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const maxAge = 10 * 60 * 1000; // 10 minutes
|
|
29
|
+
for (const [deviceId, entry] of nonceStore.entries()) {
|
|
30
|
+
if (now - entry.createdAt > maxAge) {
|
|
31
|
+
nonceStore.delete(deviceId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, 10 * 60 * 1000).unref();
|
|
35
|
+
/**
|
|
36
|
+
* Generate a new nonce for device-auth v2
|
|
37
|
+
*/
|
|
38
|
+
export function generateNonce(config = DEFAULT_DEVICE_AUTH_V2_CONFIG) {
|
|
39
|
+
return randomBytes(config.nonceLength).toString("hex");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Store nonce for device-auth v2
|
|
43
|
+
*/
|
|
44
|
+
export function storeNonce(params) {
|
|
45
|
+
const { deviceId, nonce, config } = params;
|
|
46
|
+
nonceStore.set(deviceId, {
|
|
47
|
+
nonce,
|
|
48
|
+
createdAt: Date.now(),
|
|
49
|
+
used: false,
|
|
50
|
+
});
|
|
51
|
+
// Auto-expire after TTL
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
const entry = nonceStore.get(deviceId);
|
|
54
|
+
if (entry && entry.nonce === nonce) {
|
|
55
|
+
nonceStore.delete(deviceId);
|
|
56
|
+
}
|
|
57
|
+
}, config.nonceTtlMs).unref();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Verify nonce for device-auth v2
|
|
61
|
+
*/
|
|
62
|
+
export function verifyNonce(params) {
|
|
63
|
+
const { deviceId, nonce, config } = params;
|
|
64
|
+
const entry = nonceStore.get(deviceId);
|
|
65
|
+
if (!entry) {
|
|
66
|
+
return { valid: false, reason: "Nonce not found or expired" };
|
|
67
|
+
}
|
|
68
|
+
if (entry.used) {
|
|
69
|
+
return { valid: false, reason: "Nonce already used (replay attempt)" };
|
|
70
|
+
}
|
|
71
|
+
if (Date.now() - entry.createdAt > config.nonceTtlMs) {
|
|
72
|
+
nonceStore.delete(deviceId);
|
|
73
|
+
return { valid: false, reason: "Nonce expired" };
|
|
74
|
+
}
|
|
75
|
+
if (!timingSafeEqual(Buffer.from(entry.nonce), Buffer.from(nonce))) {
|
|
76
|
+
return { valid: false, reason: "Nonce mismatch" };
|
|
77
|
+
}
|
|
78
|
+
// Mark as used
|
|
79
|
+
entry.used = true;
|
|
80
|
+
return { valid: true };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build device-auth v2 payload with nonce
|
|
84
|
+
*/
|
|
85
|
+
export function buildDeviceAuthV2Payload(params) {
|
|
86
|
+
const { deviceId, clientId, clientMode, role, scopes, signedAtMs, token, nonce } = params;
|
|
87
|
+
const scopesStr = scopes.join(",");
|
|
88
|
+
return `v2|${deviceId}|${clientId}|${clientMode}|${role}|${scopesStr}|${signedAtMs}|${token}|${nonce}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Sign device-auth v2 payload
|
|
92
|
+
*/
|
|
93
|
+
export function signDeviceAuthV2Payload(params) {
|
|
94
|
+
const { payload, secret, algorithm } = params;
|
|
95
|
+
return createHmac(algorithm, secret).update(payload).digest("hex");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verify device-auth v2 signature
|
|
99
|
+
*/
|
|
100
|
+
export function verifyDeviceAuthV2Signature(params) {
|
|
101
|
+
const { payload, signature, secret, algorithm } = params;
|
|
102
|
+
try {
|
|
103
|
+
const expectedSignature = signDeviceAuthV2Payload({ payload, secret, algorithm });
|
|
104
|
+
const signatureBuffer = Buffer.from(signature, "hex");
|
|
105
|
+
const expectedBuffer = Buffer.from(expectedSignature, "hex");
|
|
106
|
+
if (signatureBuffer.length !== expectedBuffer.length) {
|
|
107
|
+
return { valid: false, reason: "Signature length mismatch" };
|
|
108
|
+
}
|
|
109
|
+
if (!timingSafeEqual(signatureBuffer, expectedBuffer)) {
|
|
110
|
+
return { valid: false, reason: "Signature mismatch" };
|
|
111
|
+
}
|
|
112
|
+
return { valid: true };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return { valid: false, reason: "Invalid signature format" };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export const DEFAULT_TRUSTED_PROXY_CONFIG = {
|
|
119
|
+
enabled: true,
|
|
120
|
+
trustedProxies: ["127.0.0.1", "::1"],
|
|
121
|
+
userHeader: "X-Forwarded-User",
|
|
122
|
+
requiredHeaders: [],
|
|
123
|
+
allowUsers: [],
|
|
124
|
+
allowLoopback: true,
|
|
125
|
+
autoApproveLoopbackScopes: true,
|
|
126
|
+
safeLoopbackScopes: ["read", "read:config", "read:status", "write", "execute", "execute:tool"],
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Check if IP is a trusted proxy
|
|
130
|
+
*/
|
|
131
|
+
export function isTrustedProxy(ip, config = DEFAULT_TRUSTED_PROXY_CONFIG) {
|
|
132
|
+
if (!config.enabled) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return config.trustedProxies.includes(ip);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if IP is loopback
|
|
139
|
+
*/
|
|
140
|
+
export function isLoopbackIp(ip) {
|
|
141
|
+
return ip === "127.0.0.1" || ip === "::1" || ip === "localhost";
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Validate scope upgrade for loopback
|
|
145
|
+
*/
|
|
146
|
+
export function validateLoopbackScopeUpgrade(params) {
|
|
147
|
+
const { currentScopes, requestedScopes, config } = params;
|
|
148
|
+
if (!config.autoApproveLoopbackScopes) {
|
|
149
|
+
return { allowed: false, reason: "Auto-approve for loopback is disabled" };
|
|
150
|
+
}
|
|
151
|
+
const currentSet = new Set(currentScopes.map((s) => s.trim().toLowerCase()));
|
|
152
|
+
const newScopes = requestedScopes.filter((s) => !currentSet.has(s.trim().toLowerCase()));
|
|
153
|
+
const safeScopes = new Set(config.safeLoopbackScopes.map((s) => s.trim().toLowerCase()));
|
|
154
|
+
for (const scope of newScopes) {
|
|
155
|
+
const normalized = scope.trim().toLowerCase();
|
|
156
|
+
if (!safeScopes.has(normalized) &&
|
|
157
|
+
!normalized.startsWith("read:") &&
|
|
158
|
+
!normalized.startsWith("execute:")) {
|
|
159
|
+
return { allowed: false, reason: `Scope "${scope}" requires explicit approval` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { allowed: true };
|
|
163
|
+
}
|
|
164
|
+
export const DEFAULT_CONTROL_UI_CORS_CONFIG = {
|
|
165
|
+
enabled: true,
|
|
166
|
+
allowedOrigins: ["*"], // Wildcard by default for local dev
|
|
167
|
+
allowedMethods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
168
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-Request-ID"],
|
|
169
|
+
exposedHeaders: ["X-Request-ID", "X-RateLimit-Remaining"],
|
|
170
|
+
maxAgeSeconds: 86400, // 24 hours
|
|
171
|
+
allowCredentials: false,
|
|
172
|
+
wildcardSubdomains: true,
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Check if origin is allowed
|
|
176
|
+
*/
|
|
177
|
+
export function isOriginAllowed(origin, config = DEFAULT_CONTROL_UI_CORS_CONFIG) {
|
|
178
|
+
if (!config.enabled || !origin) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
// Wildcard allows all
|
|
182
|
+
if (config.allowedOrigins.includes("*")) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
// Check exact match
|
|
186
|
+
if (config.allowedOrigins.includes(origin)) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
// Check wildcard subdomains
|
|
190
|
+
if (config.wildcardSubdomains) {
|
|
191
|
+
for (const allowed of config.allowedOrigins) {
|
|
192
|
+
if (allowed.startsWith("*.")) {
|
|
193
|
+
const suffix = allowed.slice(1);
|
|
194
|
+
if (origin.endsWith(suffix)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Generate CORS headers
|
|
204
|
+
*/
|
|
205
|
+
export function generateCorsHeaders(params) {
|
|
206
|
+
const { origin, config } = params;
|
|
207
|
+
const headers = {};
|
|
208
|
+
if (!config.enabled) {
|
|
209
|
+
return headers;
|
|
210
|
+
}
|
|
211
|
+
const allowedOrigin = isOriginAllowed(origin, config) ? (origin ?? "*") : "";
|
|
212
|
+
if (allowedOrigin) {
|
|
213
|
+
headers["Access-Control-Allow-Origin"] = allowedOrigin;
|
|
214
|
+
headers["Access-Control-Allow-Methods"] = config.allowedMethods.join(", ");
|
|
215
|
+
headers["Access-Control-Allow-Headers"] = config.allowedHeaders.join(", ");
|
|
216
|
+
headers["Access-Control-Expose-Headers"] = config.exposedHeaders.join(", ");
|
|
217
|
+
headers["Access-Control-Max-Age"] = String(config.maxAgeSeconds);
|
|
218
|
+
if (config.allowCredentials) {
|
|
219
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return headers;
|
|
223
|
+
}
|
|
224
|
+
export const DEFAULT_SECURITY_HEADERS_CONFIG = {
|
|
225
|
+
enabled: true,
|
|
226
|
+
hstsMaxAgeSeconds: 31536000, // 1 year
|
|
227
|
+
hstsIncludeSubdomains: true,
|
|
228
|
+
hstsPreload: true,
|
|
229
|
+
permissionsPolicy: [
|
|
230
|
+
"accelerometer=()",
|
|
231
|
+
"camera=()",
|
|
232
|
+
"geolocation=()",
|
|
233
|
+
"gyroscope=()",
|
|
234
|
+
"magnetometer=()",
|
|
235
|
+
"microphone=()",
|
|
236
|
+
"payment=()",
|
|
237
|
+
"usb=()",
|
|
238
|
+
],
|
|
239
|
+
xContentTypeOptions: true,
|
|
240
|
+
xFrameOptions: true,
|
|
241
|
+
xXssProtection: true,
|
|
242
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Generate security headers
|
|
246
|
+
*/
|
|
247
|
+
export function generateSecurityHeaders(config = DEFAULT_SECURITY_HEADERS_CONFIG) {
|
|
248
|
+
if (!config.enabled) {
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
const headers = {};
|
|
252
|
+
// HSTS
|
|
253
|
+
if (config.hstsMaxAgeSeconds > 0) {
|
|
254
|
+
let hsts = `max-age=${config.hstsMaxAgeSeconds}`;
|
|
255
|
+
if (config.hstsIncludeSubdomains) {
|
|
256
|
+
hsts += "; includeSubDomains";
|
|
257
|
+
}
|
|
258
|
+
if (config.hstsPreload) {
|
|
259
|
+
hsts += "; preload";
|
|
260
|
+
}
|
|
261
|
+
headers["Strict-Transport-Security"] = hsts;
|
|
262
|
+
}
|
|
263
|
+
// Permissions-Policy
|
|
264
|
+
if (config.permissionsPolicy.length > 0) {
|
|
265
|
+
headers["Permissions-Policy"] = config.permissionsPolicy.join(", ");
|
|
266
|
+
}
|
|
267
|
+
// X-Content-Type-Options
|
|
268
|
+
if (config.xContentTypeOptions) {
|
|
269
|
+
headers["X-Content-Type-Options"] = "nosniff";
|
|
270
|
+
}
|
|
271
|
+
// X-Frame-Options
|
|
272
|
+
if (config.xFrameOptions) {
|
|
273
|
+
headers["X-Frame-Options"] = "DENY";
|
|
274
|
+
}
|
|
275
|
+
// X-XSS-Protection
|
|
276
|
+
if (config.xXssProtection) {
|
|
277
|
+
headers["X-XSS-Protection"] = "1; mode=block";
|
|
278
|
+
}
|
|
279
|
+
// Content-Security-Policy
|
|
280
|
+
if (config.contentSecurityPolicy) {
|
|
281
|
+
headers["Content-Security-Policy"] = config.contentSecurityPolicy;
|
|
282
|
+
}
|
|
283
|
+
// Referrer-Policy
|
|
284
|
+
headers["Referrer-Policy"] = config.referrerPolicy;
|
|
285
|
+
return headers;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Combine CORS and security headers
|
|
289
|
+
*/
|
|
290
|
+
export function generateGatewayHeaders(params) {
|
|
291
|
+
const corsHeaders = generateCorsHeaders({ origin: params.origin, config: params.corsConfig });
|
|
292
|
+
const securityHeaders = generateSecurityHeaders(params.securityConfig);
|
|
293
|
+
return { ...corsHeaders, ...securityHeaders };
|
|
294
|
+
}
|
|
@@ -32,6 +32,7 @@ const SYSTEM_COMMANDS = [
|
|
|
32
32
|
"system.execApprovals.set",
|
|
33
33
|
"browser.proxy",
|
|
34
34
|
];
|
|
35
|
+
const FILE_DANGEROUS_COMMANDS = ["file.write", "file.delete"];
|
|
35
36
|
// "High risk" node commands. These can be enabled by explicitly adding them to
|
|
36
37
|
// `gateway.nodes.allowCommands` (and ensuring they're not blocked by denyCommands).
|
|
37
38
|
export const DEFAULT_DANGEROUS_NODE_COMMANDS = [
|
|
@@ -41,6 +42,7 @@ export const DEFAULT_DANGEROUS_NODE_COMMANDS = [
|
|
|
41
42
|
...CALENDAR_DANGEROUS_COMMANDS,
|
|
42
43
|
...REMINDERS_DANGEROUS_COMMANDS,
|
|
43
44
|
...SMS_DANGEROUS_COMMANDS,
|
|
45
|
+
...FILE_DANGEROUS_COMMANDS,
|
|
44
46
|
];
|
|
45
47
|
const PLATFORM_DEFAULTS = {
|
|
46
48
|
ios: [
|
|
@@ -77,9 +79,12 @@ const PLATFORM_DEFAULTS = {
|
|
|
77
79
|
...PHOTOS_COMMANDS,
|
|
78
80
|
...MOTION_COMMANDS,
|
|
79
81
|
...SYSTEM_COMMANDS,
|
|
82
|
+
"file.read",
|
|
83
|
+
"file.exists",
|
|
84
|
+
"file.list",
|
|
80
85
|
],
|
|
81
|
-
linux: [...SYSTEM_COMMANDS],
|
|
82
|
-
windows: [...SYSTEM_COMMANDS],
|
|
86
|
+
linux: [...SYSTEM_COMMANDS, "file.read", "file.exists", "file.list"],
|
|
87
|
+
windows: [...SYSTEM_COMMANDS, "file.read", "file.exists", "file.list"],
|
|
83
88
|
unknown: [...CANVAS_COMMANDS, ...CAMERA_COMMANDS, ...LOCATION_COMMANDS, ...SYSTEM_COMMANDS],
|
|
84
89
|
};
|
|
85
90
|
function normalizePlatformId(platform, deviceFamily) {
|
package/dist/infra/net/ssrf.js
CHANGED
|
@@ -20,6 +20,16 @@ function normalizeHostnameSet(values) {
|
|
|
20
20
|
}
|
|
21
21
|
return new Set(values.map((value) => normalizeHostname(value)).filter(Boolean));
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalize hostname with Unicode NFKC folding to prevent homoglyph bypass attacks.
|
|
25
|
+
* This catches lookalike characters that could be used to bypass allowlist checks.
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeHostnameWithUnicode(value) {
|
|
28
|
+
// Apply NFKC normalization to fold homoglyphs and lookalike characters
|
|
29
|
+
const normalized = value.normalize("NFKC");
|
|
30
|
+
// Then apply standard hostname normalization
|
|
31
|
+
return normalizeHostname(normalized) || normalized.toLowerCase();
|
|
32
|
+
}
|
|
23
33
|
function normalizeHostnameAllowlist(values) {
|
|
24
34
|
if (!values || values.length === 0) {
|
|
25
35
|
return [];
|
|
@@ -89,8 +99,11 @@ export function isPrivateIpAddress(address, policy) {
|
|
|
89
99
|
return false;
|
|
90
100
|
}
|
|
91
101
|
// Security-critical parse failures should fail closed for any malformed IPv6 literal.
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
// This includes IPv6 transition mechanism literals and malformed addresses.
|
|
103
|
+
if (normalized.includes(":")) {
|
|
104
|
+
if (!parseLooseIpAddress(normalized)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
94
107
|
}
|
|
95
108
|
if (!isCanonicalDottedDecimalIPv4(normalized) && isLegacyIpv4Literal(normalized)) {
|
|
96
109
|
return true;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Command Security Hardening
|
|
3
|
+
*
|
|
4
|
+
* Prevents shell injection attacks via:
|
|
5
|
+
* - Line continuation blocking (\\\n)
|
|
6
|
+
* - Unicode normalization for homoglyph attacks
|
|
7
|
+
* - Fail-closed sandbox unavailable handling
|
|
8
|
+
* - Docker namespace join blocking
|
|
9
|
+
*
|
|
10
|
+
* OpenClaw #32384, #30951
|
|
11
|
+
*/
|
|
12
|
+
import { isSafeExecutableValue } from "./exec-safety.js";
|
|
13
|
+
/**
|
|
14
|
+
* Shell metacharacters that enable command injection or chaining.
|
|
15
|
+
* Extended set includes backslash for line continuation detection.
|
|
16
|
+
*/
|
|
17
|
+
const SHELL_INJECTION_CHARS = /[;&|`$(){}<>\\]/;
|
|
18
|
+
const SHELL_LINE_CONTINUATION = /\\\r?\n/g;
|
|
19
|
+
const SHELL_COMMENT_INJECTION = /#[^\n]*$/gm;
|
|
20
|
+
/**
|
|
21
|
+
* Unicode characters that can be used to bypass shell filters.
|
|
22
|
+
* Includes zero-width chars, homoglyphs, and special whitespace.
|
|
23
|
+
*/
|
|
24
|
+
const UNICODE_BYPASS_CHARS = /[\u200B-\u200D\uFEFF\u00A0\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a command contains shell line continuations (\\\n).
|
|
27
|
+
* OpenClaw #32384 - blocks shell wrapper bypass attempts.
|
|
28
|
+
*/
|
|
29
|
+
export function hasShellLineContinuation(command) {
|
|
30
|
+
return SHELL_LINE_CONTINUATION.test(command);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if a command contains unicode bypass characters.
|
|
34
|
+
*/
|
|
35
|
+
export function hasUnicodeBypassChars(command) {
|
|
36
|
+
return UNICODE_BYPASS_CHARS.test(command);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalize command string to prevent unicode bypass attacks.
|
|
40
|
+
* Removes zero-width characters and normalizes whitespace.
|
|
41
|
+
*/
|
|
42
|
+
export function normalizeShellCommand(command) {
|
|
43
|
+
// Remove zero-width and special unicode chars
|
|
44
|
+
let normalized = command.replace(UNICODE_BYPASS_CHARS, "");
|
|
45
|
+
// Normalize line endings
|
|
46
|
+
normalized = normalized.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
47
|
+
// Remove shell line continuations (security-critical)
|
|
48
|
+
normalized = normalized.replace(SHELL_LINE_CONTINUATION, "");
|
|
49
|
+
// Remove trailing comments that could bypass filters
|
|
50
|
+
normalized = normalized.replace(SHELL_COMMENT_INJECTION, "");
|
|
51
|
+
// Trim whitespace
|
|
52
|
+
return normalized.trim();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Enhanced shell command validation with continuation blocking.
|
|
56
|
+
* OpenClaw #32384
|
|
57
|
+
*/
|
|
58
|
+
export function isSafeShellCommand(command) {
|
|
59
|
+
if (!command || !command.trim()) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const normalized = normalizeShellCommand(command);
|
|
63
|
+
// Block if normalization removed anything (indicates bypass attempt)
|
|
64
|
+
if (normalized !== command.trim()) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// Check for shell injection chars
|
|
68
|
+
if (SHELL_INJECTION_CHARS.test(normalized)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
// Check for unicode bypass attempts
|
|
72
|
+
if (hasUnicodeBypassChars(command)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
// Check for line continuations
|
|
76
|
+
if (hasShellLineContinuation(command)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Use existing safety check
|
|
80
|
+
return isSafeExecutableValue(normalized);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Docker namespace types that should be blocked by default.
|
|
84
|
+
* OpenClaw #32384 - prevents container escape via namespace joins.
|
|
85
|
+
*/
|
|
86
|
+
const BLOCKED_DOCKER_NAMESPACES = new Set(["pid", "ipc", "uts", "mount", "network"]);
|
|
87
|
+
/**
|
|
88
|
+
* Check if a Docker run command attempts to join host namespaces.
|
|
89
|
+
* Blocks --pid=host, --ipc=host, --network=host, etc.
|
|
90
|
+
*/
|
|
91
|
+
export function hasDangerousDockerNamespace(command) {
|
|
92
|
+
const normalized = command.toLowerCase();
|
|
93
|
+
for (const ns of BLOCKED_DOCKER_NAMESPACES) {
|
|
94
|
+
// Match patterns like --pid=host, --pid host, --pidhost
|
|
95
|
+
const patterns = [
|
|
96
|
+
new RegExp(`--${ns}\\s*=\\s*host`, "i"),
|
|
97
|
+
new RegExp(`--${ns}\\s+host`, "i"),
|
|
98
|
+
new RegExp(`--${ns}host`, "i"),
|
|
99
|
+
];
|
|
100
|
+
for (const pattern of patterns) {
|
|
101
|
+
if (pattern.test(normalized)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Validate Docker command for security issues.
|
|
110
|
+
* Returns { valid: true } or { valid: false, reason: string }
|
|
111
|
+
*/
|
|
112
|
+
export function validateDockerCommand(command) {
|
|
113
|
+
// Check for namespace joins
|
|
114
|
+
if (hasDangerousDockerNamespace(command)) {
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
reason: "Docker namespace join detected (pid/ipc/uts/mount/network=host). Container escape vector.",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Check for privileged mode
|
|
121
|
+
if (/--privileged/.test(command.toLowerCase())) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
reason: "Privileged Docker container detected. Full host access bypass.",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Check for dangerous volume mounts
|
|
128
|
+
const dangerousMounts = [
|
|
129
|
+
"/:/host",
|
|
130
|
+
"/:/root",
|
|
131
|
+
"--volume=/:",
|
|
132
|
+
"-v /:/",
|
|
133
|
+
"--volume=/:/",
|
|
134
|
+
"-v /:/",
|
|
135
|
+
"--volume=/var/run/docker.sock",
|
|
136
|
+
"-v /var/run/docker.sock",
|
|
137
|
+
"--volume=/run/docker.sock",
|
|
138
|
+
"-v /run/docker.sock",
|
|
139
|
+
];
|
|
140
|
+
const normalized = command.toLowerCase();
|
|
141
|
+
for (const mount of dangerousMounts) {
|
|
142
|
+
if (normalized.includes(mount.toLowerCase())) {
|
|
143
|
+
return {
|
|
144
|
+
valid: false,
|
|
145
|
+
reason: `Dangerous volume mount detected: ${mount}. Host filesystem access bypass.`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Check for capability additions
|
|
150
|
+
if (/--cap-add=ALL|--cap-add\s+ALL|--cap-add=SYS_ADMIN|--cap-add\s+SYS_ADMIN/i.test(command)) {
|
|
151
|
+
return {
|
|
152
|
+
valid: false,
|
|
153
|
+
reason: "Dangerous capability addition detected (ALL or SYS_ADMIN). Privilege escalation vector.",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return { valid: true };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fail-closed error for when sandbox is unavailable.
|
|
160
|
+
* OpenClaw #32384 - ensures commands are rejected when safety checks can't run.
|
|
161
|
+
*/
|
|
162
|
+
export class SandboxUnavailableError extends Error {
|
|
163
|
+
constructor(message = "Sandbox unavailable. Failing closed for security.") {
|
|
164
|
+
super(message);
|
|
165
|
+
this.name = "SandboxUnavailableError";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Wrap sandbox execution with fail-closed behavior.
|
|
170
|
+
* If sandbox checks cannot be performed, reject the command.
|
|
171
|
+
*/
|
|
172
|
+
export function withFailClosedSandbox(operation, context = "sandbox-execution") {
|
|
173
|
+
return operation().catch((error) => {
|
|
174
|
+
// Always fail closed - if we can't verify safety, reject
|
|
175
|
+
if (error instanceof SandboxUnavailableError) {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
// Wrap other errors as sandbox unavailable
|
|
179
|
+
throw new SandboxUnavailableError(`Sandbox check failed (${context}): ${error instanceof Error ? error.message : String(error)}. Failing closed.`);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Validate that a shell wrapper's inner executable matches approval.
|
|
184
|
+
*/
|
|
185
|
+
export function validateShellWrapper(params) {
|
|
186
|
+
const { command, approval } = params;
|
|
187
|
+
// Extract the actual executable from the command
|
|
188
|
+
const parts = command.trim().split(/\s+/);
|
|
189
|
+
if (parts.length === 0) {
|
|
190
|
+
return { valid: false, reason: "Empty command" };
|
|
191
|
+
}
|
|
192
|
+
const actualExecutable = parts[0];
|
|
193
|
+
// Check if executable matches approved inner executable
|
|
194
|
+
if (actualExecutable !== approval.innerExecutable) {
|
|
195
|
+
return {
|
|
196
|
+
valid: false,
|
|
197
|
+
reason: `Executable mismatch: approved "${approval.innerExecutable}", got "${actualExecutable}"`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return { valid: true };
|
|
201
|
+
}
|