feat: add Telegram and WhatsApp to OpenClaw setup picker#2523
feat: add Telegram and WhatsApp to OpenClaw setup picker#2523AhmedTMM wants to merge 26 commits intoOpenRouterTeam:mainfrom
Conversation
Adds separate "Telegram" and "WhatsApp" checkboxes to the OpenClaw setup screen: - Telegram: prompts for bot token from @Botfather, injects into OpenClaw config via `openclaw config set` - WhatsApp: reminds user to scan QR code via the web dashboard after launch (no CLI setup possible) Updates USER.md with channel-specific guidance when either is selected. Bump CLI version to 0.16.16. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
louisgv
left a comment
There was a problem hiding this comment.
Security Review
Verdict: CHANGES REQUESTED
Commit: 0825264
Findings
-
HIGH packages/cli/src/shared/agent-setup.ts:381 — Command injection vulnerability in Telegram bot token handling. The
jsonEscape()function usesJSON.stringify(), which wraps the token in double quotes but does NOT prevent command substitution. Backticks and$(...)inside the token will be evaluated by the shell.Exploit example: A token like
`whoami`becomes:openclaw config set channels.telegram.botToken "`whoami`"
The backticks execute despite being inside double quotes.
Recommendation: Use proper shell escaping with single quotes, or pass the token via stdin/environment variable instead of command line arguments. Example approaches:
-
Shell escape with single quotes (handles all special chars):
function shellEscape(s: string): string { return "'" + s.replace(/'/g, "'\\''") + "'"; } const escapedBotToken = shellEscape(trimmedToken);
-
Pass via stdin (preferred for sensitive data):
await runner.runServer( "export PATH=...; echo " + JSON.stringify(trimmedToken) + " | openclaw config set channels.telegram.botToken --stdin" );
-
-
LOW packages/cli/src/shared/agent-setup.ts:411 — Hardcoded localhost URL in documentation may not match actual tunnel URL (minor usability issue, not a security concern)
Tests
- bash -n: N/A (no .sh files modified)
- bun test: PASS (1380 tests, 0 failures)
- curl|bash: N/A (no shell scripts modified)
- macOS compat: N/A (no shell scripts modified)
-- security/pr-reviewer
louisgv
left a comment
There was a problem hiding this comment.
Security Review
Verdict: APPROVED
Commit: 3aa895e
Summary
The HIGH severity command injection vulnerability identified in commit 0825264 has been properly fixed. The PR now includes comprehensive test coverage and is ready for merge.
Security Fix Verification
The original vulnerability (line 381 in old code) was:
`openclaw config set channels.telegram.botToken ${escapedBotToken}`This was vulnerable because jsonEscape() only wrapped the token in double quotes, which did NOT prevent command substitution via backticks or $(...).
The fix (lines 379-389 in new code):
- Telegram bot token is now embedded directly into a JavaScript object
- The entire config object is serialized using
JSON.stringify() - The JSON is uploaded as a file, eliminating shell command execution
- This approach is safe regardless of token content
Additional Security Checks
✅ Env var handling: SPAWN_TELEGRAM_BOT_TOKEN follows the same safe path (embedded in object → JSON.stringify)
✅ WhatsApp command: Hardcoded string with no variable interpolation (lines 259-261 in orchestrate.ts)
✅ No other command injection vectors found in changed files
Tests
- bash -n: N/A (no .sh files modified)
- bun test: PASS (1420 tests, 0 failures)
- Biome lint: PASS (0 errors on all changed files)
- curl|bash compat: N/A (no shell scripts modified)
- macOS compat: N/A (no shell scripts modified)
Test Coverage
The PR adds 926 lines of comprehensive test coverage:
openclaw-config.test.ts(591 lines): Tests gateway auth, atomic config, browser gating, Telegram/WhatsApp setuporchestrate-messaging.test.ts(335 lines): Tests WhatsApp QR flow, enabledSteps parsing, preLaunchMsg
Code Quality
- Version bumped: 0.16.19 → 0.16.20 ✅
- Follows ESM-only convention (no
asassertions) ✅ - Proper error handling and type safety ✅
- Clear comments explaining the security fix ✅
-- security/pr-reviewer
louisgv
left a comment
There was a problem hiding this comment.
Security Review
Verdict: APPROVED
Commit: b00c067
Summary
The PR has been updated with additional commits that include security improvements and non-security feature additions. The HIGH severity command injection vulnerability identified in the initial review (commit 0825264) was properly fixed in commit 3aa895e, and the new commits (3aa895e..b00c067) introduce further security hardening.
Security Improvements in New Commits
- agent-setup.ts:242-243, 257-260 — Replaced manual quote escaping (
replace(/'/g, "'\\''")) withshellQuote()utility for GitHub tokens and git identity fields. This is a security hardening that uses a battle-tested shell escaping function instead of ad-hoc string manipulation. - soak.sh:155 — Telegram bot token is now passed via environment variable (
_TOKEN=${_TOKEN} bun -e) instead of command-line interpolation. This is secure and prevents shell command substitution.
Security Verification
✅ Command injection fix verified — The original Telegram bot token vulnerability (commit 0825264) was fixed by embedding tokens in JSON objects and uploading as files (commit 3aa895e), eliminating shell execution entirely.
✅ No new vulnerabilities — Reviewed all changed files in commits 3aa895e..b00c067:
- Shell scripts:
bash -nsyntax checks pass - TypeScript: No command injection, path traversal, or credential leaks
- New shell escaping uses
shellQuote()utility (secure) - Telegram token handling in soak tests uses env vars (secure)
Non-Security Changes
The new commits also include:
- Version bump: 0.16.20 → 0.17.1
- New
--modelflag for CLI model selection - Documentation updates (.claude/rules/agent-default-models.md)
- Codex default model updated: gpt-5.1-codex → gpt-5.3-codex
- Manifest updates: featured_cloud standardization
- QA workflow enhancements (soak test scheduling)
Tests
- bash -n: PASS (all .sh files pass syntax checks)
- bun test: PASS (1420 tests, 0 failures)
- Biome lint: PASS (0 errors on all changed TypeScript files)
- curl|bash compat: OK (no changes to curl|bash patterns)
- macOS compat: OK (shell changes use compatible patterns)
Code Quality
- Proper use of
shellQuote()for user-controlled strings ✅ - Telegram token passed via env var (not command line) ✅
- No
astype assertions (follows project conventions) ✅ - Version bumped appropriately ✅
-- security/pr-reviewer
…ccount limit (OpenRouterTeam#2518) Previously, _digitalocean_max_parallel() always returned 3, assuming all quota slots were available. When pre-existing droplets occupy slots, the batch-3 parallel runs fail with "droplet limit exceeded" API errors. Now queries /v2/account for the actual droplet_limit and subtracts the current droplet count to compute available capacity. Falls back to 3 if the API is unreachable. -- qa/e2e-tester Co-authored-by: spawn-qa-bot <qa@openrouter.ai>
…rrors (OpenRouterTeam#2520) When provisioning hits a 422 "droplet limit exceeded" response, wait 30s and retry up to 3 times. Makes E2E suite resilient to transient limit hits during parallel batch provisioning. Fixes OpenRouterTeam#2516 Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of punting WhatsApp setup to "after launch", runs `openclaw channels login --channel whatsapp` as an interactive SSH session between gateway start and TUI launch. The user scans the QR code with their phone during provisioning setup. Flow: gateway starts → tunnel set up → WhatsApp QR scan → TUI launch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…am#2519) * test: add cron-triggered Telegram reminder to soak test Tests OpenClaw's ability to stay alive and execute scheduled tasks. Installs a one-shot cron on the VM before the 1h soak wait that sends a Telegram message at ~55 min, then verifies the message was sent after the wait completes. Also moves Telegram config injection before the soak wait so the cron can use the bot token immediately. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: use OpenClaw's cron scheduler instead of system crontab Replaces the raw system cron approach with OpenClaw's built-in cron scheduler (`openclaw cron add`). This properly tests that OpenClaw's gateway stays alive after 1 hour and can execute scheduled tasks. The test now: 1. Injects Telegram config + schedules an OpenClaw cron job (--at +55min) 2. Waits 1 hour (soak) 3. Verifies the job fired via `openclaw cron runs` and `openclaw cron list` Uses --delete-after-run for one-shot semantics. Verification checks both the run history and the auto-deletion as proof of execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: verify cron message on Telegram side via forwardMessage Instead of trusting OpenClaw's self-reported cron status, we now verify the message actually exists in the Telegram chat: 1. Extract message_id from OpenClaw's cron execution logs (tries `openclaw cron runs`, then ~/.openclaw/cron/ directory) 2. Call Telegram's forwardMessage API with that message_id 3. If Telegram can forward it → message EXISTS in the chat (proof from Telegram itself, not OpenClaw) This catches cases where OpenClaw reports success but the message never actually reached Telegram. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address security review findings in soak test - Add validate_positive_int() and validate SOAK_WAIT_SECONDS + SOAK_CRON_DELAY_SECONDS at startup (prevents command injection via crafted env vars) - Validate TELEGRAM_TEST_CHAT_ID is numeric in soak_validate_telegram_env - Use per-app marker file /tmp/.spawn-cron-scheduled-${app} to avoid race conditions when multiple soak tests run on the same VM Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…n gcp.ts (OpenRouterTeam#2524) Fixes OpenRouterTeam#2521 - Add path traversal and argument injection protection for localPath Fixes OpenRouterTeam#2522 - Add validation for cmd parameter before SSH execution Agent: security-auditor Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…RouterTeam#2525) Replace `if (!r.ok) { expect(...) }` and `if (result.ok) { return }` guards with unconditional assertions using toThrow() or toMatchObject(). These conditional blocks silently skipped assertions when the condition evaluated the wrong way, providing false confidence. Also remove now-unused tryCatch imports from prompt-file-security.test.ts and security.test.ts. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…th) (OpenRouterTeam#2528) Add explicit base64 character validation in _digitalocean_exec after encoding the command, matching the existing pattern in provision.sh. This ensures the encoded value contains only [A-Za-z0-9+/=] before embedding it in the SSH command string. Note: OpenRouterTeam#2527 (provision.sh base64 validation) was already fixed in a prior commit — the validation at lines 284-289 already rejects non-base64 characters and empty output. Fixes OpenRouterTeam#2526 Agent: security-auditor Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…n-depth) (OpenRouterTeam#2532) Add base64 character validation ([A-Za-z0-9+/=]) before use in SSH command strings for gcp.sh, aws.sh, and hetzner.sh cloud_exec functions -- matching the existing fix in digitalocean.sh (OpenRouterTeam#2528). Also add a validated _encode_b64 helper to soak.sh and use it for all Telegram bot token encoding, preventing corrupted base64 from breaking out of single-quoted SSH command strings. Closes OpenRouterTeam#2527 Agent: security-auditor Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terTeam#2530) The identical generateCsrfState() helper existed in both digitalocean/digitalocean.ts and shared/oauth.ts. Export it from oauth.ts (which digitalocean.ts already imports) and remove the duplicate copy. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
Consolidate 8 fragmented pipe-to-bash/sh tests in validatePrompt into 2 data-driven tests covering all inputs (with/without whitespace, complex pipelines, and standalone word acceptance). Merge 3 backtick tests into 1. Merge 2 whitespace tests into 1. Removes 19 lines of duplicate test setup. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
…penRouterTeam#2533) - Add null-byte rejection to shellQuote (defense-in-depth) - Export shellQuote for testability - Refactor interactiveSession to use shellQuote instead of inline escaping - Add comprehensive test suite for shellQuote security properties Fixes OpenRouterTeam#2529 Agent: security-auditor Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Consolidate 9 per-credential-type it() blocks in prompt-file-security.test.ts into a single data-driven test covering all 17 sensitive path patterns. Merge 2 validatePromptFileStats "accept" tests into one. Consolidate 4 unicode/encoding-attack it() blocks in security.test.ts into a single data-driven test. Merge 3 "accept identifier" it() blocks into one. Removes 19 redundant tests (1400 → 1381) with no loss of coverage. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…OpenRouterTeam#2535) PR OpenRouterTeam#2533 hardened GCP with shellQuote() and null-byte rejection, but left Hetzner, DigitalOcean, AWS, and connect.ts using inline .replace(/'/g, "'\\''") without null-byte validation. - Move shellQuote to shared/ui.ts as the single source of truth - Add null-byte validation to runServer in Hetzner, DO, and AWS - Replace inline shell escaping with shellQuote in interactiveSession across all clouds, connect.ts, and agents.ts buildEnvBlock - Re-export shellQuote from gcp.ts for backwards compatibility Agent: security-auditor Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…eam#2536) Three root-cause bugs in input test functions: 1. Stdin pass-through broken: cloud_exec uses "printf '...' | base64 -d | bash" on the remote, meaning bash reads the script from its own stdin — not the outer process's stdin. "PROMPT=$(base64 -d)" inside the script was reading from the already-consumed pipe, always producing an empty prompt. Fix: embed the base64-encoded prompt directly in the remote command string. Base64 output is [A-Za-z0-9+/=] only — safe to embed in single-quoted strings. 2. Zeroclaw flag wrong: "zeroclaw agent -p" was passing the prompt as --provider (not --prompt). The correct flag for non-interactive single-message mode is "-m"/"--message". 3. Codex model stale: "openai/gpt-5-codex" does not exist on OpenRouter. Updated to "openai/gpt-5.1-codex" which is available. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* security: add DO_CLIENT_SECRET env var override Allows users/organizations to supply their own DigitalOcean OAuth client secret via DO_CLIENT_SECRET env var rather than relying on the bundled default. The bundled secret remains as fallback. Fixes OpenRouterTeam#2537 Agent: security-auditor Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * chore: bump CLI version to 0.16.19 Agent: security-auditor Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove redundant existsSync check inside icon-integrity "is actual PNG data" tests — the file existence is already verified in the preceding test, and isPng() will throw if the file is missing. Remove the "should detect multiple dangerous patterns" test from validatePrompt — it retests the same $(…), backtick, ; rm, and |bash/sh patterns that each have their own dedicated it() block immediately above. Fix misleading test description: "should accept scripts with comments containing dangerous patterns" — the test actually expects a throw (documented as a known trade-off). Rename to "should reject…". Removes 1 test (1381 → 1380) and 18 expect() calls. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The `openclaw config set` calls for browser and Telegram settings were re-serializing openclaw.json and dropping the gateway.auth.token field, causing the dashboard to show "Unauthorized" when auto-opened via tunnel. Now all config (gateway auth, browser, channels) is built as a single JSON object and written once via uploadConfigFile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds 40 new tests across 2 files: openclaw-config.test.ts (30 tests): - Gateway auth token written correctly and matches browserUrl - Atomic config write (no `openclaw config set` commands) - Browser config gated by enabledSteps - Telegram bot token included/omitted based on input - USER.md messaging channel content - Tunnel config targeting port 18791 orchestrate-messaging.test.ts (10 tests): - SPAWN_ENABLED_STEPS parsing and threading - WhatsApp QR scan session triggered before agent launch - GitHub auth gated by enabledSteps - preLaunchMsg output behavior Also adds SPAWN_TELEGRAM_BOT_TOKEN env var override for non-interactive/CI Telegram setup (avoids prompt in tests). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Junie was added as a fully implemented agent (manifest, agent scripts, agent-setup.ts) but the packer/tarball pipeline was never updated. This meant the nightly agent-tarballs workflow could not build a pre-built tarball for Junie, forcing all deployments to do a live npm install. - Add junie entry to packer/agents.json (tier: node, @jetbrains/junie-cli) - Add junie to capture-agent.sh allowlist and path-capture case (npm-based, same as codex/kilocode — captures /root/.npm-global/) Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…penRouterTeam#2543) Adds --model / -m CLI flag to override the agent's default LLM model: spawn codex gcp --model openai/gpt-5.3-codex Also supports persistent per-agent model preferences via config file at ~/.config/spawn/preferences.json: { "models": { "codex": "openai/gpt-5.3-codex" } } Priority: --model flag > preferences file > agent default. This enables a future web UI to pass model selection via CLI args when invoking spawn programmatically to provision machines. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
…reference (OpenRouterTeam#2540) The previous PR (OpenRouterTeam#2536) set the Codex default to gpt-5.1-codex, but the latest available on OpenRouter is gpt-5.3-codex. Also adds a rules file documenting each agent's default model to prevent future regressions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
… defense (OpenRouterTeam#2546) Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Team#2547) - soak.sh: SOAK_CLOUD env var makes cloud configurable (default: sprite) - qa.sh: load TELEGRAM_BOT_TOKEN, TELEGRAM_TEST_CHAT_ID, SOAK_CLOUD from /etc/spawn-qa-auth.env in soak mode - qa.yml: add weekly Monday 3am UTC scheduled soak trigger - fix: bun eval → bun -e across soak.sh, key-request.sh, github-auth.sh (bun eval is not a valid subcommand in bun 1.3.9) - fix: export _TOKEN via env prefix so process.env._TOKEN works in bun -e - docs: update shell-scripts.md rule to say bun -e (not bun eval) Verified: 3/4 Telegram tests pass in smoke test on DigitalOcean (120s wait) getMe ✓ sendMessage ✓ getWebhookInfo ✓; cron test needs full 55-min window. Co-authored-by: spawn-qa-bot <qa@openrouter.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents (OpenRouterTeam#2548) Set every agent's featured_cloud to ["digitalocean", "sprite"] — one primary recommendation (DigitalOcean) and one fallback (Sprite). Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: L <6723574+louisgv@users.noreply.github.com>
e71fee8 to
f0ee18d
Compare
Summary
openclaw config setSetup screen
Test plan
bun testpasses (1396/1396)🤖 Generated with Claude Code