diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f944b361..fcd8e457c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -628,6 +628,8 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: 8.11.1 - name: Install Android SDK packages run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc3ebae3..4eda32488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Docs: https://docs.clawd.bot +## 2026.1.24-3 + +### Fixes +- Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie. +- Security audit: flag loopback Control UI with auth disabled as critical. (#1795) Thanks @orlyjamie. + +## 2026.1.24-2 + +### Fixes +- Packaging: include dist/link-understanding output in npm tarball (fixes missing apply.js import on install). + +## 2026.1.24-1 + +### Fixes +- Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install). + ## 2026.1.24 ### Highlights @@ -38,6 +54,7 @@ Docs: https://docs.clawd.bot - Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47. - BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.clawd.bot/channels/bluebubbles - BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing. +- iMessage: normalize chat_id/chat_guid/chat_identifier prefixes case-insensitively and keep service-prefixed handles stable. (#1708) Thanks @aaronn. - Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev. - Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.clawd.bot/channels/signal - Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338. diff --git a/README.md b/README.md index 1329c5e2b..ebbdc43d5 100644 --- a/README.md +++ b/README.md @@ -477,32 +477,33 @@ Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and Thanks to all clawtributors:

- steipete bohdanpodvirnyi iHildy joaohlisboa mneves75 MatthieuBizien MaudeBot Glucksberg rahthakor vrknetha - radek-paclt Tobias Bischoff joshp123 czekaj mukhtharcm maxsumrall xadenryan rodrigouroz juanpablodlc hsrvc - magimetal meaningfool patelhiren NicholasSpisak sebslight jonisjongithub abhisekbasu1 zerone0x jamesgroat claude - JustYannicc tyler6204 SocialNerd42069 Hyaxia dantelex daveonkels google-labs-jules[bot] lc0rp vignesh07 mteam88 - Eng. Juan Combetto Mariano Belinky dbhurley TSavo julianengel bradleypriest benithors timolins nachx639 pvoo - sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras - andranik-sahakyan davidguttman sleontenko denysvitali sircrumpet peschee rafaelreis-r thewilloftheshadow ratulsarna lutr0 - danielz1z emanuelst KristijanJovanovski CashWilliams rdev osolmaz joshrad-dev kiranjd adityashaw2 sheeek - artuskg Takhoffman onutc pauloportella neooriginal manuelhettich minghinmatthewlam myfunc travisirby buddyh - connorshea kyleok mcinteerj dependabot[bot] John-Rood timkrase gerardward2007 obviyus roshanasingh4 tosh-hamburg - azade-c bjesuiter cheeeee Josh Phillips dlauer pookNast Whoaa512 YuriNachos chriseidhof robbyczgw-cla - ysqander aj47 superman32432432 Yurii Chukhlib grp06 ngutman antons austinm911 blacksmith-sh[bot] damoahdominic - dan-dr HeimdallStrategy imfing jalehman jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse - dougvk erikpr1994 Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist - sibbl chrisrodz Friederike Seiler gabriel-trigo iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 manmal ogulcancelik - pasogott petradonka rubyrunsstuff siddhantjain suminhthanh svkozak VACInc wes-davis zats 24601 - adam91holt ameno- Chris Taylor Django Navarro evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash - pcty-nextgen-service-account Syhids Aaron Konyer aaronveklabs andreabadesso Andrii cash-echo-bot Clawd ClawdFx erik-agens - Evizero fcatuhe itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi longmaba - mickahouan mjrussell odnxe p6l-richard philipp-spiess robaxelsen Sash Catanzarite T5-AndyML travisp VAC - william arzt zknicker abhaymundhara alejandro maza andrewting19 anpoirier arthyn Asleep123 bolismauro conhecendoia - dasilva333 Dimitrios Ploutarchos Drake Thomsen EnzeD fal3 Felix Krause ganghyun kim grrowl gtsifrikas HazAT - hrdwdmrbl hugobarauna Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin kitze levifig Lloyd loukotal - martinpucik Matt mini Miles mrdbstn MSch Mustafa Tag Eldeen ndraiman nexty5870 prathamdby ptn1411 - reeltimeapps RLTCmpe Rolf Fredheim Rony Kelner Samrat Jha sergical shiv19 siraht snopoke testingabc321 - The Admiral thesash Ubuntu voidserf Vultr-Clawd Admin Wimmie wstock yazinsai Zach Knickerbocker Alphonse-arianee - Azade carlulsoe ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani odrobnik pcty-nextgen-ios-builder Quentin - Randy Torres rhjoh ronak-guliani William Stock + steipete plum-dawg bohdanpodvirnyi iHildy joaohlisboa mneves75 MatthieuBizien MaudeBot Glucksberg rahthakor + vrknetha radek-paclt Tobias Bischoff joshp123 czekaj mukhtharcm sebslight maxsumrall xadenryan rodrigouroz + juanpablodlc hsrvc magimetal meaningfool tyler6204 patelhiren NicholasSpisak jonisjongithub abhisekbasu1 zerone0x + jamesgroat claude JustYannicc SocialNerd42069 Hyaxia dantelex daveonkels google-labs-jules[bot] lc0rp mousberg + vignesh07 mteam88 Eng. Juan Combetto Mariano Belinky dbhurley TSavo julianengel bradleypriest benithors rohannagpal + timolins benostein f-trycua nachx639 pvoo sreekaransrinath gupsammy cristip73 stefangalescu nachoiacovino + Vasanth Rao Naik Sabavat petter-b cpojer scald gumadeiras andranik-sahakyan davidguttman sleontenko denysvitali sircrumpet + peschee rafaelreis-r thewilloftheshadow ratulsarna lutr0 danielz1z emanuelst KristijanJovanovski CashWilliams rdev + osolmaz joshrad-dev kiranjd adityashaw2 sheeek artuskg Takhoffman onutc pauloportella neooriginal + manuelhettich minghinmatthewlam myfunc travisirby buddyh connorshea kyleok mcinteerj dependabot[bot] John-Rood + timkrase uos-status gerardward2007 obviyus roshanasingh4 tosh-hamburg azade-c bjesuiter cheeeee Josh Phillips + robbyczgw-cla dlauer pookNast Whoaa512 YuriNachos chriseidhof ngutman ysqander aj47 superman32432432 + Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr HeimdallStrategy imfing jalehman + jarvis-medmatic kkarimi mahmoudashraf93 pkrmf RandyVentures Ryan Lisse dougvk erikpr1994 Ghost jonasjancarik + Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist sibbl chrisrodz Friederike Seiler gabriel-trigo + iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 manmal ogulcancelik pasogott petradonka rubyrunsstuff siddhantjain + suminhthanh svkozak VACInc wes-davis zats 24601 adam91holt ameno- Chris Taylor Django Navarro + evalexpr henrino3 humanwritten larlyssa odysseus0 oswalpalash pcty-nextgen-service-account Syhids Aaron Konyer aaronveklabs + andreabadesso Andrii cash-echo-bot Clawd ClawdFx dguido EnzeD erik-agens Evizero fcatuhe + itsjaydesu ivancasco ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi longmaba mickahouan mjrussell + odnxe p6l-richard philipp-spiess robaxelsen Sash Catanzarite T5-AndyML travisp VAC william arzt zknicker + abhaymundhara alejandro maza andrewting19 anpoirier arthyn Asleep123 bolismauro conhecendoia dasilva333 Developer + Dimitrios Ploutarchos Drake Thomsen fal3 Felix Krause ganghyun kim grrowl gtsifrikas HazAT hrdwdmrbl hugobarauna + Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin levifig Lloyd loukotal louzhixian martinpucik Matt mini + Miles mrdbstn MSch Mustafa Tag Eldeen ndraiman nexty5870 prathamdby ptn1411 reeltimeapps RLTCmpe + Rolf Fredheim Rony Kelner Samrat Jha senoldogann Seredeep sergical shiv19 siraht snopoke testingabc321 + The Admiral thesash Ubuntu voidserf Vultr-Clawd Admin Wimmie wstock yazinsai ymat19 Zach Knickerbocker + aaronn Alphonse-arianee Azade carlulsoe ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani odrobnik + pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock

