Skip to content

fix(discord,slack): add SSRF policy for media downloads in proxy environments#25475

Merged
Takhoffman merged 2 commits intoopenclaw:mainfrom
Sid-Qin:fix/channel-media-ssrf-policy
Mar 1, 2026
Merged

fix(discord,slack): add SSRF policy for media downloads in proxy environments#25475
Takhoffman merged 2 commits intoopenclaw:mainfrom
Sid-Qin:fix/channel-media-ssrf-policy

Conversation

@Sid-Qin
Copy link
Contributor

@Sid-Qin Sid-Qin commented Feb 24, 2026

Summary

  • Problem: Discord and Slack media downloads (attachments, stickers, forwarded images) call fetchRemoteMedia() without any ssrfPolicy. When running behind a local transparent proxy (Clash, mihomo, Shadowrocket) in fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 (RFC 2544) range, and the SSRF guard blocks them — making all inbound media invisible to the agent.
  • Why it matters: Any user on macOS/Linux running Clash TUN, mihomo, or Shadowrocket cannot receive Discord attachments or Slack files. This is a growing user base in regions where proxies are required.
  • What changed: Added per-channel hardcoded SSRF policy constants (DISCORD_MEDIA_SSRF_POLICY, SLACK_MEDIA_SSRF_POLICY) that allowlist known CDN hostnames and set allowRfc2544BenchmarkRange: true, then passed them to every fetchRemoteMedia() call site in the Discord and Slack monitor paths.
  • What did NOT change (scope boundary): No config schema changes. No modification to the core SSRF guard, SsrFPolicy type, or fetchWithSsrFGuard. Telegram already has its own policy on main and is not touched. The web_fetch tool path is handled separately by PR fix(web-fetch): expose ssrfPolicy.allowRfc2544BenchmarkRange config option #25258.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Discord and Slack inbound media (attachments, stickers, forwarded images) now download successfully when DNS resolves CDN hostnames to 198.18.x.x virtual IPs (common with Clash/mihomo/Shadowrocket fake-ip mode). No config changes required — the fix is automatic.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes — previously blocked fetches to Discord/Slack CDNs now succeed when DNS resolves to RFC 2544 benchmark range IPs.
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: The SSRF relaxation is scoped to:
    1. Specific trusted CDN hostnames onlycdn.discordapp.com, media.discordapp.net for Discord; *.slack.com, *.slack-edge.com, *.slack-files.com for Slack. Requests to other hosts remain fully guarded.
    2. Only the RFC 2544 benchmark range (198.18.0.0/15) — all other private/internal/special-use IP ranges remain blocked.
    3. Follows the exact same pattern as the existing TELEGRAM_MEDIA_SSRF_POLICY already on main.

Repro + Verification

Environment

  • OS: macOS 15.3 (Darwin 25.3.0)
  • Runtime/container: Node.js, OpenClaw 2026.2.23
  • Model/provider: any
  • Integration/channel: Discord (primary), Slack (same root cause)
  • Relevant config: Shadowrocket / Clash in fake-ip mode; cdn.discordapp.com resolves to 198.18.0.44

Steps

  1. Run OpenClaw with a Discord channel configured, behind a proxy in fake-ip mode (e.g., Shadowrocket, Clash TUN)
  2. Send a message with a .txt or image attachment in Discord
  3. Observe the agent's response — the attachment content is missing

Expected

  • Agent receives and processes the attachment normally

Actual

  • Agent ignores the attachment; gateway log shows security: blocked URL fetch … target=https://cdn.discordapp.com … reason=Blocked: resolves to private/internal/special-use IP address

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

All 14 Discord monitor tests pass (including 1 new + 3 updated assertions).
All 26 Slack monitor tests pass (including 2 new SSRF policy tests).

