
























@@ -1,4 +1,6 @@
1-import { describe, expect, it, vi } from "vitest";
1+import os from "node:os";
2+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+import { makeNetworkInterfacesSnapshot } from "../test-helpers/network-interfaces.js";
24import { createAuthRateLimiter, type AuthRateLimiter } from "./auth-rate-limit.js";
35import {
46assertGatewayAuthConfigured,
@@ -558,6 +560,26 @@ describe("gateway auth", () => {
558560});
559561560562describe("trusted-proxy auth", () => {
563+function mockLocalInterfaces(nonLoopbackAddress = "10.0.0.2", family: "IPv4" | "IPv6" = "IPv4") {
564+const spy = vi.isMockFunction(os.networkInterfaces)
565+ ? vi.mocked(os.networkInterfaces)
566+ : vi.spyOn(os, "networkInterfaces");
567+spy.mockReturnValue(
568+makeNetworkInterfacesSnapshot({
569+lo: [{ address: "127.0.0.1", family: "IPv4", internal: true }],
570+eth0: [{ address: nonLoopbackAddress, family }],
571+}),
572+);
573+}
574+575+beforeEach(() => {
576+mockLocalInterfaces();
577+});
578+579+afterEach(() => {
580+vi.restoreAllMocks();
581+});
582+561583type GatewayConnectInput = Parameters<typeof authorizeGatewayConnect>[0];
562584const trustedProxyConfig = {
563585userHeader: "x-forwarded-user",
@@ -602,6 +624,57 @@ describe("trusted-proxy auth", () => {
602624expect(res.user).toBe("nick@example.com");
603625});
604626627+it("rejects trusted-proxy headers from the host non-loopback interface address", async () => {
628+mockLocalInterfaces("10.0.0.1");
629+630+const res = await authorizeTrustedProxy({
631+trustedProxies: ["10.0.0.1"],
632+remoteAddress: "10.0.0.1",
633+headers: {
634+"x-forwarded-user": "nick@example.com",
635+"x-forwarded-proto": "https",
636+"x-openclaw-proxy-auth": "present",
637+},
638+});
639+640+expect(res.ok).toBe(false);
641+expect(res.reason).toBe("trusted_proxy_local_interface_source");
642+});
643+644+it("rejects trusted-proxy headers when local interface discovery fails", async () => {
645+vi.mocked(os.networkInterfaces).mockImplementation(() => {
646+throw new Error("interface discovery failed");
647+});
648+649+const res = await authorizeTrustedProxy({
650+trustedProxies: ["10.0.0.1"],
651+remoteAddress: "10.0.0.1",
652+headers: {
653+"x-forwarded-user": "nick@example.com",
654+"x-forwarded-proto": "https",
655+},
656+});
657+658+expect(res.ok).toBe(false);
659+expect(res.reason).toBe("trusted_proxy_local_interface_check_failed");
660+});
661+662+it("rejects trusted-proxy headers from a host IPv6 interface address", async () => {
663+mockLocalInterfaces("fd7a:115c:a1e0::1234", "IPv6");
664+665+const res = await authorizeTrustedProxy({
666+trustedProxies: ["fd7a:115c:a1e0::1234"],
667+remoteAddress: "fd7a:115c:a1e0::1234",
668+headers: {
669+"x-forwarded-user": "nick@example.com",
670+"x-forwarded-proto": "https",
671+},
672+});
673+674+expect(res.ok).toBe(false);
675+expect(res.reason).toBe("trusted_proxy_local_interface_source");
676+});
677+605678it("rejects trusted-proxy HTTP requests from origins outside the allowlist", async () => {
606679await expect(
607680authorizeHttpGatewayConnect({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。