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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

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

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

Conversation

@AhmedTMM

Copy link
Copy Markdown
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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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):

      functionshellEscape(s: string): string{return"'"+s.replace(/'/g,"'\\''")+"'";}constescapedBotToken=shellEscape(trimmedToken);
    2. Pass via stdin (preferred for sensitive data):

      awaitrunner.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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

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-1and others added 19 commits March 12, 2026 18:21
…ccount limit (OpenRouterLabs#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 (OpenRouterLabs#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.
FixesOpenRouterLabs#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>
…bs#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 (OpenRouterLabs#2524)
FixesOpenRouterLabs#2521 - Add path traversal and argument injection protection for localPath
FixesOpenRouterLabs#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>
…RouterLabs#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) (OpenRouterLabs#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: OpenRouterLabs#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.
FixesOpenRouterLabs#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) (OpenRouterLabs#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 (OpenRouterLabs#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.
ClosesOpenRouterLabs#2527
Agent: security-auditor
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…terLabs#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>
…penRouterLabs#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
FixesOpenRouterLabs#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>
…OpenRouterLabs#2535)
PR OpenRouterLabs#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>
…abs#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.
FixesOpenRouterLabs#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-1and 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>
…penRouterLabs#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 (OpenRouterLabs#2540)
The previous PR (OpenRouterLabs#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 (OpenRouterLabs#2546)
Agent: code-health
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…Labs#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 (OpenRouterLabs#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
AhmedTMMforce-pushed the feat/messaging-setup branch from e71fee8 to f0ee18dCompareMarch 13, 2026 01:24
@louisgv

Copy link
Copy Markdown
Collaborator

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 freeto 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

@AhmedTMM@louisgv@la14-1