mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-13 03:52:55 +00:00
* fix(qqbot): allow RFC2544 benchmark range for token fetch (#88984) QQ Bot `bots.qq.com` token-fetch path was failing for users whose DNS resolver maps the hostname into the RFC 2544 benchmark range `198.18.0.0/15` (commonly seen with fake-IP proxy stacks: sing-box, Clash, Surge, WSL2 DNS). The default SSRF guard treats that range as private and blocks the request, surfacing as "Network error getting access_token: Blocked: resolves to private/internal/special-use IP address". Pass a host-scoped `SsrFPolicy` (`allowRfc2544BenchmarkRange: true`) to the single hard-coded `TOKEN_URL` request, mirroring the existing `QQBOT_MEDIA_SSRF_POLICY` pattern used by the media path. Because `TOKEN_URL` is a const and not user-controlled, the relaxation cannot widen attack surface to other hosts. Adds a regression test asserting `policy: { allowRfc2544BenchmarkRange: true }` is forwarded into `fetchWithSsrFGuard`, and updates the existing equality assertion accordingly. Fixes #88984 * fix(qqbot): scope token ssrf policy
This commit is contained in:
@@ -42,6 +42,10 @@ describe("QQBot token manager", () => {
|
||||
url: "https://bots.qq.com/app/getAppAccessToken",
|
||||
auditContext: "qqbot-token",
|
||||
capture: false,
|
||||
policy: {
|
||||
hostnameAllowlist: ["bots.qq.com"],
|
||||
allowRfc2544BenchmarkRange: true,
|
||||
},
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -54,6 +58,25 @@ describe("QQBot token manager", () => {
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("passes the RFC2544 SSRF allowance to the token fetch (regression for #88984)", async () => {
|
||||
mockGuardedTokenResponse('{"access_token":"token-1","expires_in":7200}', {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
|
||||
await expect(new TokenManager().getAccessToken("app-id", "secret")).resolves.toBe("token-1");
|
||||
expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
url: "https://bots.qq.com/app/getAppAccessToken",
|
||||
auditContext: "qqbot-token",
|
||||
policy: {
|
||||
hostnameAllowlist: ["bots.qq.com"],
|
||||
allowRfc2544BenchmarkRange: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not cache access tokens forever when expires_in is unsafe", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-29T12:00:00.000Z"));
|
||||
|
||||
@@ -12,13 +12,31 @@ import {
|
||||
resolveExpiresAtMsFromDurationSeconds,
|
||||
resolveTimestampMsToIsoString,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import type { EngineLogger } from "../types.js";
|
||||
import { formatErrorMessage } from "../utils/format.js";
|
||||
|
||||
const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
||||
const DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 7200;
|
||||
|
||||
/**
|
||||
* Host-scoped SSRF policy for the QQ Bot token endpoint.
|
||||
*
|
||||
* `TOKEN_URL` is a hard-coded `https://bots.qq.com/...` constant, so this
|
||||
* relaxation only ever applies to that single host. Fake-IP proxy stacks
|
||||
* (sing-box, Clash, Surge, WSL2 DNS, etc.) routinely map `bots.qq.com` into
|
||||
* the RFC 2544 benchmark range `198.18.0.0/15`, which the default SSRF
|
||||
* guard blocks. We mirror the existing media-path pattern
|
||||
* (`QQBOT_MEDIA_SSRF_POLICY` in `../utils/file-utils.ts`) so the relaxation
|
||||
* stays narrowly host-scoped instead of weakening the global default.
|
||||
*
|
||||
* See https://github.com/openclaw/openclaw/issues/88984.
|
||||
*/
|
||||
const QQBOT_TOKEN_SSRF_POLICY: SsrFPolicy = {
|
||||
hostnameAllowlist: ["bots.qq.com"],
|
||||
allowRfc2544BenchmarkRange: true,
|
||||
};
|
||||
|
||||
interface CachedToken {
|
||||
token: string;
|
||||
expiresAt: number;
|
||||
@@ -234,6 +252,7 @@ export class TokenManager {
|
||||
url: TOKEN_URL,
|
||||
auditContext: "qqbot-token",
|
||||
capture: false,
|
||||
policy: QQBOT_TOKEN_SSRF_POLICY,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user