Skip to content

feat: add Telegram and WhatsApp to OpenClaw setup picker#2523

Closed
AhmedTMM wants to merge 26 commits intoOpenRouterTeam:mainfrom
AhmedTMM:feat/messaging-setup
Closed

feat: add Telegram and WhatsApp to OpenClaw setup picker#2523
AhmedTMM wants to merge 26 commits intoOpenRouterTeam:mainfrom
AhmedTMM:feat/messaging-setup

Conversation

@AhmedTMM
Copy link
Collaborator

Summary

  • Adds Telegram and WhatsApp as separate options in the OpenClaw setup screen
  • Telegram: prompts for bot token from @Botfather, injects via openclaw config set
  • WhatsApp: reminds user to complete QR code scanning via the web dashboard after launch
  • Updates USER.md with channel-specific guidance when selected

Setup screen

◆ Setup options
│ ◻ GitHub CLI
│ ◻ Reuse saved OpenRouter key
│ ◻ Chrome browser (~400 MB — enables web tools)
│ ◻ Telegram (connect via bot token from @BotFather)
│ ◻ WhatsApp (scan QR code via web dashboard after launch)

Test plan

  • Select Telegram, enter a bot token → verify it's injected into config
  • Select Telegram, press Enter (skip) → verify graceful skip message
  • Select WhatsApp → verify reminder about web dashboard
  • Select neither → verify no messaging prompts appear
  • bun test passes (1396/1396)

🤖 Generated with Claude Code

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>
@AhmedTMM AhmedTMM marked this pull request as draft March 12, 2026 07:56
@AhmedTMM AhmedTMM marked this pull request as ready for review March 12, 2026 21:45
@AhmedTMM AhmedTMM marked this pull request as draft March 12, 2026 21:45
@AhmedTMM AhmedTMM marked this pull request as ready for review March 12, 2026 21:53
Copy link
Member

@louisgv louisgv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 uses JSON.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:

    1. Shell escape with single quotes (handles all special chars):

      function shellEscape(s: string): string {
        return "'" + s.replace(/'/g, "'\\''") + "'";
      }
      const escapedBotToken = shellEscape(trimmedToken);
    2. 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
louisgv previously approved these changes Mar 12, 2026
Copy link
Member

@louisgv louisgv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 setup
  • orchestrate-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 as assertions) ✅
  • Proper error handling and type safety ✅
  • Clear comments explaining the security fix ✅

-- security/pr-reviewer

louisgv
louisgv previously approved these changes Mar 13, 2026
Copy link
Member

@louisgv louisgv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, "'\\''")) with shellQuote() 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 -n syntax 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 --model flag 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 as type assertions (follows project conventions) ✅
  • Version bumped appropriately ✅

-- security/pr-reviewer

la14-1 and others added 19 commits March 12, 2026 18:21
…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>
la14-1 and others added 6 commits March 12, 2026 18:21
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>
@AhmedTMM AhmedTMM force-pushed the feat/messaging-setup branch from e71fee8 to f0ee18d Compare March 13, 2026 01:24
@louisgv
Copy link
Member

louisgv commented Mar 13, 2026

Closing this PR due to significant divergence from main and merge conflicts.

Analysis:

Next steps:
I'll file a follow-up issue to track the Telegram/WhatsApp feature, which should be implemented in a fresh PR based on current main.


-- security/pr-reviewer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants