fix(gateway): tighten trusted-proxy local fallback

This commit is contained in:
rick 2026-03-03 23:28:04 -06:00
parent 1cab57f257
commit 8dfb38af22
2 changed files with 70 additions and 1 deletions

View File

@ -417,6 +417,63 @@ describe("trusted-proxy auth", () => {
expect(localToken.method).toBe("token");
});
it("does not let local fallback preempt valid trusted-proxy header auth", async () => {
const res = await authorizeGatewayConnect({
auth: {
mode: "trusted-proxy",
allowTailscale: false,
token: "secret",
trustedProxy: trustedProxyConfig,
},
connectAuth: { token: "wrong" },
trustedProxies: ["127.0.0.1"],
req: {
socket: { remoteAddress: "127.0.0.1" },
headers: {
host: "gateway.local",
"x-forwarded-for": "203.0.113.10",
"x-forwarded-user": "nick@example.com",
"x-forwarded-proto": "https",
},
} as never,
});
expect(res.ok).toBe(true);
expect(res.method).toBe("trusted-proxy");
expect(res.user).toBe("nick@example.com");
});
it("applies rate limiting before trusted-proxy local shared-secret fallback", async () => {
const limiter = createLimiterSpy();
limiter.check.mockReturnValue({
allowed: false,
remaining: 0,
retryAfterMs: 60_000,
});
const res = await authorizeGatewayConnect({
auth: {
mode: "trusted-proxy",
allowTailscale: false,
token: "secret",
trustedProxy: trustedProxyConfig,
},
connectAuth: { token: "secret" },
trustedProxies: ["127.0.0.1"],
req: {
socket: { remoteAddress: "127.0.0.1" },
headers: {
host: "127.0.0.1:19001",
},
} as never,
rateLimiter: limiter,
});
expect(res.ok).toBe(false);
expect(res.reason).toBe("rate_limited");
expect(limiter.check).toHaveBeenCalled();
});
it("rejects request from untrusted source", async () => {
const res = await authorizeTrustedProxy({
remoteAddress: "192.168.1.100",

View File

@ -419,7 +419,19 @@ export async function authorizeGatewayConnect(
!req?.headers?.["x-forwarded-host"];
if (auth.mode === "trusted-proxy") {
if (localDirect || localLoopbackWithoutProxyHeaders) {
if (localLoopbackWithoutProxyHeaders && limiter) {
const rlCheck: RateLimitCheckResult = limiter.check(ip, rateLimitScope);
if (!rlCheck.allowed) {
return {
ok: false,
reason: "rate_limited",
rateLimited: true,
retryAfterMs: rlCheck.retryAfterMs,
};
}
}
if (localLoopbackWithoutProxyHeaders) {
const sharedSecretFallback = authorizeSharedSecretFallback({
auth,
connectAuth,