@@ -21,6 +21,7 @@ const MAX_FORWARDED_FOR_LENGTH = 2048;
2121const MAX_FORWARDED_FOR_HOPS = 32 ;
2222
2323let cachedTrustedProxyIpEnv : string | undefined | null = null ;
24+ let cachedTrustedProxyEnableEnv : boolean | undefined | null = null ;
2425let cachedNodeEnv : string | undefined | null = null ;
2526let cachedTrustProxyFn : TrustProxyFn = proxyaddr . compile ( [ ] ) ;
2627let warnedInvalidTrustedProxyIpEnv : string | undefined ;
@@ -107,22 +108,31 @@ const parseTrustedProxyIpEnv = (trustedProxyIpEnv?: string) => {
107108 return Array . from ( validAddresses ) ;
108109} ;
109110
110- // 构建并缓存 proxy-addr 的信任判定函数;非生产环境默认信任 loopback,叠加 TRUSTED_PROXY_IPS 配置。
111+ // 构建并缓存 proxy-addr 的信任判定函数;用于 TRUSTED_PROXY_ENABLE=true 的可信代理校验模式。
112+ // 可信代理校验模式下,非生产环境默认信任 loopback,并叠加 TRUSTED_PROXY_IPS 配置。
111113// 仅当环境变量或 NODE_ENV 变化时才重新编译,避免每次请求都重复解析。
112114const getTrustProxyFn = ( ) => {
115+ const trustedProxyEnable = env . TRUSTED_PROXY_ENABLE ;
113116 const trustedProxyIpEnv = env . TRUSTED_PROXY_IPS ;
114117 const nodeEnv = process . env . NODE_ENV ;
115- if ( trustedProxyIpEnv === cachedTrustedProxyIpEnv && nodeEnv === cachedNodeEnv ) {
118+ if (
119+ trustedProxyEnable === cachedTrustedProxyEnableEnv &&
120+ trustedProxyIpEnv === cachedTrustedProxyIpEnv &&
121+ nodeEnv === cachedNodeEnv
122+ ) {
116123 return cachedTrustProxyFn ;
117124 }
118125
126+ cachedTrustedProxyEnableEnv = trustedProxyEnable ;
119127 cachedTrustedProxyIpEnv = trustedProxyIpEnv ;
120128 cachedNodeEnv = nodeEnv ;
121129
122- const trustedProxyAddresses = [
123- ...( nodeEnv === 'production' ? [ ] : ( [ 'loopback' ] satisfies proxyaddr . Address [ ] ) ) ,
124- ...parseTrustedProxyIpEnv ( trustedProxyIpEnv )
125- ] ;
130+ const trustedProxyAddresses = trustedProxyEnable
131+ ? [
132+ ...( nodeEnv === 'production' ? [ ] : ( [ 'loopback' ] satisfies proxyaddr . Address [ ] ) ) ,
133+ ...parseTrustedProxyIpEnv ( trustedProxyIpEnv )
134+ ]
135+ : [ ] ;
126136 cachedTrustProxyFn = proxyaddr . compile ( trustedProxyAddresses ) ;
127137
128138 return cachedTrustProxyFn ;
@@ -149,11 +159,6 @@ export const isTrustedProxyIp = (rawIp?: string | null) => {
149159
150160// 取 TCP 连接对端地址(socket / 旧版 connection 兜底),作为最可信的回退来源。
151161const getRemoteIp = ( req : RequestWithClientIp ) => {
152- console . log ( {
153- remoteAddress : req . socket ?. remoteAddress ,
154- xForwardedFor : req . headers ?. [ 'x-forwarded-for' ] ,
155- xRealIp : req . headers ?. [ 'x-real-ip' ]
156- } ) ;
157162 return normalizeClientIp ( req . socket ?. remoteAddress ?? req . connection ?. remoteAddress ) ;
158163} ;
159164
@@ -168,6 +173,19 @@ const getForwardedFor = (req: RequestWithClientIp) => {
168173 return getHeaderValue ( req . headers , 'x-forwarded-for' ) ;
169174} ;
170175
176+ // 关闭可信代理校验时的兼容模式:直接相信转发头。
177+ // X-Forwarded-For 按行业约定取最左侧 IP;如果不存在或非法,再尝试 X-Real-IP。
178+ const getClientIpFromForwardingHeaders = ( req : RequestWithClientIp ) => {
179+ const forwardedFor = getForwardedFor ( req ) ;
180+ if ( forwardedFor && forwardedFor . length <= MAX_FORWARDED_FOR_LENGTH ) {
181+ const firstForwardedIp = forwardedFor . split ( ',' ) [ 0 ] ?. trim ( ) ;
182+ const ip = normalizeClientIp ( firstForwardedIp ) ;
183+ if ( ip ) return ip ;
184+ }
185+
186+ return getClientIpFromRealIp ( req ) ;
187+ } ;
188+
171189// 在调用 proxy-addr 前对 XFF 做尺寸/跳数/格式预检,防止超长或畸形头造成解析放大攻击。
172190const isForwardedForSafeToParse = ( forwardedFor : string ) => {
173191 if ( forwardedFor . length > MAX_FORWARDED_FOR_LENGTH ) return false ;
@@ -195,11 +213,16 @@ const createProxyAddrRequest = (remoteAddress: string, forwardedFor: string) =>
195213
196214// 对外:从请求中解析出最终客户端 IP。
197215// 策略:
198- // 1. 取 socket 远端 IP 作为底线;若不可解析直接返回 undefined。
199- // 2. 远端不在受信代理列表 -> 直接返回远端 IP,忽略一切转发头(防伪造)。
200- // 3. 远端可信 -> 优先用 X-Forwarded-For(经安全校验后交给 proxy-addr 沿信任链回溯),
216+ // 1. TRUSTED_PROXY_ENABLE=false -> 兼容模式,直接相信 X-Forwarded-For / X-Real-IP,再回退远端 IP。
217+ // 2. TRUSTED_PROXY_ENABLE=true -> 可信代理校验模式,先取 socket 远端 IP 作为底线;若不可解析直接返回 undefined。
218+ // 3. 远端不在受信代理列表 -> 直接返回远端 IP,忽略一切转发头(防伪造)。
219+ // 4. 远端可信 -> 优先用 X-Forwarded-For(经安全校验后交给 proxy-addr 沿信任链回溯),
201220// 否则回退 X-Real-IP;校验失败或转发头本身仍是受信代理时退回远端 IP。
202221export const getClientIpFromRequest = ( req : RequestWithClientIp ) => {
222+ if ( ! env . TRUSTED_PROXY_ENABLE ) {
223+ return getClientIpFromForwardingHeaders ( req ) ?? getRemoteIp ( req ) ;
224+ }
225+
203226 const remoteIp = getRemoteIp ( req ) ;
204227 if ( ! remoteIp ) return ;
205228
0 commit comments