diff --git a/appcast.xml b/appcast.xml index a589863ae..8158ac244 100644 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,101 @@ Clawdbot + + 2026.1.24-1 + Sun, 25 Jan 2026 14:05:25 +0000 + https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml + 7952 + 2026.1.24-1 + 15.0 + Clawdbot 2026.1.24-1 +

Fixes

+
    +
  • Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).
  • +
+

View full changelog

+]]>
+ +
+ + 2026.1.24 + Sun, 25 Jan 2026 13:31:05 +0000 + https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml + 7944 + 2026.1.24 + 15.0 + Clawdbot 2026.1.24 +

Highlights

+
    +
  • Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama https://docs.clawd.bot/providers/venice
  • +
  • Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.
  • +
  • TTS: Edge fallback (keyless) + /tts auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.clawd.bot/tts
  • +
  • Exec approvals: approve in-chat via /approve across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
  • +
  • Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.clawd.bot/channels/telegram
  • +
+

Changes

+
    +
  • Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.
  • +
  • TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
  • +
  • TTS: add auto mode enum (off/always/inbound/tagged) with per-session /tts override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts
  • +
  • Telegram: treat DM topics as separate sessions and keep DM history limits stable with thread suffixes. (#1597) Thanks @rohannagpal.
  • +
  • Telegram: add channels.telegram.linkPreview to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.clawd.bot/channels/telegram
  • +
  • Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web
  • +
  • UI: refresh Control UI dashboard design system (typography, colors, spacing). (#1786) Thanks @mousberg.
  • +
  • Exec approvals: forward approval prompts to chat with /approve for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
  • +
  • Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.
  • +
  • Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.clawd.bot/diagnostics/flags
  • +
  • Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
  • +
  • Docs: add verbose installer troubleshooting guidance.
  • +
  • Docs: add macOS VM guide with local/hosted options + VPS/nodes guidance. (#1693) Thanks @f-trycua.
  • +
  • Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock
  • +
  • Docs: update Fly.io guide notes.
  • +
  • Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.
  • +
+

Fixes

+
    +
  • Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589.
  • +
  • Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
  • +
  • Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.
  • +
  • Web UI: hide internal message_id hints in chat bubbles.
  • +
  • Gateway: allow Control UI token-only auth to skip device pairing even when device identity is present (gateway.controlUi.allowInsecureAuth). (#1679) Thanks @steipete.
  • +
  • Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47.
  • +
  • BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.clawd.bot/channels/bluebubbles
  • +
  • BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
  • +
  • Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.
  • +
  • Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.clawd.bot/channels/signal
  • +
  • Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338.
  • +
  • Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
  • +
  • Telegram: honor per-account proxy for outbound API calls. (#1774) Thanks @radek-paclt.
  • +
  • Telegram: fall back to text when voice notes are blocked by privacy settings. (#1725) Thanks @foeken.
  • +
  • Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)
  • +
  • Voice Call: serialize Twilio TTS playback and cancel on barge-in to prevent overlap. (#1713) Thanks @dguido.
  • +
  • Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy.
  • +
  • Google Chat: normalize space targets without double spaces/ prefix.
  • +
  • Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
  • +
  • Agents: use the active auth profile for auto-compaction recovery.
  • +
  • Media understanding: skip image understanding when the primary model already supports vision. (#1747) Thanks @tyler6204.
  • +
  • Models: default missing custom provider fields so minimal configs are accepted.
  • +
  • Messaging: keep newline chunking safe for fenced markdown blocks across channels.
  • +
  • TUI: reload history after gateway reconnect to restore session state. (#1663)
  • +
  • Heartbeat: normalize target identifiers for consistent routing.
  • +
  • Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.
  • +
  • Exec: treat Windows platform labels as Windows for node shell selection. (#1760) Thanks @ymat19.
  • +
  • Gateway: include inline config env vars in service install environments. (#1735) Thanks @Seredeep.
  • +
  • Gateway: skip Tailscale DNS probing when tailscale.mode is off. (#1671)
  • +
  • Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.
  • +
  • Gateway: clarify Control UI/WebChat auth error hints for missing tokens. (#1690)
  • +
  • Gateway: listen on IPv6 loopback when bound to 127.0.0.1 so localhost webhooks work.
  • +
  • Gateway: store lock files in the temp directory to avoid stale locks on persistent volumes. (#1676)
  • +
  • macOS: default direct-transport ws:// URLs to port 18789; document gateway.remote.transport. (#1603) Thanks @ngutman.
  • +
  • Tests: cap Vitest workers on CI macOS to reduce timeouts. (#1597) Thanks @rohannagpal.
  • +
  • Tests: avoid fake-timer dependency in embedded runner stream mock to reduce CI flakes. (#1597) Thanks @rohannagpal.
  • +
  • Tests: increase embedded runner ordering test timeout to reduce CI flakes. (#1597) Thanks @rohannagpal.
  • +
+

View full changelog

+]]>
+ +
2026.1.23 Sat, 24 Jan 2026 13:02:18 +0000 @@ -89,127 +184,5 @@ ]]> - - 2026.1.22 - Fri, 23 Jan 2026 08:58:14 +0000 - https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml - 7530 - 2026.1.22 - 15.0 - Clawdbot 2026.1.22 -

Changes

-
    -
  • Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer.
  • -
  • Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren.
  • -
  • Slack: add chat-type reply threading overrides via replyToModeByChatType. (#1442) Thanks @stefangalescu.
  • -
  • BlueBubbles: add asVoice support for MP3/CAF voice memos in sendAttachment. (#1477, #1482) Thanks @Nicell.
  • -
  • Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link.
  • -
-

Fixes

-
    -
  • BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.
  • -
  • Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky.
  • -
  • Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.
  • -
  • Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer.
  • -
  • Agents: sanitize assistant history text to strip tool-call markers. (#1456) Thanks @zerone0x.
  • -
  • Discord: clarify Message Content Intent onboarding hint. (#1487) Thanks @kyleok.
  • -
  • Gateway: stop the service before uninstalling and fail if it remains loaded.
  • -
  • Agents: surface concrete API error details instead of generic AI service errors.
  • -
  • Exec: fall back to non-PTY when PTY spawn fails (EBADF). (#1484)
  • -
  • Exec approvals: allow per-segment allowlists for chained shell commands on gateway + node hosts. (#1458) Thanks @czekaj.
  • -
  • Agents: make OpenAI sessions image-sanitize-only; gate tool-id/repair sanitization by provider.
  • -
  • Doctor: honor CLAWDBOT_GATEWAY_TOKEN for auth checks and security audit token reuse. (#1448) Thanks @azade-c.
  • -
  • Agents: make tool summaries more readable and only show optional params when set.
  • -
  • Agents: honor SOUL.md guidance even when the file is nested or path-qualified. (#1434) Thanks @neooriginal.
  • -
  • Matrix (plugin): persist m.direct for resolved DMs and harden room fallback. (#1436, #1486) Thanks @sibbl.
  • -
  • CLI: prefer ~ for home paths in output.
  • -
  • Mattermost (plugin): enforce pairing/allowlist gating, keep @username targets, and clarify plugin-only docs. (#1428) Thanks @damoahdominic.
  • -
  • Agents: centralize transcript sanitization in the runner; keep tags and error turns intact.
  • -
  • Auth: skip auth profiles in cooldown during initial selection and rotation. (#1316) Thanks @odrobnik.
  • -
  • Agents/TUI: honor user-pinned auth profiles during cooldown and preserve search picker ranking. (#1432) Thanks @tobiasbischoff.
  • -
  • Docs: fix gog auth services example to include docs scope. (#1454) Thanks @zerone0x.
  • -
  • Slack: reduce WebClient retries to avoid duplicate sends. (#1481)
  • -
  • Slack: read thread replies for message reads when threadId is provided (replies-only). (#1450) Thanks @rodrigouroz.
  • -
  • macOS: prefer linked channels in gateway summary to avoid false “not linked” status.
  • -
  • macOS/tests: fix gateway summary lookup after guard unwrap; prevent browser opens during tests. (ECID-1483)
  • -
-

View full changelog

-]]>
- -
- - 2026.1.21 - Thu, 22 Jan 2026 12:22:35 +0000 - https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml - 7374 - 2026.1.21 - 15.0 - Clawdbot 2026.1.21 -

Highlights

-
    -
  • Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster
  • -
  • Custom assistant identity + avatars in the Control UI. https://docs.clawd.bot/cli/agents https://docs.clawd.bot/web/control-ui
  • -
  • Cache optimizations: cache-ttl pruning + defaults reduce token spend on cold requests. https://docs.clawd.bot/concepts/session-pruning
  • -
  • Exec approvals + elevated ask/full modes. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/elevated
  • -
  • Signal typing/read receipts + MSTeams attachments. https://docs.clawd.bot/channels/signal https://docs.clawd.bot/channels/msteams
  • -
  • /models UX refresh + clawdbot update wizard. https://docs.clawd.bot/cli/models https://docs.clawd.bot/cli/update
  • -
-

Changes

-
    -
  • Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster (#1152) Thanks @vignesh07.
  • -
  • Agents/UI: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer. https://docs.clawd.bot/gateway/configuration https://docs.clawd.bot/cli/agents
  • -
  • Control UI: add custom assistant identity support and per-session identity display. (#1420) Thanks @robbyczgw-cla. https://docs.clawd.bot/web/control-ui
  • -
  • CLI: add clawdbot update wizard with interactive channel selection + restart prompts, plus preflight checks before rebasing. https://docs.clawd.bot/cli/update
  • -
  • Models/Commands: add /models, improve /model listing UX, and expand clawdbot models paging. (#1398) Thanks @vignesh07. https://docs.clawd.bot/cli/models
  • -
  • CLI: move gateway service commands under clawdbot gateway, flatten node service commands under clawdbot node, and add gateway probe for reachability. https://docs.clawd.bot/cli/gateway https://docs.clawd.bot/cli/node
  • -
  • Exec: add elevated ask/full modes, tighten allowlist gating, and render approvals tables on write. https://docs.clawd.bot/tools/elevated https://docs.clawd.bot/tools/exec-approvals
  • -
  • Exec approvals: default to local host, add gateway/node targeting + target details, support wildcard agent allowlists, and tighten allowlist parsing/safe bins. https://docs.clawd.bot/cli/approvals https://docs.clawd.bot/tools/exec-approvals
  • -
  • Heartbeat: allow explicit session keys and active hours. (#1256) Thanks @zknicker. https://docs.clawd.bot/gateway/heartbeat
  • -
  • Sessions: add per-channel idle durations via sessions.channelIdleMinutes. (#1353) Thanks @cash-echo-bot.
  • -
  • Nodes: run exec-style, expose PATH in status/describe, and bootstrap PATH for node-host execution. https://docs.clawd.bot/cli/node
  • -
  • Cache: add cache.ttlPrune mode and auth-aware defaults for cache TTL behavior.
  • -
  • Queue: add per-channel debounce overrides for auto-reply. https://docs.clawd.bot/concepts/queue
  • -
  • Discord: add wildcard channel config support. (#1334) Thanks @pvoo. https://docs.clawd.bot/channels/discord
  • -
  • Signal: add typing indicators and DM read receipts via signal-cli. https://docs.clawd.bot/channels/signal
  • -
  • MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. https://docs.clawd.bot/channels/msteams
  • -
  • Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead).
  • -
  • macOS: refresh Settings (location access in Permissions, connection mode in menu, remove CLI install UI).
  • -
  • Diagnostics: add cache trace config for debugging. (#1370) Thanks @parubets.
  • -
  • Docs: Lobster guides + org URL updates, /model allowlist troubleshooting, Gmail message search examples, gateway.mode troubleshooting, prompt injection guidance, npm prefix/node CLI notes, control UI dev gatewayUrl note, tool_use FAQ, showcase video, and sharp/node-gyp workaround. (#1427, #1220, #1405) Thanks @vignesh07, @mbelinky.
  • -
-

Breaking

-
    -
  • BREAKING: Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set gateway.controlUi.allowInsecureAuth: true to allow token-only auth. https://docs.clawd.bot/web/control-ui#insecure-http
  • -
  • BREAKING: Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert.
  • -
-

Fixes

-
    -
  • Streaming/Typing/Media: keep reply tags across streamed chunks, start typing indicators at run start, and accept MEDIA paths with spaces/tilde while preferring the message tool hint for image replies.
  • -
  • Agents/Providers: drop unsigned thinking blocks for Claude models (Google Antigravity) and enforce alphanumeric tool call ids for strict providers (Mistral/OpenRouter). (#1372) Thanks @zerone0x.
  • -
  • Exec approvals: treat main as the default agent, align node/gateway allowlist prechecks, validate resolved paths, avoid allowlist resolve races, and avoid null optional params. (#1417, #1414, #1425) Thanks @czekaj.
  • -
  • Exec/Windows: resolve Windows exec paths with extensions and handle safe-bin exe names.
  • -
  • Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman.
  • -
  • Gateway: prevent multiple gateways from sharing the same config/state (singleton lock), keep auto bind loopback-first with explicit tailnet binding, and improve SSH auth handling. (#1380)
  • -
  • Control UI: remove the chat stop button, keep the composer aligned to the bottom edge, stabilize session previews, and refresh the debug panel on route-driven tab changes. (#1373) Thanks @yazinsai.
  • -
  • UI/config: export SECTION_META for config form modules. (#1418) Thanks @MaudeBot.
  • -
  • macOS: keep chat pinned during streaming replies, include Textual resources, respect wildcard exec approvals, allow SSH agent auth, and default distribution builds to universal binaries. (#1279, #1362, #1384, #1396) Thanks @ameno-, @JustYannicc.
  • -
  • BlueBubbles: resolve short message IDs safely, expose full IDs in templates, and harden short-id fetch wrappers. (#1369, #1387) Thanks @tyler6204.
  • -
  • Models/Configure: inherit session model overrides in threads/topics, map OpenCode Zen models to the correct APIs, narrow Anthropic OAuth allowlist handling, seed allowlist fallbacks, list the full catalog when no allowlist is set, and limit /model list output. (#1376, #1416)
  • -
  • Memory: prevent CLI hangs by deferring vector probes, add sqlite-vec/embedding timeouts, and make session memory indexing async.
  • -
  • Cron: cap reminder context history to 10 messages and honor contextMessages. (#1103) Thanks @mkbehr.
  • -
  • Cache: restore the 1h cache TTL option and reset the pruning window.
  • -
  • Zalo Personal: tolerate ANSI/log-prefixed JSON output from zca. (#1379) Thanks @ptn1411.
  • -
  • Browser: suppress Chrome restore prompts for managed profiles. (#1419) Thanks @jamesgroat.
  • -
  • Infra: preserve fetch helper methods/preconnect when wrapping abort signals and normalize Telegram fetch aborts.
  • -
  • Config/Doctor: avoid stack traces for invalid configs, log the config path, avoid WhatsApp config resurrection, and warn when gateway.mode is unset. (#900)
  • -
  • CLI: read Codex CLI account_id for workspace billing. (#1422) Thanks @aj47.
  • -
  • Logs/Status: align rolling log filenames with local time and report sandboxed runtime in clawdbot status. (#1343)
  • -
  • Embedded runner: persist injected history images so attachments aren’t reloaded each turn. (#1374) Thanks @Nicell.
  • -
  • Nodes/Subagents: include agent/node/gateway context in tool failure logs and ensure subagent list uses the command session.
  • -
-

View full changelog

-]]>
- -
-
+ \ No newline at end of file diff --git a/docs/gateway/security.md b/docs/gateway/security.md index ed0054411..05e1673c6 100644 --- a/docs/gateway/security.md +++ b/docs/gateway/security.md @@ -63,6 +63,23 @@ downgrade—prefer HTTPS (Tailscale Serve) or open the UI on `127.0.0.1`. `clawdbot security audit` warns when this setting is enabled. +## Reverse Proxy Configuration + +If you run the Gateway behind a reverse proxy (nginx, Caddy, Traefik, etc.), you should configure `gateway.trustedProxies` for proper client IP detection. + +When the Gateway detects proxy headers (`X-Forwarded-For` or `X-Real-IP`) from an address that is **not** in `trustedProxies`, it will **not** treat connections as local clients. If gateway auth is disabled, those connections are rejected. This prevents authentication bypass where proxied connections would otherwise appear to come from localhost and receive automatic trust. + +```yaml +gateway: + trustedProxies: + - "127.0.0.1" # if your proxy runs on localhost + auth: + mode: password + password: ${CLAWDBOT_GATEWAY_PASSWORD} +``` + +When `trustedProxies` is configured, the Gateway will use `X-Forwarded-For` headers to determine the real client IP for local client detection. Make sure your proxy overwrites (not appends to) incoming `X-Forwarded-For` headers to prevent spoofing. + ## Local session logs live on disk Clawdbot stores session transcripts on disk under `~/.clawdbot/agents//sessions/*.jsonl`. diff --git a/docs/help/faq.md b/docs/help/faq.md index 4949f9292..7a5ca6ce8 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -105,6 +105,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [How can my agent access my computer if the Gateway is hosted remotely?](#how-can-my-agent-access-my-computer-if-the-gateway-is-hosted-remotely) - [Tailscale is connected but I get no replies. What now?](#tailscale-is-connected-but-i-get-no-replies-what-now) - [Can two Clawdbots talk to each other (local + VPS)?](#can-two-clawdbots-talk-to-each-other-local-vps) + - [Do I need separate VPSes for multiple agents](#do-i-need-separate-vpses-for-multiple-agents) - [Is there a benefit to using a node on my personal laptop instead of SSH from a VPS?](#is-there-a-benefit-to-using-a-node-on-my-personal-laptop-instead-of-ssh-from-a-vps) - [Do nodes run a gateway service?](#do-nodes-run-a-gateway-service) - [Is there an API / RPC way to apply config?](#is-there-an-api-rpc-way-to-apply-config) @@ -1463,6 +1464,16 @@ allowlists, or a "do not reply to bot messages" rule). Docs: [Remote access](/gateway/remote), [Agent CLI](/cli/agent), [Agent send](/tools/agent-send). +### Do I need separate VPSes for multiple agents + +No. One Gateway can host multiple agents, each with its own workspace, model defaults, +and routing. That is the normal setup and it is much cheaper and simpler than running +one VPS per agent. + +Use separate VPSes only when you need hard isolation (security boundaries) or very +different configs that you do not want to share. Otherwise, keep one Gateway and +use multiple agents or sub-agents. + ### Is there a benefit to using a node on my personal laptop instead of SSH from a VPS Yes - nodes are the first‑class way to reach your laptop from a remote Gateway, and they diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md index 8015ffe2e..d2d267661 100644 --- a/docs/platforms/mac/release.md +++ b/docs/platforms/mac/release.md @@ -30,17 +30,17 @@ Notes: # From repo root; set release IDs so Sparkle feed is enabled. # APP_BUILD must be numeric + monotonic for Sparkle compare. BUNDLE_ID=com.clawdbot.mac \ -APP_VERSION=2026.1.24 \ +APP_VERSION=2026.1.24-3 \ APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-app.sh # Zip for distribution (includes resource forks for Sparkle delta support) -ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.24.zip +ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.24-3.zip # Optional: also build a styled DMG for humans (drag to /Applications) -scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.24.dmg +scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.24-3.dmg # Recommended: build + notarize/staple zip + DMG # First, create a keychain profile once: @@ -48,26 +48,26 @@ scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.24.dmg # --apple-id "" --team-id "" --password "" NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \ BUNDLE_ID=com.clawdbot.mac \ -APP_VERSION=2026.1.24 \ +APP_VERSION=2026.1.24-3 \ APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-dist.sh # Optional: ship dSYM alongside the release -ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.24.dSYM.zip +ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.24-3.dSYM.zip ``` ## Appcast entry Use the release note generator so Sparkle renders formatted HTML notes: ```bash -SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.24.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml +SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.24-3.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml ``` Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry. Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing. ## Publish & verify -- Upload `Clawdbot-2026.1.24.zip` (and `Clawdbot-2026.1.24.dSYM.zip`) to the GitHub release for tag `v2026.1.24`. +- Upload `Clawdbot-2026.1.24-3.zip` (and `Clawdbot-2026.1.24-3.dSYM.zip`) to the GitHub release for tag `v2026.1.24-3`. - Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`. - Sanity checks: - `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200. diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 070abb1c3..6492bd469 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -17,7 +17,7 @@ When the operator says “release”, immediately do this preflight (no extra qu - Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed. 1) **Version & metadata** -- [ ] Bump `package.json` version (e.g., `1.1.0`). +- [ ] Bump `package.json` version (e.g., `2026.1.24`). - [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs. - [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts). - [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`. diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index 4385272be..925b05bc1 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/bluebubbles", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot BlueBubbles channel plugin", "clawdbot": { diff --git a/extensions/copilot-proxy/package.json b/extensions/copilot-proxy/package.json index 02d1cdbdd..792a94225 100644 --- a/extensions/copilot-proxy/package.json +++ b/extensions/copilot-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/copilot-proxy", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Copilot Proxy provider plugin", "clawdbot": { diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index 407ce60d1..2afc99e2e 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/diagnostics-otel", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot diagnostics OpenTelemetry exporter", "clawdbot": { diff --git a/extensions/discord/package.json b/extensions/discord/package.json index 0a645718b..dae5fe1f1 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/discord", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Discord channel plugin", "clawdbot": { diff --git a/extensions/google-antigravity-auth/package.json b/extensions/google-antigravity-auth/package.json index ff3c485f2..96bffde7c 100644 --- a/extensions/google-antigravity-auth/package.json +++ b/extensions/google-antigravity-auth/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/google-antigravity-auth", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Google Antigravity OAuth provider plugin", "clawdbot": { diff --git a/extensions/google-gemini-cli-auth/oauth.test.ts b/extensions/google-gemini-cli-auth/oauth.test.ts index d776d9be3..a6ee8ee98 100644 --- a/extensions/google-gemini-cli-auth/oauth.test.ts +++ b/extensions/google-gemini-cli-auth/oauth.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; -import { join } from "node:path"; +import { join, parse } from "node:path"; // Mock fs module before importing the module under test const mockExistsSync = vi.fn(); @@ -19,7 +19,9 @@ vi.mock("node:fs", async (importOriginal) => { }); describe("extractGeminiCliCredentials", () => { - const normalizePath = (value: string) => value.replace(/\\/g, "/"); + const normalizePath = (value: string) => + value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase(); + const rootDir = parse(process.cwd()).root || "/"; const FAKE_CLIENT_ID = "123456789-abcdef.apps.googleusercontent.com"; const FAKE_CLIENT_SECRET = "GOCSPX-FakeSecretValue123"; const FAKE_OAUTH2_CONTENT = ` @@ -49,11 +51,33 @@ describe("extractGeminiCliCredentials", () => { }); it("extracts credentials from oauth2.js in known path", async () => { - const fakeBinDir = "/fake/bin"; + const fakeBinDir = join(rootDir, "fake", "bin"); const fakeGeminiPath = join(fakeBinDir, "gemini"); - const fakeResolvedPath = "/fake/lib/node_modules/@google/gemini-cli/dist/index.js"; - const fakeOauth2Path = - "/fake/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"; + const fakeResolvedPath = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "dist", + "index.js", + ); + const fakeOauth2Path = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "node_modules", + "@google", + "gemini-cli-core", + "dist", + "src", + "code_assist", + "oauth2.js", + ); process.env.PATH = fakeBinDir; @@ -77,9 +101,18 @@ describe("extractGeminiCliCredentials", () => { }); it("returns null when oauth2.js cannot be found", async () => { - const fakeBinDir = "/fake/bin"; + const fakeBinDir = join(rootDir, "fake", "bin"); const fakeGeminiPath = join(fakeBinDir, "gemini"); - const fakeResolvedPath = "/fake/lib/node_modules/@google/gemini-cli/dist/index.js"; + const fakeResolvedPath = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "dist", + "index.js", + ); process.env.PATH = fakeBinDir; @@ -95,11 +128,33 @@ describe("extractGeminiCliCredentials", () => { }); it("returns null when oauth2.js lacks credentials", async () => { - const fakeBinDir = "/fake/bin"; + const fakeBinDir = join(rootDir, "fake", "bin"); const fakeGeminiPath = join(fakeBinDir, "gemini"); - const fakeResolvedPath = "/fake/lib/node_modules/@google/gemini-cli/dist/index.js"; - const fakeOauth2Path = - "/fake/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"; + const fakeResolvedPath = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "dist", + "index.js", + ); + const fakeOauth2Path = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "node_modules", + "@google", + "gemini-cli-core", + "dist", + "src", + "code_assist", + "oauth2.js", + ); process.env.PATH = fakeBinDir; @@ -118,11 +173,33 @@ describe("extractGeminiCliCredentials", () => { }); it("caches credentials after first extraction", async () => { - const fakeBinDir = "/fake/bin"; + const fakeBinDir = join(rootDir, "fake", "bin"); const fakeGeminiPath = join(fakeBinDir, "gemini"); - const fakeResolvedPath = "/fake/lib/node_modules/@google/gemini-cli/dist/index.js"; - const fakeOauth2Path = - "/fake/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"; + const fakeResolvedPath = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "dist", + "index.js", + ); + const fakeOauth2Path = join( + rootDir, + "fake", + "lib", + "node_modules", + "@google", + "gemini-cli", + "node_modules", + "@google", + "gemini-cli-core", + "dist", + "src", + "code_assist", + "oauth2.js", + ); process.env.PATH = fakeBinDir; diff --git a/extensions/google-gemini-cli-auth/package.json b/extensions/google-gemini-cli-auth/package.json index f4b666ab0..dc8a894d7 100644 --- a/extensions/google-gemini-cli-auth/package.json +++ b/extensions/google-gemini-cli-auth/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/google-gemini-cli-auth", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Gemini CLI OAuth provider plugin", "clawdbot": { diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index cf73b6795..056bdedb6 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/googlechat", - "version": "2026.1.22", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Google Chat channel plugin", "clawdbot": { @@ -34,6 +34,6 @@ "clawdbot": "workspace:*" }, "peerDependencies": { - "clawdbot": ">=2026.1.24-0" + "clawdbot": ">=2026.1.24" } } diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index a3ac1c642..79aa7890d 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/imessage", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot iMessage channel plugin", "clawdbot": { diff --git a/extensions/imessage/src/channel.ts b/extensions/imessage/src/channel.ts index 50615cd22..556c2970a 100644 --- a/extensions/imessage/src/channel.ts +++ b/extensions/imessage/src/channel.ts @@ -8,8 +8,10 @@ import { imessageOnboardingAdapter, IMessageConfigSchema, listIMessageAccountIds, + looksLikeIMessageTargetId, migrateBaseNameToDefaultAccount, normalizeAccountId, + normalizeIMessageMessagingTarget, PAIRING_APPROVED_MESSAGE, resolveChannelMediaMaxBytes, resolveDefaultIMessageAccountId, @@ -110,14 +112,9 @@ export const imessagePlugin: ChannelPlugin = { resolveToolPolicy: resolveIMessageGroupToolPolicy, }, messaging: { + normalizeTarget: normalizeIMessageMessagingTarget, targetResolver: { - looksLikeId: (raw) => { - const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(imessage:|chat_id:)/i.test(trimmed)) return true; - if (trimmed.includes("@")) return true; - return /^\+?\d{3,}$/.test(trimmed); - }, + looksLikeId: looksLikeIMessageTargetId, hint: "", }, }, diff --git a/extensions/line/package.json b/extensions/line/package.json index b58b2eb4d..b518f5ca5 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/line", - "version": "2026.1.22", + "version": "2026.1.24", "type": "module", "description": "Clawdbot LINE channel plugin", "clawdbot": { diff --git a/extensions/llm-task/package.json b/extensions/llm-task/package.json index e27384d9e..a03344d1a 100644 --- a/extensions/llm-task/package.json +++ b/extensions/llm-task/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/llm-task", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot JSON-only LLM task plugin", "clawdbot": { diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index ea774ecba..3926b553b 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/lobster", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)", "clawdbot": { diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index edf64c999..24529ee97 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/matrix", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Matrix channel plugin", "clawdbot": { diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 251fe7b0b..77d799c34 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/mattermost", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Mattermost channel plugin", "clawdbot": { diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index 2dd09751b..c70c2a63f 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/memory-core", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot core memory search plugin", "clawdbot": { @@ -9,6 +9,6 @@ ] }, "peerDependencies": { - "clawdbot": ">=2026.1.23-1" + "clawdbot": ">=2026.1.24" } } diff --git a/extensions/memory-lancedb/package.json b/extensions/memory-lancedb/package.json index 4f0e97377..80018044f 100644 --- a/extensions/memory-lancedb/package.json +++ b/extensions/memory-lancedb/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/memory-lancedb", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot LanceDB-backed long-term memory plugin with auto-recall/capture", "dependencies": { diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 80d566e7c..b336b80e6 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/msteams", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Microsoft Teams channel plugin", "clawdbot": { diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 5c6f5e243..bf5e443e5 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/nextcloud-talk", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Nextcloud Talk channel plugin", "clawdbot": { diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index b415ffe83..3a3e5ac56 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/nostr", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Nostr channel plugin for NIP-04 encrypted DMs", "clawdbot": { diff --git a/extensions/open-prose/package.json b/extensions/open-prose/package.json index 3fa6e8b17..873f3458a 100644 --- a/extensions/open-prose/package.json +++ b/extensions/open-prose/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/open-prose", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "OpenProse VM skill pack plugin (slash command + telemetry).", "clawdbot": { diff --git a/extensions/signal/package.json b/extensions/signal/package.json index 89de33544..034c65dea 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/signal", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Signal channel plugin", "clawdbot": { diff --git a/extensions/slack/package.json b/extensions/slack/package.json index f129515f5..73f2f6ecd 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/slack", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Slack channel plugin", "clawdbot": { diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index e4005c739..81b378df2 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/telegram", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Telegram channel plugin", "clawdbot": { diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 6fd64d03f..dca4f914d 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/tlon", - "version": "2026.1.22", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Tlon/Urbit channel plugin", "clawdbot": { diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index 248b0cb8b..840776c19 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/voice-call", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot voice-call plugin", "dependencies": { diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index 3dcc4cf6b..8e18af842 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/whatsapp", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot WhatsApp channel plugin", "clawdbot": { diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index 7ced3106a..a3a87a878 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/zalo", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Zalo channel plugin", "clawdbot": { diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index 9f406c56c..513295b46 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -1,6 +1,6 @@ { "name": "@clawdbot/zalouser", - "version": "2026.1.23", + "version": "2026.1.24", "type": "module", "description": "Clawdbot Zalo Personal Account plugin via zca-cli", "dependencies": { diff --git a/package.json b/package.json index 3e908b3e1..5d77e25d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clawdbot", - "version": "2026.1.24-0", + "version": "2026.1.24-3", "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent", "type": "module", "main": "dist/index.js", @@ -33,6 +33,7 @@ "dist/macos/**", "dist/media/**", "dist/media-understanding/**", + "dist/link-understanding/**", "dist/process/**", "dist/plugins/**", "dist/plugin-sdk/**", @@ -64,6 +65,7 @@ "git-hooks/**", "dist/terminal/**", "dist/routing/**", + "dist/shared/**", "dist/utils/**", "dist/logging/**", "dist/memory/**", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8adb028b..781a461a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -357,8 +357,8 @@ importers: extensions/memory-core: dependencies: clawdbot: - specifier: '>=2026.1.23-1' - version: 2026.1.23-1(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3) + specifier: '>=2026.1.24' + version: link:../.. extensions/memory-lancedb: dependencies: @@ -3124,11 +3124,6 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - clawdbot@2026.1.23-1: - resolution: {integrity: sha512-t51ks5bnTRQNCzoTunUJaoeMjamvP3zP5EyyadmI34kXYGIbWcCx242w5XMr5h4sLSw59nBw3lJ74vErWDsz9w==} - engines: {node: '>=22.12.0'} - hasBin: true - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -8867,82 +8862,6 @@ snapshots: dependencies: clsx: 2.1.1 - clawdbot@2026.1.23-1(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3): - dependencies: - '@agentclientprotocol/sdk': 0.13.1(zod@4.3.6) - '@aws-sdk/client-bedrock': 3.975.0 - '@buape/carbon': 0.14.0(hono@4.11.4) - '@clack/prompts': 0.11.0 - '@grammyjs/runner': 2.0.3(grammy@1.39.3) - '@grammyjs/transformer-throttler': 1.2.1(grammy@1.39.3) - '@homebridge/ciao': 1.3.4 - '@lydell/node-pty': 1.2.0-beta.3 - '@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-coding-agent': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-tui': 0.49.3 - '@mozilla/readability': 0.6.0 - '@sinclair/typebox': 0.34.47 - '@slack/bolt': 4.6.0(@types/express@5.0.6) - '@slack/web-api': 7.13.0 - '@whiskeysockets/baileys': 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5) - ajv: 8.17.1 - body-parser: 2.2.2 - chalk: 5.6.2 - chokidar: 5.0.0 - chromium-bidi: 13.0.1(devtools-protocol@0.0.1561482) - cli-highlight: 2.1.11 - commander: 14.0.2 - croner: 9.1.0 - detect-libc: 2.1.2 - discord-api-types: 0.38.37 - dotenv: 17.2.3 - express: 5.2.1 - file-type: 21.3.0 - grammy: 1.39.3 - hono: 4.11.4 - jiti: 2.6.1 - json5: 2.2.3 - jszip: 3.10.1 - linkedom: 0.18.12 - long: 5.3.2 - markdown-it: 14.1.0 - osc-progress: 0.3.0 - pdfjs-dist: 5.4.530 - playwright-core: 1.58.0 - proper-lockfile: 4.1.2 - qrcode-terminal: 0.12.0 - sharp: 0.34.5 - sqlite-vec: 0.1.7-alpha.2 - tar: 7.5.4 - tslog: 4.10.2 - undici: 7.19.0 - ws: 8.19.0 - yaml: 2.8.2 - zod: 4.3.6 - optionalDependencies: - '@napi-rs/canvas': 0.1.88 - node-llama-cpp: 3.15.0(typescript@5.9.3) - transitivePeerDependencies: - - '@discordjs/opus' - - '@modelcontextprotocol/sdk' - - '@types/express' - - audio-decode - - aws-crt - - bufferutil - - canvas - - debug - - devtools-protocol - - encoding - - ffmpeg-static - - jimp - - link-preview-js - - node-opus - - opusscript - - supports-color - - typescript - - utf-8-validate - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 diff --git a/scripts/clawtributors-map.json b/scripts/clawtributors-map.json index 7ad1f926c..8899afc93 100644 --- a/scripts/clawtributors-map.json +++ b/scripts/clawtributors-map.json @@ -2,6 +2,7 @@ "ensureLogins": [ "odrobnik", "alphonse-arianee", + "aaronn", "ronak-guliani", "cpojer", "carlulsoe", diff --git a/src/channels/plugins/normalize/imessage.test.ts b/src/channels/plugins/normalize/imessage.test.ts new file mode 100644 index 000000000..afb2ec358 --- /dev/null +++ b/src/channels/plugins/normalize/imessage.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from "vitest"; + +import { normalizeIMessageMessagingTarget } from "./imessage.js"; + +describe("imessage target normalization", () => { + it("preserves service prefixes for handles", () => { + expect(normalizeIMessageMessagingTarget("sms:+1 (555) 222-3333")).toBe("sms:+15552223333"); + }); + + it("drops service prefixes for chat targets", () => { + expect(normalizeIMessageMessagingTarget("sms:chat_id:123")).toBe("chat_id:123"); + expect(normalizeIMessageMessagingTarget("imessage:CHAT_GUID:abc")).toBe("chat_guid:abc"); + expect(normalizeIMessageMessagingTarget("auto:ChatIdentifier:foo")).toBe("chat_identifier:foo"); + }); +}); diff --git a/src/channels/plugins/normalize/imessage.ts b/src/channels/plugins/normalize/imessage.ts new file mode 100644 index 000000000..ec04d6557 --- /dev/null +++ b/src/channels/plugins/normalize/imessage.ts @@ -0,0 +1,35 @@ +import { normalizeIMessageHandle } from "../../../imessage/targets.js"; + +// Service prefixes that indicate explicit delivery method; must be preserved during normalization +const SERVICE_PREFIXES = ["imessage:", "sms:", "auto:"] as const; +const CHAT_TARGET_PREFIX_RE = + /^(chat_id:|chatid:|chat:|chat_guid:|chatguid:|guid:|chat_identifier:|chatidentifier:|chatident:)/i; + +export function normalizeIMessageMessagingTarget(raw: string): string | undefined { + const trimmed = raw.trim(); + if (!trimmed) return undefined; + + // Preserve service prefix if present (e.g., "sms:+1555" → "sms:+15551234567") + const lower = trimmed.toLowerCase(); + for (const prefix of SERVICE_PREFIXES) { + if (lower.startsWith(prefix)) { + const remainder = trimmed.slice(prefix.length).trim(); + const normalizedHandle = normalizeIMessageHandle(remainder); + if (!normalizedHandle) return undefined; + if (CHAT_TARGET_PREFIX_RE.test(normalizedHandle)) return normalizedHandle; + return `${prefix}${normalizedHandle}`; + } + } + + const normalized = normalizeIMessageHandle(trimmed); + return normalized || undefined; +} + +export function looksLikeIMessageTargetId(raw: string): boolean { + const trimmed = raw.trim(); + if (!trimmed) return false; + if (/^(imessage:|sms:|auto:)/i.test(trimmed)) return true; + if (CHAT_TARGET_PREFIX_RE.test(trimmed)) return true; + if (trimmed.includes("@")) return true; + return /^\+?\d{3,}$/.test(trimmed); +} diff --git a/src/gateway/server.auth.e2e.test.ts b/src/gateway/server.auth.e2e.test.ts index a2645a75d..17a8802b2 100644 --- a/src/gateway/server.auth.e2e.test.ts +++ b/src/gateway/server.auth.e2e.test.ts @@ -351,6 +351,27 @@ describe("gateway server auth/connect", () => { } }); + test("rejects proxied connections without auth when proxy headers are untrusted", async () => { + const prevToken = process.env.CLAWDBOT_GATEWAY_TOKEN; + delete process.env.CLAWDBOT_GATEWAY_TOKEN; + const port = await getFreePort(); + const server = await startGatewayServer(port); + const ws = new WebSocket(`ws://127.0.0.1:${port}`, { + headers: { "x-forwarded-for": "203.0.113.10" }, + }); + await new Promise((resolve) => ws.once("open", resolve)); + const res = await connectReq(ws); + expect(res.ok).toBe(false); + expect(res.error?.message ?? "").toContain("gateway auth required"); + ws.close(); + await server.close(); + if (prevToken === undefined) { + delete process.env.CLAWDBOT_GATEWAY_TOKEN; + } else { + process.env.CLAWDBOT_GATEWAY_TOKEN = prevToken; + } + }); + test("accepts device token auth for paired device", async () => { const { loadOrCreateDeviceIdentity } = await import("../infra/device-identity.js"); const { approveDevicePairing, getPairedDevice, listDevicePairing } = diff --git a/src/gateway/server/ws-connection/message-handler.ts b/src/gateway/server/ws-connection/message-handler.ts index 5ef3f26e7..35265ce63 100644 --- a/src/gateway/server/ws-connection/message-handler.ts +++ b/src/gateway/server/ws-connection/message-handler.ts @@ -26,7 +26,7 @@ import type { ResolvedGatewayAuth } from "../../auth.js"; import { authorizeGatewayConnect } from "../../auth.js"; import { loadConfig } from "../../../config/config.js"; import { buildDeviceAuthPayload } from "../../device-auth.js"; -import { isLocalGatewayAddress, resolveGatewayClientIp } from "../../net.js"; +import { isLocalGatewayAddress, isTrustedProxyAddress, resolveGatewayClientIp } from "../../net.js"; import { resolveNodeCommandAllowlist } from "../../node-command-policy.js"; import { type ConnectParams, @@ -177,7 +177,24 @@ export function attachGatewayWsMessageHandler(params: { const configSnapshot = loadConfig(); const trustedProxies = configSnapshot.gateway?.trustedProxies ?? []; const clientIp = resolveGatewayClientIp({ remoteAddr, forwardedFor, realIp, trustedProxies }); - const isLocalClient = isLocalGatewayAddress(clientIp); + + // If proxy headers are present but the remote address isn't trusted, don't treat + // the connection as local. This prevents auth bypass when running behind a reverse + // proxy without proper configuration - the proxy's loopback connection would otherwise + // cause all external requests to be treated as trusted local clients. + const hasProxyHeaders = Boolean(forwardedFor || realIp); + const remoteIsTrustedProxy = isTrustedProxyAddress(remoteAddr, trustedProxies); + const hasUntrustedProxyHeaders = hasProxyHeaders && !remoteIsTrustedProxy; + const isLocalClient = !hasUntrustedProxyHeaders && isLocalGatewayAddress(clientIp); + const reportedClientIp = hasUntrustedProxyHeaders ? undefined : clientIp; + + if (hasUntrustedProxyHeaders) { + logWsControl.warn( + "Proxy headers detected from untrusted address. " + + "Connection will not be treated as local. " + + "Configure gateway.trustedProxies to restore local client detection behind your proxy.", + ); + } const isWebchatConnect = (p: ConnectParams | null | undefined) => isWebchatClient(p?.client); @@ -322,6 +339,31 @@ export function attachGatewayWsMessageHandler(params: { const isControlUi = connectParams.client.id === GATEWAY_CLIENT_IDS.CONTROL_UI; const allowInsecureControlUi = isControlUi && configSnapshot.gateway?.controlUi?.allowInsecureAuth === true; + if (hasUntrustedProxyHeaders && resolvedAuth.mode === "none") { + setHandshakeState("failed"); + setCloseCause("proxy-auth-required", { + client: connectParams.client.id, + clientDisplayName: connectParams.client.displayName, + mode: connectParams.client.mode, + version: connectParams.client.version, + }); + send({ + type: "res", + id: frame.id, + ok: false, + error: errorShape( + ErrorCodes.INVALID_REQUEST, + "gateway auth required behind reverse proxy", + { + details: { + hint: "set gateway.auth or configure gateway.trustedProxies", + }, + }, + ), + }); + close(1008, "gateway auth required"); + return; + } if (!device) { const canSkipDevice = allowInsecureControlUi ? hasSharedAuth : hasTokenAuth; @@ -581,7 +623,7 @@ export function attachGatewayWsMessageHandler(params: { clientMode: connectParams.client.mode, role, scopes, - remoteIp: clientIp, + remoteIp: reportedClientIp, silent: isLocalClient, }); const context = buildRequestContext(); @@ -665,7 +707,7 @@ export function attachGatewayWsMessageHandler(params: { clientMode: connectParams.client.mode, role, scopes, - remoteIp: clientIp, + remoteIp: reportedClientIp, }); } } @@ -714,7 +756,7 @@ export function attachGatewayWsMessageHandler(params: { if (presenceKey) { upsertPresence(presenceKey, { host: connectParams.client.displayName ?? connectParams.client.id ?? os.hostname(), - ip: isLocalClient ? undefined : clientIp, + ip: isLocalClient ? undefined : reportedClientIp, version: connectParams.client.version, platform: connectParams.client.platform, deviceFamily: connectParams.client.deviceFamily, @@ -773,7 +815,9 @@ export function attachGatewayWsMessageHandler(params: { setHandshakeState("connected"); if (role === "node") { const context = buildRequestContext(); - const nodeSession = context.nodeRegistry.register(nextClient, { remoteIp: clientIp }); + const nodeSession = context.nodeRegistry.register(nextClient, { + remoteIp: reportedClientIp, + }); const instanceIdRaw = connectParams.client.instanceId; const instanceId = typeof instanceIdRaw === "string" ? instanceIdRaw.trim() : ""; const nodeIdsForPairing = new Set([nodeSession.nodeId]); diff --git a/src/imessage/targets.test.ts b/src/imessage/targets.test.ts index 956dfa321..6350167a3 100644 --- a/src/imessage/targets.test.ts +++ b/src/imessage/targets.test.ts @@ -28,6 +28,27 @@ describe("imessage targets", () => { expect(normalizeIMessageHandle(" +1 (555) 222-3333 ")).toBe("+15552223333"); }); + it("normalizes chat_id prefixes case-insensitively", () => { + expect(normalizeIMessageHandle("CHAT_ID:123")).toBe("chat_id:123"); + expect(normalizeIMessageHandle("Chat_Id:456")).toBe("chat_id:456"); + expect(normalizeIMessageHandle("chatid:789")).toBe("chat_id:789"); + expect(normalizeIMessageHandle("CHAT:42")).toBe("chat_id:42"); + }); + + it("normalizes chat_guid prefixes case-insensitively", () => { + expect(normalizeIMessageHandle("CHAT_GUID:abc-def")).toBe("chat_guid:abc-def"); + expect(normalizeIMessageHandle("ChatGuid:XYZ")).toBe("chat_guid:XYZ"); + expect(normalizeIMessageHandle("GUID:test-guid")).toBe("chat_guid:test-guid"); + }); + + it("normalizes chat_identifier prefixes case-insensitively", () => { + expect(normalizeIMessageHandle("CHAT_IDENTIFIER:iMessage;-;chat123")).toBe( + "chat_identifier:iMessage;-;chat123", + ); + expect(normalizeIMessageHandle("ChatIdentifier:test")).toBe("chat_identifier:test"); + expect(normalizeIMessageHandle("CHATIDENT:foo")).toBe("chat_identifier:foo"); + }); + it("checks allowFrom against chat_id", () => { const ok = isAllowedIMessageSender({ allowFrom: ["chat_id:9"], diff --git a/src/imessage/targets.ts b/src/imessage/targets.ts index befb3f6d6..03fdcf306 100644 --- a/src/imessage/targets.ts +++ b/src/imessage/targets.ts @@ -34,6 +34,27 @@ export function normalizeIMessageHandle(raw: string): string { if (lowered.startsWith("imessage:")) return normalizeIMessageHandle(trimmed.slice(9)); if (lowered.startsWith("sms:")) return normalizeIMessageHandle(trimmed.slice(4)); if (lowered.startsWith("auto:")) return normalizeIMessageHandle(trimmed.slice(5)); + + // Normalize chat_id/chat_guid/chat_identifier prefixes case-insensitively + for (const prefix of CHAT_ID_PREFIXES) { + if (lowered.startsWith(prefix)) { + const value = trimmed.slice(prefix.length).trim(); + return `chat_id:${value}`; + } + } + for (const prefix of CHAT_GUID_PREFIXES) { + if (lowered.startsWith(prefix)) { + const value = trimmed.slice(prefix.length).trim(); + return `chat_guid:${value}`; + } + } + for (const prefix of CHAT_IDENTIFIER_PREFIXES) { + if (lowered.startsWith(prefix)) { + const value = trimmed.slice(prefix.length).trim(); + return `chat_identifier:${value}`; + } + } + if (trimmed.includes("@")) return trimmed.toLowerCase(); const normalized = normalizeE164(trimmed); if (normalized) return normalized; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 3e213746f..60782ff6d 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -197,12 +197,6 @@ export { } from "../channels/plugins/setup-helpers.js"; export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; -export { - listIMessageAccountIds, - resolveDefaultIMessageAccountId, - resolveIMessageAccount, - type ResolvedIMessageAccount, -} from "../imessage/accounts.js"; export type { ChannelOnboardingAdapter, @@ -210,7 +204,6 @@ export type { } from "../channels/plugins/onboarding-types.js"; export { addWildcardAllowFrom, promptAccountId } from "../channels/plugins/onboarding/helpers.js"; export { promptChannelAccessConfig } from "../channels/plugins/onboarding/channel-access.js"; -export { imessageOnboardingAdapter } from "../channels/plugins/onboarding/imessage.js"; export { createActionGate, @@ -264,6 +257,19 @@ export { } from "../channels/plugins/normalize/discord.js"; export { collectDiscordStatusIssues } from "../channels/plugins/status-issues/discord.js"; +// Channel: iMessage +export { + listIMessageAccountIds, + resolveDefaultIMessageAccountId, + resolveIMessageAccount, + type ResolvedIMessageAccount, +} from "../imessage/accounts.js"; +export { imessageOnboardingAdapter } from "../channels/plugins/onboarding/imessage.js"; +export { + looksLikeIMessageTargetId, + normalizeIMessageMessagingTarget, +} from "../channels/plugins/normalize/imessage.js"; + // Channel: Slack export { listEnabledSlackAccounts, diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index cd7df057e..2ee7e27ee 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -53,6 +53,55 @@ describe("security audit", () => { ).toBe(true); }); + it("warns when loopback control UI lacks trusted proxies", async () => { + const cfg: ClawdbotConfig = { + gateway: { + bind: "loopback", + controlUi: { enabled: true }, + }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect(res.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + checkId: "gateway.trusted_proxies_missing", + severity: "warn", + }), + ]), + ); + }); + + it("flags loopback control UI without auth as critical", async () => { + const cfg: ClawdbotConfig = { + gateway: { + bind: "loopback", + controlUi: { enabled: true }, + auth: { mode: "none" as any }, + }, + }; + + const res = await runSecurityAudit({ + config: cfg, + includeFilesystem: false, + includeChannelSecurity: false, + }); + + expect(res.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + checkId: "gateway.loopback_no_auth", + severity: "critical", + }), + ]), + ); + }); + it("flags logging.redactSensitive=off", async () => { const cfg: ClawdbotConfig = { logging: { redactSensitive: "off" }, diff --git a/src/security/audit.ts b/src/security/audit.ts index 87e6e3397..3695cf049 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -207,6 +207,10 @@ function collectGatewayConfigFindings(cfg: ClawdbotConfig): SecurityAuditFinding const bind = typeof cfg.gateway?.bind === "string" ? cfg.gateway.bind : "loopback"; const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off"; const auth = resolveGatewayAuth({ authConfig: cfg.gateway?.auth, tailscaleMode }); + const controlUiEnabled = cfg.gateway?.controlUi?.enabled !== false; + const trustedProxies = Array.isArray(cfg.gateway?.trustedProxies) + ? cfg.gateway.trustedProxies + : []; if (bind !== "loopback" && auth.mode === "none") { findings.push({ @@ -218,6 +222,32 @@ function collectGatewayConfigFindings(cfg: ClawdbotConfig): SecurityAuditFinding }); } + if (bind === "loopback" && controlUiEnabled && trustedProxies.length === 0) { + findings.push({ + checkId: "gateway.trusted_proxies_missing", + severity: "warn", + title: "Reverse proxy headers are not trusted", + detail: + "gateway.bind is loopback and gateway.trustedProxies is empty. " + + "If you expose the Control UI through a reverse proxy, configure trusted proxies " + + "so local-client checks cannot be spoofed.", + remediation: + "Set gateway.trustedProxies to your proxy IPs or keep the Control UI local-only.", + }); + } + + if (bind === "loopback" && controlUiEnabled && auth.mode === "none") { + findings.push({ + checkId: "gateway.loopback_no_auth", + severity: "critical", + title: "Gateway auth disabled on loopback", + detail: + "gateway.bind is loopback and gateway.auth is disabled. " + + "If the Control UI is exposed through a reverse proxy, unauthenticated access is possible.", + remediation: "Set gateway.auth (token recommended) or keep the Control UI local-only.", + }); + } + if (tailscaleMode === "funnel") { findings.push({ checkId: "gateway.tailscale_funnel",