mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 00:18:26 +00:00
fix(discord,slack): add SSRF policy for media downloads in proxy environments (#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 #25355, #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>
This commit is contained in:
@@ -94,6 +94,7 @@ describe("resolveForwardedMediaList", () => {
|
||||
filePathHint: attachment.filename,
|
||||
maxBytes: 512,
|
||||
fetchImpl: undefined,
|
||||
ssrfPolicy: expect.objectContaining({ allowRfc2544BenchmarkRange: true }),
|
||||
});
|
||||
expect(saveMediaBuffer).toHaveBeenCalledTimes(1);
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(expect.any(Buffer), "image/png", "inbound", 512);
|
||||
@@ -168,6 +169,7 @@ describe("resolveForwardedMediaList", () => {
|
||||
filePathHint: "wave.png",
|
||||
maxBytes: 512,
|
||||
fetchImpl: undefined,
|
||||
ssrfPolicy: expect.objectContaining({ allowRfc2544BenchmarkRange: true }),
|
||||
});
|
||||
expect(saveMediaBuffer).toHaveBeenCalledTimes(1);
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(expect.any(Buffer), "image/png", "inbound", 512);
|
||||
@@ -236,6 +238,7 @@ describe("resolveMediaList", () => {
|
||||
filePathHint: "hello.png",
|
||||
maxBytes: 512,
|
||||
fetchImpl: undefined,
|
||||
ssrfPolicy: expect.objectContaining({ allowRfc2544BenchmarkRange: true }),
|
||||
});
|
||||
expect(saveMediaBuffer).toHaveBeenCalledTimes(1);
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(expect.any(Buffer), "image/png", "inbound", 512);
|
||||
@@ -278,6 +281,37 @@ describe("resolveMediaList", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Discord media SSRF policy", () => {
|
||||
beforeEach(() => {
|
||||
fetchRemoteMedia.mockClear();
|
||||
saveMediaBuffer.mockClear();
|
||||
});
|
||||
|
||||
it("passes ssrfPolicy with Discord CDN allowedHostnames and allowRfc2544BenchmarkRange", async () => {
|
||||
fetchRemoteMedia.mockResolvedValueOnce({
|
||||
buffer: Buffer.from("img"),
|
||||
contentType: "image/png",
|
||||
});
|
||||
saveMediaBuffer.mockResolvedValueOnce({
|
||||
path: "/tmp/a.png",
|
||||
contentType: "image/png",
|
||||
});
|
||||
|
||||
await resolveMediaList(
|
||||
asMessage({
|
||||
attachments: [{ id: "a1", url: "https://cdn.discordapp.com/a.png", filename: "a.png" }],
|
||||
}),
|
||||
1024,
|
||||
);
|
||||
|
||||
const policy = fetchRemoteMedia.mock.calls[0][0].ssrfPolicy;
|
||||
expect(policy).toEqual({
|
||||
allowedHostnames: ["cdn.discordapp.com", "media.discordapp.net"],
|
||||
allowRfc2544BenchmarkRange: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDiscordMessageText", () => {
|
||||
it("includes forwarded message snapshots in body text", () => {
|
||||
const text = resolveDiscordMessageText(
|
||||
|
||||
@@ -2,9 +2,15 @@ import type { ChannelType, Client, Message } from "@buape/carbon";
|
||||
import { StickerFormatType, type APIAttachment, type APIStickerItem } from "discord-api-types/v10";
|
||||
import { buildMediaPayload } from "../../channels/plugins/media-payload.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import type { SsrFPolicy } from "../../infra/net/ssrf.js";
|
||||
import { fetchRemoteMedia, type FetchLike } from "../../media/fetch.js";
|
||||
import { saveMediaBuffer } from "../../media/store.js";
|
||||
|
||||
const DISCORD_MEDIA_SSRF_POLICY: SsrFPolicy = {
|
||||
allowedHostnames: ["cdn.discordapp.com", "media.discordapp.net"],
|
||||
allowRfc2544BenchmarkRange: true,
|
||||
};
|
||||
|
||||
export type DiscordMediaInfo = {
|
||||
path: string;
|
||||
contentType?: string;
|
||||
@@ -228,6 +234,7 @@ async function appendResolvedMediaFromAttachments(params: {
|
||||
filePathHint: attachment.filename ?? attachment.url,
|
||||
maxBytes: params.maxBytes,
|
||||
fetchImpl: params.fetchImpl,
|
||||
ssrfPolicy: DISCORD_MEDIA_SSRF_POLICY,
|
||||
});
|
||||
const saved = await saveMediaBuffer(
|
||||
fetched.buffer,
|
||||
@@ -320,6 +327,7 @@ async function appendResolvedMediaFromStickers(params: {
|
||||
filePathHint: candidate.fileName,
|
||||
maxBytes: params.maxBytes,
|
||||
fetchImpl: params.fetchImpl,
|
||||
ssrfPolicy: DISCORD_MEDIA_SSRF_POLICY,
|
||||
});
|
||||
const saved = await saveMediaBuffer(
|
||||
fetched.buffer,
|
||||
|
||||
Reference in New Issue
Block a user