fix: Client-IP-Ermittlung härten (CF-Connecting-IP, X-Real-IP)
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 4m59s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 4m59s
CF-Connecting-IP wird nur noch als öffentliche Unicast-Adresse akzeptiert (kein 0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16, 240/4 "Pseudo IPv4", 255.255.255.255, ::, ::1, fc00::/7, fe80::/10 sowie IPv4-gemappte Formen); sonst gilt die Gegenstelle aus X-Forwarded-For. Der X-Real-IP-Fallback gilt nur noch außerhalb von Produktion. In Produktion ohne X-Forwarded-For wird "unknown" geliefert. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
97f0e033e0
commit
0d254db867
2 files changed
+57
-4
No files matched your search
@@ -1,8 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { rateLimitSubject, resolveClientIp } from "./rate-limit";
|
||||
|
||||
const h = (values: Record<string, string>) => new Headers(values);
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
describe("resolveClientIp (Client-IP hinter Cloudflare und Traefik)", () => {
|
||||
it("nimmt den letzten X-Forwarded-For-Eintrag, nie einen vom Client gesetzten", () => {
|
||||
expect(resolveClientIp(h({ "x-forwarded-for": "1.2.3.4, 198.51.100.9" }))).toBe("198.51.100.9");
|
||||
@@ -18,10 +22,38 @@ describe("resolveClientIp (Client-IP hinter Cloudflare und Traefik)", () => {
|
||||
expect(resolveClientIp(h({ "x-forwarded-for": "198.51.100.9", "cf-connecting-ip": "203.0.113.7" }))).toBe("198.51.100.9");
|
||||
});
|
||||
|
||||
it("fällt ohne X-Forwarded-For auf X-Real-IP zurück, sonst „unknown“", () => {
|
||||
it("fällt ohne X-Forwarded-For außerhalb von Produktion auf X-Real-IP zurück, sonst „unknown“", () => {
|
||||
vi.stubEnv("NODE_ENV", "development");
|
||||
expect(resolveClientIp(h({ "x-real-ip": "198.51.100.9" }))).toBe("198.51.100.9");
|
||||
expect(resolveClientIp(h({}))).toBe("unknown");
|
||||
});
|
||||
|
||||
it("ignoriert X-Real-IP in Produktion, wenn X-Forwarded-For fehlt", () => {
|
||||
vi.stubEnv("NODE_ENV", "production");
|
||||
expect(resolveClientIp(h({ "x-real-ip": "198.51.100.9" }))).toBe("unknown");
|
||||
expect(resolveClientIp(h({ "x-forwarded-for": "198.51.100.9", "x-real-ip": "203.0.113.1" }))).toBe("198.51.100.9");
|
||||
});
|
||||
|
||||
describe("CF-Connecting-IP nur als öffentliche Unicast-Adresse", () => {
|
||||
const edge = "172.70.1.2";
|
||||
const nonPublic = [
|
||||
"0.0.0.0", "0.1.2.3", "10.0.0.1", "100.64.0.1", "100.127.255.255", "127.0.0.1", "169.254.1.1",
|
||||
"172.16.0.1", "172.31.255.255", "192.168.1.1", "240.0.0.1", "255.255.255.255",
|
||||
"::", "::1", "fc00::1", "fd12:3456::1", "fe80::1", "febf::1",
|
||||
"::ffff:10.0.0.1", "::ffff:127.0.0.1", "::ffff:192.168.1.1", "::ffff:240.0.0.1", "::ffff:0.0.0.0",
|
||||
];
|
||||
|
||||
it.each(nonPublic)("verwirft %s und nutzt die Cloudflare-Edge als Gegenstelle", (ip) => {
|
||||
expect(resolveClientIp(h({ "x-forwarded-for": edge, "cf-connecting-ip": ip }))).toBe(edge);
|
||||
});
|
||||
|
||||
it.each(["203.0.113.7", "100.63.255.255", "172.32.0.1", "239.1.1.1", "2001:db8::5", "::ffff:203.0.113.7"])(
|
||||
"akzeptiert öffentliche Adresse %s",
|
||||
(ip) => {
|
||||
expect(resolveClientIp(h({ "x-forwarded-for": edge, "cf-connecting-ip": ip }))).toBe(ip);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rateLimitSubject (IPv6 je /64)", () => {
|
||||
|
||||
Reference in new issue
Block a user