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.ts — blueBubblesPolicy() helper + its caller sendBlueBubblesAttachment (outbound path)
extensions/bluebubbles/src/monitor-processing.ts — blueBubblesPolicy() 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
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).
Summary
Aisle security analysis on #67510 flagged (Medium / CWE-918) that several BlueBubbles fetch call-sites currently pass
undefinedforssrfPolicyto explicitly take the non-SSRF fallback path inblueBubblesFetchWithTimeout. That path intentionally skipsfetchWithSsrFGuardto 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 BlueBubblesbaseUrl.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: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.ts—blueBubblesPolicy()helper + its callersendBlueBubblesAttachment(outbound path)extensions/bluebubbles/src/monitor-processing.ts—blueBubblesPolicy()helper used byfetchBlueBubblesParticipantsForInboundMessageand 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 theblueBubblesPolicypattern, 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
serverUrlcould be influenced by an attacker (multi-tenant, social-engineered config).Acceptance
baseUrlhostname, notundefined.dangerouslyAllowPrivateNetwork: truewhen the user has configured the server to that host.lint:tmp:no-raw-channel-fetchstays clean (no new raw-fetch call-sites needed).