fix(diffs): harden viewer proxy access (#57912)

* fix(diffs): harden viewer proxy access

* fix(diffs): restore mapped loopback access
This commit is contained in:
Agustin Rivera
2026-03-30 13:17:27 -07:00
committed by GitHub
parent 910134b702
commit 30a1690323
4 changed files with 84 additions and 22 deletions

View File

@@ -1,5 +1,6 @@
import type { IncomingMessage, ServerResponse } from "node:http";
import type { PluginLogger } from "../api.js";
import { resolveRequestClientIp } from "../runtime-api.js";
import type { DiffArtifactStore } from "./store.js";
import { DIFF_ARTIFACT_ID_PATTERN, DIFF_ARTIFACT_TOKEN_PATTERN } from "./types.js";
import { VIEWER_ASSET_PREFIX, getServedViewerAsset } from "./viewer-assets.js";
@@ -25,6 +26,8 @@ export function createDiffsHttpHandler(params: {
store: DiffArtifactStore;
logger?: PluginLogger;
allowRemoteViewer?: boolean;
trustedProxies?: readonly string[];
allowRealIpFallback?: boolean;
}) {
const viewerFailureLimiter = new ViewerFailureLimiter();
@@ -42,7 +45,10 @@ export function createDiffsHttpHandler(params: {
return false;
}
const access = resolveViewerAccess(req);
const access = resolveViewerAccess(req, {
trustedProxies: params.trustedProxies,
allowRealIpFallback: params.allowRealIpFallback,
});
if (!access.localRequest && params.allowRemoteViewer !== true) {
respondText(res, 404, "Diff not found");
return true;
@@ -186,12 +192,30 @@ function hasProxyForwardingHints(req: IncomingMessage): boolean {
);
}
function resolveViewerAccess(req: IncomingMessage): {
function resolveViewerAccess(
req: IncomingMessage,
params: {
trustedProxies?: readonly string[];
allowRealIpFallback?: boolean;
},
): {
remoteKey: string;
localRequest: boolean;
} {
const remoteKey = normalizeRemoteClientKey(req.socket?.remoteAddress);
const localRequest = isLoopbackClientIp(remoteKey) && !hasProxyForwardingHints(req);
const proxyHintsPresent = hasProxyForwardingHints(req);
const clientIp =
proxyHintsPresent || (params.trustedProxies?.length ?? 0) > 0
? // Reuse gateway proxy trust rules and fail closed when a trusted proxy hop
// does not provide usable client-origin headers.
resolveRequestClientIp(
req,
params.trustedProxies ? [...params.trustedProxies] : undefined,
params.allowRealIpFallback === true,
)
: req.socket?.remoteAddress;
const remoteKey = normalizeRemoteClientKey(clientIp ?? req.socket?.remoteAddress);
const localRequest =
!proxyHintsPresent && typeof clientIp === "string" && isLoopbackClientIp(remoteKey);
return { remoteKey, localRequest };
}