fix(security): strip custom auth headers on cross-origin redirects

This commit is contained in:
Peter Steinberger
2026-03-07 17:34:31 +00:00
parent 630485ac98
commit 46715371b0
3 changed files with 33 additions and 10 deletions

View File

@@ -601,6 +601,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Models/provider config precedence: prefer exact `models.providers.<name>` matches before normalized provider aliases in embedded model resolution, preventing alias/canonical key collisions from applying the wrong provider `api`, `baseUrl`, or headers. (#35934) thanks @RealKai42.
- Network/fetch guard redirect auth stripping: switch cross-origin redirect handling in `fetchWithSsrFGuard` from a narrow sensitive-header denylist to a safe-header allowlist so custom auth headers like `X-Api-Key` and `Private-Token` no longer leak on origin changes. Thanks @Rickidevs for reporting.
- Logging/Subsystem console timestamps: route subsystem console timestamp rendering through `formatConsoleTimestamp(...)` so `pretty` and timestamp-prefix output use local timezone formatting consistently instead of inline UTC `toISOString()` paths. (#25970) Thanks @openperf.
- Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, prevent inbound preview text from leaking into prompt system events, 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 #31209, #29610, #30432, #30331, and #29501. Thanks @stakeswky, @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.
- Feishu/Target routing + replies + dedupe: normalize provider-prefixed targets (`feishu:`/`lark:`), prefer configured `channels.feishu.defaultAccount` for tool execution, honor Feishu outbound `renderMode` in adapter text/caption sends, fall back to normal send when reply targets are withdrawn/deleted, and add synchronous in-memory dedupe guard for concurrent duplicate inbound events. Landed from contributor PRs #30428, #30438, #29958, #30444, and #29463. Thanks @bmendonca3 and @Yaxuan42.

View File

@@ -154,7 +154,12 @@ describe("fetchWithSsrFGuard hardening", () => {
"Proxy-Authorization": "Basic c2VjcmV0",
Cookie: "session=abc",
Cookie2: "legacy=1",
"X-Api-Key": "custom-secret",
"Private-Token": "private-secret",
"X-Trace": "1",
Accept: "application/json",
"Content-Type": "application/json",
"User-Agent": "OpenClaw-Test/1.0",
},
},
});
@@ -164,7 +169,12 @@ describe("fetchWithSsrFGuard hardening", () => {
expect(headers.get("proxy-authorization")).toBeNull();
expect(headers.get("cookie")).toBeNull();
expect(headers.get("cookie2")).toBeNull();
expect(headers.get("x-trace")).toBe("1");
expect(headers.get("x-api-key")).toBeNull();
expect(headers.get("private-token")).toBeNull();
expect(headers.get("x-trace")).toBeNull();
expect(headers.get("accept")).toBe("application/json");
expect(headers.get("content-type")).toBe("application/json");
expect(headers.get("user-agent")).toBe("OpenClaw-Test/1.0");
await result.release();
});

View File

@@ -52,12 +52,21 @@ type GuardedFetchPresetOptions = Omit<
>;
const DEFAULT_MAX_REDIRECTS = 3;
const CROSS_ORIGIN_REDIRECT_SENSITIVE_HEADERS = [
"authorization",
"proxy-authorization",
"cookie",
"cookie2",
];
const CROSS_ORIGIN_REDIRECT_SAFE_HEADERS = new Set([
"accept",
"accept-encoding",
"accept-language",
"cache-control",
"content-language",
"content-type",
"if-match",
"if-modified-since",
"if-none-match",
"if-unmodified-since",
"pragma",
"range",
"user-agent",
]);
export function withStrictGuardedFetchMode(params: GuardedFetchPresetOptions): GuardedFetchOptions {
return { ...params, mode: GUARDED_FETCH_MODE.STRICT };
@@ -87,9 +96,12 @@ function stripSensitiveHeadersForCrossOriginRedirect(init?: RequestInit): Reques
if (!init?.headers) {
return init;
}
const headers = new Headers(init.headers);
for (const header of CROSS_ORIGIN_REDIRECT_SENSITIVE_HEADERS) {
headers.delete(header);
const incoming = new Headers(init.headers);
const headers = new Headers();
for (const [key, value] of incoming.entries()) {
if (CROSS_ORIGIN_REDIRECT_SAFE_HEADERS.has(key.toLowerCase())) {
headers.set(key, value);
}
}
return { ...init, headers };
}