Skip to content

[Fix] Inbound image attachments silently fail in BlueBubbles and Slack — SSRF guard dispatcher incompatible with Node native fetch #62530

@philiptome-canada

Description

@philiptome-canada

Sharing a fix for others hitting silent image download failures in BlueBubbles (iMessage) and Slack channels. Took a full day of debugging to find — posting here so others don't have to.

Symptom

Agent receives messages with image attachments but never saves them to ~/.openclaw/media/inbound/. No visible error. Agent replies saying it can't see the image. This affects both BlueBubbles and Slack on macOS, OpenClaw v2026.4.5.

Root Cause

The fetchRemoteMedia helper creates an undici dispatcher via the SSRF guard and passes it as part of init to the fetchImpl callback. When that reaches Node's native fetch(), it throws:

fetch failed | invalid onRequestStart method

This is silently swallowed by the surrounding try/catch. Nothing is logged at default log levels.

Fix

Replace fetchRemoteMedia + SSRF guard with plain fetch() + AbortController in two places:

1. BlueBubbles (reactions-*.jsdownloadBlueBubblesAttachment)

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 30000);
try {
  const response = await fetch(url, { method: 'GET', signal: controller.signal });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const buf = await response.arrayBuffer();
  return { buffer: new Uint8Array(buf), contentType: response.headers.get('content-type') ?? attachment.mimeType };
} finally { clearTimeout(timer); }

Safe because dangerouslyAllowPrivateNetwork: true is already in config for local network access.

2. Slack (actions-*.jsresolveSlackMedia)

Same pattern but add the Bearer token header:

const response = await fetch(url, {
  method: 'GET',
  headers: { Authorization: `Bearer ${params.token}` },
  redirect: 'follow',
  signal: controller.signal
});

3. Also required for BlueBubbles (channel.runtime-*.js)

Add allowPrivateNetwork to the downloadBlueBubblesAttachment call opts — it was being silently dropped:

const downloaded = await downloadBlueBubblesAttachment(attachment, {
  cfg: config,
  accountId: account.accountId,
  maxBytes,
  allowPrivateNetwork: isPrivateNetworkOptInEnabled(account.config)  // ← add this
});

Verified

Both iMessage (BlueBubbles, Private API disabled) and Slack image attachments now download correctly and are described by the agent.

Related

Original bug report with full diagnosis trail: #62248

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions