Gateway: harden trusted proxy X-Forwarded-For parsing (#22429)

This commit is contained in:
Vincent Koc
2026-02-20 23:59:20 -05:00
committed by GitHub
parent 35be87b09b
commit 07039dc089
3 changed files with 16 additions and 2 deletions

View File

@@ -108,6 +108,7 @@ Docs: https://docs.openclaw.ai
- iOS/Watch: refresh iOS and watch app icon assets with the lobster icon set to keep phone/watch branding aligned. (#21997) Thanks @mbelinky.
- CLI/Onboarding: fix Anthropic-compatible custom provider verification by normalizing base URLs to avoid duplicate `/v1` paths during setup checks. (#21336) Thanks @17jmumford.
- Security/Dependencies: bump transitive `hono` usage to `4.11.10` to incorporate timing-safe authentication comparison hardening for `basicAuth`/`bearerAuth` (`GHSA-gq3j-xvxp-8hrf`). Thanks @vincentkoc.
- Security/Gateway: parse `X-Forwarded-For` with trust-preserving semantics when requests come from configured trusted proxies, preventing proxy-chain spoofing from influencing client IP classification and rate-limit identity. Thanks @AnthonyDiSanti and @vincentkoc.
- iOS/Gateway/Tools: prefer uniquely connected node matches when duplicate display names exist, surface actionable `nodes invoke` pairing-required guidance with request IDs, and refresh active iOS gateway registration after location-capability setting changes so capability updates apply immediately. (#22120) thanks @mbelinky.
## 2026.2.19

View File

@@ -145,12 +145,21 @@ describe("resolveGatewayClientIp", () => {
it("returns forwarded client IP when the remote is a trusted proxy", () => {
const ip = resolveGatewayClientIp({
remoteAddr: "127.0.0.1",
forwardedFor: "10.0.0.2, 127.0.0.1",
forwardedFor: "127.0.0.1, 10.0.0.2",
trustedProxies: ["127.0.0.1"],
});
expect(ip).toBe("10.0.0.2");
});
it("does not trust the left-most X-Forwarded-For value when behind a trusted proxy", () => {
const ip = resolveGatewayClientIp({
remoteAddr: "127.0.0.1",
forwardedFor: "198.51.100.99, 10.0.0.9, 127.0.0.1",
trustedProxies: ["127.0.0.1"],
});
expect(ip).toBe("127.0.0.1");
});
it("fails closed when trusted proxy headers are missing", () => {
const ip = resolveGatewayClientIp({
remoteAddr: "127.0.0.1",

View File

@@ -147,7 +147,11 @@ function stripOptionalPort(ip: string): string {
}
export function parseForwardedForClientIp(forwardedFor?: string): string | undefined {
const raw = forwardedFor?.split(",")[0]?.trim();
const entries = forwardedFor
?.split(",")
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0);
const raw = entries?.at(-1);
if (!raw) {
return undefined;
}