Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions vtex/loaders/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ const buildProxyRoutes = (

return ({
pathTemplate,
// These are VTEX system paths — checkout, account, login, /api, /_v.
// Without the priority bump a catch-all `/*` route from an A/B test
// audience outranks them (1000 + rank("/*") = 1003 beats rank
// ("/checkout") = 6) and swallows the whole platform surface.
highPriority: true,
Comment on lines +84 to +88

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify how extraPaths are defined and whether callers expect normal priority.
rg -n -C 8 '\bextraPaths\b|\bPATHS_TO_PROXY\b|\bhighPriority\b' --glob '*.ts' .

Repository: deco-cx/apps

Length of output: 24295


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'vtex proxy relevant section:\n'
sed -n '1,120p' vtex/loaders/proxy.ts | cat -n

printf '\nwebsite router relevant section:\n'
sed -n '1,180p' website/handlers/router.ts | cat -n

printf '\nwebsite audience/mod relevant section:\n'
sed -n '1,260p' website/mod.ts | cat -n

printf '\nreferences to extraPathsToProxy and extraPaths in vtex:\n'
rg -n -C 6 'extraPaths(ToProxy)?|PathsToProxy' vtex --glob '*.ts'

Repository: deco-cx/apps

Length of output: 23663


Keep extraPaths out of the high-priority block.

routeFromPath is closed over highPriority: true and invoked for both PATHS_TO_PROXY and every configured extraPaths. Use normal priority for extraPaths, or explicitly document these routes as VTEX system paths that must bypass A/B test catch-all routes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vtex/loaders/proxy.ts` around lines 84 - 88, Update the route construction
around routeFromPath so highPriority: true applies only to entries from
PATHS_TO_PROXY, not configured extraPaths. Ensure extraPaths use normal priority
unless they are explicitly classified as VTEX system paths requiring the same
bypass behavior.

handler: {
value: handlerValue,
},
Expand Down
35 changes: 35 additions & 0 deletions website/handlers/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ const HOP_BY_HOP = [
const noTrailingSlashes = (str: string) =>
str.at(-1) === "/" ? str.slice(0, -1) : str;
const sanitize = (str: string) => str.startsWith("/") ? str : `/${str}`;
/**
* Canonical form of an IP for comparison only — never for forwarding.
* x-forwarded-for entries may be bracketed and carry a port ([::1]:443,
* 1.2.3.4:56789) and IPv6 hex casing varies between hops; cf-connecting-ip
* is always a bare address.
*/
const normalizeIp = (value: string): string => {
const ip = value.trim().toLowerCase();
const bracketed = ip.match(/^\[(.+)\](?::\d+)?$/);
if (bracketed) return bracketed[1];
const ipv4WithPort = ip.match(/^([\d.]+):\d+$/);
if (ipv4WithPort) return ipv4WithPort[1];
return ip;
};
Comment on lines +26 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Equivalent IPv6 spellings can still be prepended as duplicate client entries because normalizeIp is not actually canonical for IPv6. Canonicalize parsed IPv6 (including mapped forms) before the deduplication comparison while preserving the original forwarded value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At website/handlers/proxy.ts, line 26:

<comment>Equivalent IPv6 spellings can still be prepended as duplicate client entries because `normalizeIp` is not actually canonical for IPv6. Canonicalize parsed IPv6 (including mapped forms) before the deduplication comparison while preserving the original forwarded value.</comment>

<file context>
@@ -17,6 +17,20 @@ const HOP_BY_HOP = [
+ * 1.2.3.4:56789) and IPv6 hex casing varies between hops; cf-connecting-ip
+ * is always a bare address.
+ */
+const normalizeIp = (value: string): string => {
+  const ip = value.trim().toLowerCase();
+  const bracketed = ip.match(/^\[(.+)\](?::\d+)?$/);
</file context>
Suggested change
const normalizeIp = (value: string): string => {
const ip = value.trim().toLowerCase();
const bracketed = ip.match(/^\[(.+)\](?::\d+)?$/);
if (bracketed) return bracketed[1];
const ipv4WithPort = ip.match(/^([\d.]+):\d+$/);
if (ipv4WithPort) return ipv4WithPort[1];
return ip;
};
const normalizeIp = (value: string): string => {
const ip = value.trim().toLowerCase();
const bracketed = ip.match(/^\[(.+)\](?::\d+)?$/);
const host = bracketed?.[1] ??
ip.match(/^([\d.]+):\d+$/)?.[1] ??
ip;
if (!host.includes(":")) return host;
try {
return new URL(`http://[${host}]`).hostname.slice(1, -1);
} catch {
return host;
}
};

export const removeCFHeaders = (headers: Headers) => {
headers.forEach((_value, key) => {
if (key.startsWith("cf-")) {
Expand Down Expand Up @@ -145,7 +159,28 @@ export default function Proxy({
if (isFreshCtx<DecoSiteState>(_ctx)) {
_ctx?.state?.monitoring?.logger?.log?.("proxy received headers", headers);
}
// cf-connecting-ip carries the real client IP and removeCFHeaders is about
// to drop it, leaving the proxied origin without x-real-ip. x-forwarded-for
// usually already arrives with the client IP first, so only fill the gaps.
//
// Trust boundary: these headers are only as trustworthy as the ingress in
// front of this handler. x-forwarded-for is already forwarded untouched, so
// an origin reachable outside the CDN could always be fed a forged first
// entry — deriving x-real-ip from cf-connecting-ip does not widen that.
// Authenticating the edge belongs at the ingress, not here.
const clientIp = headers.get("cf-connecting-ip");
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
removeCFHeaders(headers); // cf-headers are not ASCII-compliant
if (clientIp) {
const forwardedFor = headers.get("x-forwarded-for");
if (!forwardedFor) {
headers.set("x-forwarded-for", clientIp);
} else if (
normalizeIp(forwardedFor.split(",")[0]) !== normalizeIp(clientIp)
) {
headers.set("x-forwarded-for", `${clientIp}, ${forwardedFor}`);
}
headers.set("x-real-ip", clientIp);
}
if (removeDirtyCookies) {
removeDirtyCookiesFn(headers);
}
Expand Down
Loading