diff --git a/CHANGELOG.md b/CHANGELOG.md index b457be7ae46..93defed66e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Docs: https://docs.openclaw.ai - Gateway/Control plane: reduce cross-client write limiter contention by adding `connId` fallback keying when device ID and client IP are both unavailable. - Security/Config: block prototype-key traversal during config merge patch and legacy migration merge helpers (`__proto__`, `constructor`, `prototype`) to prevent prototype pollution during config mutation flows. (#22968) Thanks @Clawborn. - Security/Shell env: validate login-shell executable paths for shell-env fallback (`/etc/shells` + trusted prefixes), block `SHELL`/`HOME`/`ZDOTDIR` in config env ingestion before fallback execution, and sanitize fallback shell exec env to pin `HOME` to the real user home while dropping `ZDOTDIR` and other dangerous startup vars. This ships in the next npm release. Thanks @tdjackey for reporting. +- Network/SSRF: enable `autoSelectFamily` on pinned undici dispatchers (with attempt timeout) so IPv6-unreachable environments can quickly fall back to IPv4 for guarded fetch paths. (#19950) Thanks @ENAwareness. - Security/Config: make parsed chat allowlist checks fail closed when `allowFrom` is empty, restoring expected DM/pairing gating. - Security/Exec: in non-default setups that manually add `sort` to `tools.exec.safeBins`, block `sort --compress-program` so allowlist-mode safe-bin checks cannot bypass approval. Thanks @tdjackey for reporting. - Security/Exec approvals: when users choose `allow-always` for shell-wrapper commands (for example `/bin/zsh -lc ...`), persist allowlist patterns for the inner executable(s) instead of the wrapper shell binary, preventing accidental broad shell allowlisting in moderate mode. (#23276) Thanks @xrom2863. diff --git a/src/infra/net/ssrf.dispatcher.test.ts b/src/infra/net/ssrf.dispatcher.test.ts new file mode 100644 index 00000000000..2a7f79d2ddf --- /dev/null +++ b/src/infra/net/ssrf.dispatcher.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it, vi } from "vitest"; + +const { agentCtor } = vi.hoisted(() => ({ + agentCtor: vi.fn(function MockAgent(this: { options: unknown }, options: unknown) { + this.options = options; + }), +})); + +vi.mock("undici", () => ({ + Agent: agentCtor, +})); + +import { createPinnedDispatcher, type PinnedHostname } from "./ssrf.js"; + +describe("createPinnedDispatcher", () => { + it("enables network family auto-selection for pinned lookups", () => { + const lookup = vi.fn(); + const pinned: PinnedHostname = { + hostname: "api.telegram.org", + addresses: ["149.154.167.220"], + lookup, + }; + + const dispatcher = createPinnedDispatcher(pinned); + + expect(dispatcher).toBeDefined(); + expect(agentCtor).toHaveBeenCalledWith({ + connect: { + lookup, + autoSelectFamily: true, + autoSelectFamilyAttemptTimeout: 300, + }, + }); + }); +}); diff --git a/src/infra/net/ssrf.ts b/src/infra/net/ssrf.ts index d44d7a0eb2c..964e95b4dbd 100644 --- a/src/infra/net/ssrf.ts +++ b/src/infra/net/ssrf.ts @@ -296,6 +296,8 @@ export function createPinnedDispatcher(pinned: PinnedHostname): Dispatcher { return new Agent({ connect: { lookup: pinned.lookup, + autoSelectFamily: true, + autoSelectFamilyAttemptTimeout: 300, }, }); }