Human Verification (required)

  • Verified scenarios: Confirmed on local OpenClaw 2026.2.23 instance (Sidona) with Shadowrocket proxy — Discord attachments were blocked before the local equivalent patch, and succeeded after.
  • Edge cases checked: Non-proxy environments unaffected (CDN resolves to public IPs, SSRF guard is never triggered). Telegram path already fixed on main and is not regressed.
  • What you did not verify: Slack in a live proxy environment (no Slack workspace configured), but the code path is identical to the Discord fix.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert this single commit; the SSRF policy constants are self-contained and not referenced elsewhere.
  • Files/config to restore: src/discord/monitor/message-utils.ts, src/slack/monitor/media.ts
  • Known bad symptoms reviewers should watch for: If Discord/Slack CDN hostnames change (unlikely), the allowedHostnames list would need updating.

Risks and Mitigations

  • Risk: A compromised or spoofed Discord/Slack CDN hostname could bypass SSRF checks for the RFC 2544 range.
    • Mitigation: The allowedHostnames list is tightly scoped to first-party CDN domains controlled by Discord/Slack. The same trust model is already applied to Telegram's api.telegram.org on main.

@openclaw-barnacle openclaw-barnacle bot added channel: discord Channel integration: discord channel: slack Channel integration: slack size: S trusted-contributor labels Feb 24, 2026
SidQin-cyber and others added 2 commits March 1, 2026 11:23
…ronments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>
@Takhoffman Takhoffman force-pushed the fix/channel-media-ssrf-policy branch from 4a035d3 to 8527711 Compare March 1, 2026 17:29
@aisle-research-bot
Copy link

aisle-research-bot bot commented Mar 1, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low SSRF guard relaxed for Slack media downloads by allowing RFC2544 benchmark IP range (198.18.0.0/15)
2 🔵 Low SSRF guard relaxed for Discord media downloads by allowing RFC2544 benchmark IP range (198.18.0.0/15)

1. 🔵 SSRF guard relaxed for Slack media downloads by allowing RFC2544 benchmark IP range (198.18.0.0/15)

Property Value
Severity Low
CWE CWE-918
Location src/slack/monitor/media.ts:111-114

Description

Slack media fetching now passes an SSRF policy that explicitly sets allowRfc2544BenchmarkRange: true. In the SSRF guard implementation (isPrivateIpAddress -> isBlockedSpecialUseIpv4Address), this makes the reserved RFC2544 benchmarking network 198.18.0.0/15 not treated as blocked/special-use.

Impact:

  • If a Slack-hosted URL (or a redirect target) resolves to an IP in 198.18.0.0/15, the request will not be blocked by the SSRF guard.
  • Many environments treat RFC2544 ranges as non-public/internal (they are reserved and not globally routable). Allowing them can enable SSRF-style access to internal services in network setups that route or repurpose this range.

Vulnerable code (new policy applied to Slack media fetches):

const SLACK_MEDIA_SSRF_POLICY = {
  allowedHostnames: ["*.slack.com", "*.slack-edge.com", "*.slack-files.com"],
  allowRfc2544BenchmarkRange: true,
};

Data flow:

  • Input: file.url_private(_download) / forwarded attachment image_url
  • Sink: fetchRemoteMedia()fetchWithSsrFGuard()resolvePinnedHostnameWithPolicy() which uses the policy to decide what IPs to block/allow
  • Policy change: permits RFC2544 range that is otherwise blocked

Recommendation

Do not allow RFC2544 benchmarking addresses for Slack media fetching unless you have a very specific, controlled requirement.

  • Remove allowRfc2544BenchmarkRange: true from the Slack media SSRF policy.
  • If you truly need it for a specific deployment, gate it behind an explicit configuration option and document the SSRF risk.

Secure example:

const SLACK_MEDIA_SSRF_POLICY = {
  allowedHostnames: ["*.slack.com", "*.slack-edge.com", "*.slack-files.com"],// keep RFC2544 (198.18.0.0/15) blocked
  allowRfc2544BenchmarkRange: false,
};

