Skip to content

Commit

Permalink
feat(ip): Add Vercel platform-specific IP header detection (#2022)
Browse files Browse the repository at this point in the history
This adds platform-specific IP header detection for Vercel. I've chosen to detect via `X-Real-IP` first since that is the header used by `@vercel/functions` and then I've fallen back to `x-vercel-forwarded-for` and finally `x-forwarded-for` based on my interpretation of their documentation.
  • Loading branch information
blaine-arcjet authored Oct 23, 2024
1 parent 1a13d9c commit d886c76
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 1 deletion.
5 changes: 5 additions & 0 deletions env/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type Env = {
[key: string]: unknown;
FLY_APP_NAME?: string;
VERCEL?: string;
NODE_ENV?: string;
ARCJET_KEY?: string;
ARCJET_ENV?: string;
Expand All @@ -12,6 +13,10 @@ export function platform(env: Env) {
if (typeof env["FLY_APP_NAME"] === "string" && env["FLY_APP_NAME"] !== "") {
return "fly-io" as const;
}

if (typeof env["VERCEL"] === "string" && env["VERCEL"] === "1") {
return "vercel" as const;
}
}

export function isDevelopment(env: Env) {
Expand Down
51 changes: 50 additions & 1 deletion ip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ export interface RequestLike {
requestContext?: PartialRequestContext;
}

export type Platform = "cloudflare" | "fly-io";
export type Platform = "cloudflare" | "fly-io" | "vercel";

export interface Options {
platform?: Platform;
Expand Down Expand Up @@ -649,6 +649,55 @@ function findIP(
return "";
}

if (platform === "vercel") {
// https://vercel.com/docs/edge-network/headers/request-headers#x-real-ip
// Also used by `@vercel/functions`, see:
// https://github.com/vercel/vercel/blob/d7536d52c87712b1b3f83e4b0fd535a1fb7e384c/packages/functions/src/headers.ts#L12
const xRealIP = headers.get("x-real-ip");
if (isGlobalIP(xRealIP)) {
return xRealIP;
}

// https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-forwarded-for
// By default, it seems this will be 1 address, but they discuss trusted
// proxy forwarding so we try to parse it like normal. See
// https://vercel.com/docs/edge-network/headers/request-headers#custom-x-forwarded-for-ip
const xVercelForwardedFor = headers.get("x-vercel-forwarded-for");
const xVercelForwardedForItems = parseXForwardedFor(xVercelForwardedFor);
// As per MDN X-Forwarded-For Headers documentation at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
// We may find more than one IP in the `x-forwarded-for` header. Since the
// first IP will be closest to the user (and the most likely to be spoofed),
// we want to iterate tail-to-head so we reverse the list.
for (const item of xVercelForwardedForItems.reverse()) {
if (isGlobalIP(item)) {
return item;
}
}

// https://vercel.com/docs/edge-network/headers/request-headers#x-forwarded-for
// By default, it seems this will be 1 address, but they discuss trusted
// proxy forwarding so we try to parse it like normal. See
// https://vercel.com/docs/edge-network/headers/request-headers#custom-x-forwarded-for-ip
const xForwardedFor = headers.get("x-forwarded-for");
const xForwardedForItems = parseXForwardedFor(xForwardedFor);
// As per MDN X-Forwarded-For Headers documentation at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
// We may find more than one IP in the `x-forwarded-for` header. Since the
// first IP will be closest to the user (and the most likely to be spoofed),
// we want to iterate tail-to-head so we reverse the list.
for (const item of xForwardedForItems.reverse()) {
if (isGlobalIP(item)) {
return item;
}
}

// If we are using a platform check and don't have a Global IP, we exit
// early with an empty IP since the more generic headers shouldn't be
// trusted over the platform-specific headers.
return "";
}

// Standard headers used by Amazon EC2, Heroku, and others.
const xClientIP = headers.get("x-client-ip");
if (isGlobalIP(xClientIP)) {
Expand Down
3 changes: 3 additions & 0 deletions ip/test/ipv4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ describe("find public IPv4", () => {
headerSuite("X-Client-IP");
headerSuite("X-Forwarded-For");
headerSuite("CF-Connecting-IP", { platform: "cloudflare" });
headerSuite("X-Real-IP", { platform: "vercel" });
headerSuite("X-Vercel-Forwarded-For", { platform: "vercel" });
headerSuite("X-Forwarded-For", { platform: "vercel" });
headerSuite("DO-Connecting-IP");
headerSuite("Fastly-Client-IP");
headerSuite("Fly-Client-IP", { platform: "fly-io" });
Expand Down
3 changes: 3 additions & 0 deletions ip/test/ipv6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ describe("find public IPv6", () => {
headerSuite("X-Forwarded-For");
headerSuite("CF-Connecting-IPv6", { platform: "cloudflare" });
headerSuite("CF-Connecting-IP", { platform: "cloudflare" });
headerSuite("X-Real-IP", { platform: "vercel" });
headerSuite("X-Vercel-Forwarded-For", { platform: "vercel" });
headerSuite("X-Forwarded-For", { platform: "vercel" });
headerSuite("DO-Connecting-IP");
headerSuite("Fastly-Client-IP");
headerSuite("Fly-Client-IP", { platform: "fly-io" });
Expand Down

0 comments on commit d886c76

Please sign in to comment.