openclaw - ✅(Solved) Fix bluebubbles: tighten SSRF guard on API fetches (bypass via undefined SsrFPolicy) [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#67752Fetched 2026-04-17 08:29:27
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Participants
Timeline (top)
labeled ×1

Aisle security analysis on #67510 flagged (Medium / CWE-918) that several BlueBubbles fetch call-sites currently pass undefined for ssrfPolicy to explicitly take the non-SSRF fallback path in blueBubblesFetchWithTimeout. That path intentionally skips fetchWithSsrFGuard to support self-hosted deployments on localhost — but in doing so it also drops DNS pinning, private-IP blocking, and redirect controls for any request to the configured BlueBubbles baseUrl.

The design tradeoff was deliberate (localhost BB is the common case, and default SSRF policy blocks private IPs), but the Aisle report correctly notes that a safer pattern exists and is already used in downloadBlueBubblesAttachment:

ssrfPolicy: allowPrivateNetwork
  ? { allowPrivateNetwork: true }
  : trustedHostname && (allowPrivateNetworkConfig !== false || !trustedHostnameIsPrivate)
    ? { allowedHostnames: [trustedHostname] }
    : undefined,

This keeps SSRF protections scoped to only the configured hostname, which is almost always what the user actually wants — localhost works, but redirects/rebinding to anything else still get blocked.

Root Cause

  • Flagged by Aisle in this PR comment on #67510.
  • Deferred from #67510 because the PR's focus was the attachment-fetch Node 22 break, and the shared policy refactor is larger in surface area than the fix warranted.
  • No user-reported exploit; concern is defense-in-depth for any setup where the BB serverUrl could be influenced by an attacker (multi-tenant, social-engineered config).

PR fix notes

PR #68234: bluebubbles: consolidate HTTP traffic through typed BlueBubblesClient

Description (problem / solution / changelog)

Draft — not yet ready for merge. Opening for early review feedback; will also smoke-test on Lobster before marking ready.

Summary

All outbound BlueBubbles HTTP calls now route through a single typed BlueBubblesClient that resolves SSRF policy and auth material once at construction. Previously each of the 17 callsites computed its own SsrFPolicy and threaded it through blueBubblesFetchWithTimeout — the per-callsite divergence is what caused #34749 (image attachments blocked on localhost) and #59722 (reactions blocked on private IPs).

What closes

  • Closes #34749 — image attachments blocked by SSRF guard (localhost). downloadAttachment now threads this.ssrfPolicy to BOTH fetchRemoteMedia AND the fetchImpl callback, where the legacy helper silently omitted the policy on the callback path.
  • Closes #59722 — reactions not covered by allowlist. reactions.ts now uses the same policy as every other call; the asymmetric {} fallback that blocked private-IP BB deployments is gone.

How

New extensions/bluebubbles/src/client.ts:

  • BlueBubblesClient class with typed operations (request, requestJson, requestMultipart, ping, getServerInfo, react, getMessageAttachments, downloadAttachment).
  • createBlueBubblesClient({cfg, accountId, serverUrl, password, ...}) for full-config callers.
  • createBlueBubblesClientFromParts({baseUrl, password, allowPrivateNetwork, ...}) for low-level helpers that already have the resolved tuple.
  • Three-mode SSRF policy resolved once at construction (previously inlined in attachments.ts only):
    1. { allowPrivateNetwork: true } — user opted in
    2. { allowedHostnames: [trustedHost] } — narrow allowlist
    3. undefined — non-SSRF fallback path
  • Pluggable BlueBubblesAuthStrategy with blueBubblesQueryStringAuth (default, preserves current ?password=) and blueBubblesHeaderAuth so the #66869 header-auth cutover becomes a one-line default flip.

Migrated: attachments.ts, reactions.ts, probe.ts, chat.ts, send.ts, history.ts, catchup.ts, monitor-processing.ts all now route through the client. Public function signatures preserved for compatibility.

Test fixtures: monitor.test.ts and monitor.webhook-auth.test.ts install the shared SSRF-guard passthrough (createBlueBubblesFetchGuardPassthroughInstaller) — required because the consolidation moves callers from the unguarded fallback onto the mode-2 allowlist for self-hosted BB servers.

Size

+171 / -310 LOC across BB files (139-line net reduction). 28 new tests in client.test.ts, full BB suite stays at 462/462.

Verification

  • pnpm test extensions/bluebubbles — 22 files / 462 tests pass
  • pnpm lint extensions/bluebubbles/src/ — 0 warnings / 0 errors (82 files)
  • pnpm tsgo — pre-existing unrelated errors on origin/main (codex/discord/qa-lab/src/commands); none in bluebubbles
  • pnpm build — pre-existing amazon-bedrock-mantle runtime-deps staging error on clean main; unrelated
  • Smoke test on Lobster before marking ready for review

Deferred to follow-ups (intentionally NOT in this PR)

  • #66869 header auth cutoverAuthStrategy interface is in place; flipping the default requires BB Server coordination.
  • #67752 SSRF tightening — centralized policy resolution makes this a single-point-of-change follow-up.
  • Delete blueBubblesFetchWithTimeout / buildBlueBubblesApiUrl from types.ts — they're now private-to-client infrastructure; safe to remove once this lands.

Blocks #57181, #60715 (both become instance-level tests against BlueBubblesClient once this lands).

🤖 Generated with Claude Code

Changed files

  • extensions/bluebubbles/src/attachments.test.ts (modified, +5/-1)
  • extensions/bluebubbles/src/attachments.ts (modified, +32/-117)
  • extensions/bluebubbles/src/catchup.ts (modified, +17/-22)
  • extensions/bluebubbles/src/chat.ts (modified, +19/-49)
  • extensions/bluebubbles/src/client.test.ts (added, +613/-0)
  • extensions/bluebubbles/src/client.ts (added, +570/-0)
  • extensions/bluebubbles/src/history.ts (modified, +12/-9)
  • extensions/bluebubbles/src/monitor-processing.ts (modified, +13/-20)
  • extensions/bluebubbles/src/monitor.test.ts (modified, +11/-0)
  • extensions/bluebubbles/src/monitor.webhook-auth.test.ts (modified, +7/-0)
  • extensions/bluebubbles/src/multipart.ts (modified, +17/-3)
  • extensions/bluebubbles/src/probe.ts (modified, +15/-17)
  • extensions/bluebubbles/src/reactions.ts (modified, +9/-26)
  • extensions/bluebubbles/src/send.ts (modified, +33/-50)

Code Example

ssrfPolicy: allowPrivateNetwork
  ? { allowPrivateNetwork: true }
  : trustedHostname && (allowPrivateNetworkConfig !== false || !trustedHostnameIsPrivate)
    ? { allowedHostnames: [trustedHostname] }
    : undefined,
RAW_BUFFERClick to expand / collapse

Summary

Aisle security analysis on #67510 flagged (Medium / CWE-918) that several BlueBubbles fetch call-sites currently pass undefined for ssrfPolicy to explicitly take the non-SSRF fallback path in blueBubblesFetchWithTimeout. That path intentionally skips fetchWithSsrFGuard to support self-hosted deployments on localhost — but in doing so it also drops DNS pinning, private-IP blocking, and redirect controls for any request to the configured BlueBubbles baseUrl.

The design tradeoff was deliberate (localhost BB is the common case, and default SSRF policy blocks private IPs), but the Aisle report correctly notes that a safer pattern exists and is already used in downloadBlueBubblesAttachment:

ssrfPolicy: allowPrivateNetwork
  ? { allowPrivateNetwork: true }
  : trustedHostname && (allowPrivateNetworkConfig !== false || !trustedHostnameIsPrivate)
    ? { allowedHostnames: [trustedHostname] }
    : undefined,

This keeps SSRF protections scoped to only the configured hostname, which is almost always what the user actually wants — localhost works, but redirects/rebinding to anything else still get blocked.

Call-sites to migrate

  • extensions/bluebubbles/src/attachments.tsblueBubblesPolicy() helper + its caller sendBlueBubblesAttachment (outbound path)
  • extensions/bluebubbles/src/monitor-processing.tsblueBubblesPolicy() helper used by fetchBlueBubblesParticipantsForInboundMessage and the attachment retry path (fetchBlueBubblesMessageAttachments)
  • extensions/bluebubbles/src/send.ts, chat.ts, probe.ts, reactions.ts, multipart.ts, catchup.ts — each has their own copy of the blueBubblesPolicy pattern, all currently routing localhost through the unguarded fallback in one form or another.

Worth a single pass that centralises a shared resolveBlueBubblesSsrFPolicy({ baseUrl, allowPrivateNetwork }) helper returning { allowedHostnames: [hostname] } by default, { allowPrivateNetwork: true } when explicitly opted in.

Context

  • Flagged by Aisle in this PR comment on #67510.
  • Deferred from #67510 because the PR's focus was the attachment-fetch Node 22 break, and the shared policy refactor is larger in surface area than the fix warranted.
  • No user-reported exploit; concern is defense-in-depth for any setup where the BB serverUrl could be influenced by an attacker (multi-tenant, social-engineered config).

Acceptance

  • All BlueBubbles fetch call-sites use a policy that scopes SSRF to the configured baseUrl hostname, not undefined.
  • Localhost / same-host / private-IP deployments continue to work without dangerouslyAllowPrivateNetwork: true when the user has configured the server to that host.
  • lint:tmp:no-raw-channel-fetch stays clean (no new raw-fetch call-sites needed).

extent analysis

TL;DR

Implement a shared resolveBlueBubblesSsrFPolicy helper to centralize SSRF policy resolution for BlueBubbles fetch call-sites.

Guidance

  • Identify and migrate all call-sites listed in the issue to use the proposed resolveBlueBubblesSsrFPolicy helper.
  • Verify that the new policy helper correctly scopes SSRF protections to the configured baseUrl hostname.
  • Ensure localhost and private-IP deployments continue to work without requiring dangerouslyAllowPrivateNetwork: true.
  • Review the blueBubblesPolicy pattern in each affected file to ensure consistency with the new shared helper.

Example

const resolveBlueBubblesSsrFPolicy = ({ baseUrl, allowPrivateNetwork }) => {
  if (allowPrivateNetwork) {
    return { allowPrivateNetwork: true };
  }
  const hostname = new URL(baseUrl).hostname;
  return { allowedHostnames: [hostname] };
};

Notes

The provided example is a minimal implementation of the proposed resolveBlueBubblesSsrFPolicy helper. The actual implementation may vary depending on the specific requirements and constraints of the project.

Recommendation

Apply the workaround by implementing the shared resolveBlueBubblesSsrFPolicy helper to centralize SSRF policy resolution and improve defense-in-depth for BlueBubbles fetch call-sites. This approach ensures that SSRF protections are consistently applied across all call-sites, reducing the risk of potential exploits.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix bluebubbles: tighten SSRF guard on API fetches (bypass via undefined SsrFPolicy) [1 pull requests, 1 participants]