(Optionally) add a defense-in-depth unit test in fetch-guard.ssrf.test.ts asserting that 198.18.0.0/15 remains blocked for Slack media fetches.


2. 🔵 SSRF guard relaxed for Discord media downloads by allowing RFC2544 benchmark IP range (198.18.0.0/15)

Property Value
Severity Low
CWE CWE-918
Location src/discord/monitor/message-utils.ts:9-12

Description

DISCORD_MEDIA_SSRF_POLICY enables allowRfc2544BenchmarkRange: true and is passed into fetchRemoteMedia(...) for inbound Discord attachments/stickers.

This meaningfully relaxes SSRF protections:

  • The SSRF layer normally blocks RFC2544 benchmark/testing IPs (198.18.0.0/15) as special-use non-public addresses.
  • With allowRfc2544BenchmarkRange: true, isBlockedSpecialUseIpv4Address(...) treats 198.18/15 as not blocked, so fetchWithSsrFGuard(...) can fetch URLs that resolve to that range, including direct IP-literals and redirect targets.
  • Additionally, allowedHostnames in the SSRF implementation is treated as an explicit bypass for private/special-use checks (both pre-DNS and post-DNS). If cdn.discordapp.com / media.discordapp.net ever resolve to internal/special-use space due to split-horizon DNS, DNS poisoning, or host-file manipulation, those checks are skipped entirely.

Vulnerable code (policy enabling RFC2544 allowance):

const DISCORD_MEDIA_SSRF_POLICY: SsrFPolicy = {
  allowedHostnames: ["cdn.discordapp.com", "media.discordapp.net"],
  allowRfc2544BenchmarkRange: true,
};

Concrete bypass surface enabled by this option:

  • Any user-influenced fetch that uses this policy can now reach hosts in 198.18.0.0/15 (previously blocked), including via redirects (the redirect-following fetch loop re-applies the same policy each hop).

Impact depends on deployment networking, but 198.18/15 is commonly treated as “internal/non-public” and may route to test networks or network appliances; allowing it increases SSRF pivot potential.

Recommendation

Do not enable RFC2544 benchmark range access for inbound media downloads unless there is a documented, unavoidable requirement.

  • Remove allowRfc2544BenchmarkRange: true (default is safer).
  • If the intent is to restrict downloads to Discord CDN hosts, use hostnameAllowlist (enforcement) rather than allowedHostnames (bypass list), and keep resolved-IP checks enabled.

Example safer policy:

const DISCORD_MEDIA_SSRF_POLICY: SsrFPolicy = {
  hostnameAllowlist: ["cdn.discordapp.com", "media.discordapp.net"],// leave allowRfc2544BenchmarkRange undefined/false
};

If you truly must allow these hostnames even when they resolve to internal space, consider a more targeted exception (e.g., only in specific deployments) and add monitoring/auditing for any fetches whose resolved IP is non-global.


Analyzed PR: #25475 at commit 8527711

Last updated on: 2026-03-01T17:43:22Z

@Takhoffman Takhoffman merged commit 39a4512 into openclaw:main Mar 1, 2026
9 checks passed
zooqueen added a commit to hanzoai/bot that referenced this pull request Mar 1, 2026
ansh pushed a commit to vibecode/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
steipete pushed a commit to Sid-Qin/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
safzanpirani pushed a commit to safzanpirani/clawdbot that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
steipete pushed a commit to Sid-Qin/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
amitmiran137 pushed a commit to amitmiran137/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
robertchang-ga pushed a commit to robertchang-ga/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
hanqizheng pushed a commit to hanqizheng/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
execute008 pushed a commit to execute008/openclaw that referenced this pull request Mar 2, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
dorgonman pushed a commit to kanohorizonia/openclaw that referenced this pull request Mar 3, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
sachinkundu pushed a commit to sachinkundu/openclaw that referenced this pull request Mar 6, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…ronments (openclaw#25475)

* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs openclaw#25355, openclaw#25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
zooqueen added a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord channel: slack Channel integration: slack size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants