diff --git a/CHANGELOG.md b/CHANGELOG.md index 70695132b..af780acc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Docs: https://docs.openclaw.ai - OpenAI Responses/Compaction: rewrite and unify the OpenAI Responses store patches to treat empty `baseUrl` as non-direct, honor `compat.supportsStore=false`, and auto-inject server-side compaction `context_management` for compatible direct OpenAI models (with per-model opt-out/threshold overrides). Landed from contributor PRs #16930 (@OiPunk), #22441 (@EdwardWu7), and #25088 (@MoerAI). Thanks @OiPunk, @EdwardWu7, and @MoerAI. - Signal/Sync message null-handling: treat `syncMessage` presence (including `null`) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin. - Inbound metadata/Multi-account routing: include `account_id` in trusted inbound metadata so multi-account channel sessions can reliably disambiguate the receiving account in prompt context. Landed from contributor PR #30984 by @Stxle2. Thanks @Stxle2. +- Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (`198.18.0.0/15`) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux. - Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted `System:` context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky. - Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as `msg_type: "file"`, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff. ## Unreleased diff --git a/src/agents/tools/web-guarded-fetch.test.ts b/src/agents/tools/web-guarded-fetch.test.ts new file mode 100644 index 000000000..b8be25be7 --- /dev/null +++ b/src/agents/tools/web-guarded-fetch.test.ts @@ -0,0 +1,51 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { fetchWithSsrFGuard } from "../../infra/net/fetch-guard.js"; +import { withStrictWebToolsEndpoint, withTrustedWebToolsEndpoint } from "./web-guarded-fetch.js"; + +vi.mock("../../infra/net/fetch-guard.js", () => ({ + fetchWithSsrFGuard: vi.fn(), +})); + +describe("web-guarded-fetch", () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it("uses trusted SSRF policy for trusted web tools endpoints", async () => { + vi.mocked(fetchWithSsrFGuard).mockResolvedValue({ + response: new Response("ok", { status: 200 }), + finalUrl: "https://example.com", + release: async () => {}, + }); + + await withTrustedWebToolsEndpoint({ url: "https://example.com" }, async () => undefined); + + expect(fetchWithSsrFGuard).toHaveBeenCalledWith( + expect.objectContaining({ + url: "https://example.com", + policy: expect.objectContaining({ + dangerouslyAllowPrivateNetwork: true, + allowRfc2544BenchmarkRange: true, + }), + }), + ); + }); + + it("keeps strict endpoint policy unchanged", async () => { + vi.mocked(fetchWithSsrFGuard).mockResolvedValue({ + response: new Response("ok", { status: 200 }), + finalUrl: "https://example.com", + release: async () => {}, + }); + + await withStrictWebToolsEndpoint({ url: "https://example.com" }, async () => undefined); + + expect(fetchWithSsrFGuard).toHaveBeenCalledWith( + expect.objectContaining({ + url: "https://example.com", + }), + ); + const call = vi.mocked(fetchWithSsrFGuard).mock.calls[0]?.[0]; + expect(call?.policy).toBeUndefined(); + }); +}); diff --git a/src/agents/tools/web-guarded-fetch.ts b/src/agents/tools/web-guarded-fetch.ts index 2f905a215..f427eabca 100644 --- a/src/agents/tools/web-guarded-fetch.ts +++ b/src/agents/tools/web-guarded-fetch.ts @@ -7,6 +7,7 @@ import type { SsrFPolicy } from "../../infra/net/ssrf.js"; const WEB_TOOLS_TRUSTED_NETWORK_SSRF_POLICY: SsrFPolicy = { dangerouslyAllowPrivateNetwork: true, + allowRfc2544BenchmarkRange: true, }; type WebToolGuardedFetchOptions = Omit & { diff --git a/src/infra/outbound/outbound.test.ts b/src/infra/outbound/outbound.test.ts index 08e6876a7..4bb00b4db 100644 --- a/src/infra/outbound/outbound.test.ts +++ b/src/infra/outbound/outbound.test.ts @@ -955,8 +955,8 @@ describe("resolveOutboundSessionRoute", () => { target: "12345", threadId: "12345:99", expected: { - sessionKey: "agent:main:telegram:direct:12345", - from: "telegram:12345", + sessionKey: "agent:main:telegram:direct:12345:thread:99", + from: "telegram:12345:topic:99", to: "telegram:12345", threadId: 99, chatType: "direct",