@@ -88,7 +88,7 @@ function isPrivateIp(ip) {
8888 if ( ! ip ) return false ;
8989
9090 // Clean up Node's IPv6-mapped IPv4 prefix (::ffff:)
91- const cleanIp = ip . replace ( / ^ : : f f f f : / , '' ) ;
91+ const cleanIp = ip . replace ( / ^ : : f f f f : / , '' ) . trim ( ) ;
9292
9393 if ( cleanIp === '::1' || cleanIp === 'localhost' ) return true ;
9494
@@ -98,7 +98,7 @@ function isPrivateIp(ip) {
9898 const first = parseInt ( parts [ 0 ] , 10 ) ;
9999 const second = parseInt ( parts [ 1 ] , 10 ) ;
100100
101- // Standard Private Ranges
101+ // Standard Private Ranges (10.x, 192.168.x, 172.16-31.x, 127.x)
102102 if ( first === 127 || first === 10 ) return true ;
103103 if ( first === 192 && second === 168 ) return true ;
104104 if ( first === 172 && second >= 16 && second <= 31 ) return true ;
@@ -107,87 +107,26 @@ function isPrivateIp(ip) {
107107}
108108
109109function getIPFromRequest ( req ) {
110- // 1. Always check Cloudflare/Render verified header first
111- if ( req . headers [ 'cf-connecting-ip' ] ) {
112- return req . headers [ 'cf-connecting-ip' ] ;
113- }
114-
115- const ipListHeader = req . headers [ 'x-forwarded-for' ] ;
116- if ( ipListHeader ) {
117- const IPs = ipListHeader . split ( "," ) . map ( ( ip ) => ip . trim ( ) ) ;
118-
119- // Start from the right (the most recent proxy)
120- for ( let i = IPs . length - 1 ; i >= 0 ; i -- ) {
121- const curIp = IPs [ i ] ;
122- // If we hit an IP that is NOT private, check if it's the last one left
123- // or if it's a known public proxy (like Render's Azure IPs).
124- if ( ! isPrivateIp ( curIp ) ) {
125- // If we are at the very first IP (index 0), it's definitely the user.
126- if ( i === 0 ) return curIp ;
127-
128- // If this isn't the first IP, it might be Render's public proxy.
129- // We keep looping until we hit index 0.
130- continue ;
131- }
132- }
133- // If the loop finished or we want the most likely candidate:
134- return IPs [ 0 ] ;
135- }
136-
137- // Fallback to socket address, cleaning the prefix if it exists
138- return ( req . socket . remoteAddress || "" ) . replace ( / ^ : : f f f f : / , '' ) ;
139- } function isPrivateIp ( ip ) {
140- if ( ! ip ) return false ;
141-
142- // Clean up Node's IPv6-mapped IPv4 prefix (::ffff:)
143- const cleanIp = ip . replace ( / ^ : : f f f f : / , '' ) ;
144-
145- if ( cleanIp === '::1' || cleanIp === 'localhost' ) return true ;
146-
147- const parts = cleanIp . split ( '.' ) ;
148- if ( parts . length !== 4 ) return false ;
149-
150- const first = parseInt ( parts [ 0 ] , 10 ) ;
151- const second = parseInt ( parts [ 1 ] , 10 ) ;
152-
153- // Standard Private Ranges
154- if ( first === 127 || first === 10 ) return true ;
155- if ( first === 192 && second === 168 ) return true ;
156- if ( first === 172 && second >= 16 && second <= 31 ) return true ;
157-
158- return false ;
159- }
160-
161- function getIPFromRequest ( req ) {
162- // 1. Always check Cloudflare/Render verified header first
163- if ( req . headers [ 'cf-connecting-ip' ] ) {
164- return req . headers [ 'cf-connecting-ip' ] ;
165- }
166-
167- const ipListHeader = req . headers [ 'x-forwarded-for' ] ;
168- if ( ipListHeader ) {
169- const IPs = ipListHeader . split ( "," ) . map ( ( ip ) => ip . trim ( ) ) ;
170-
171- // Start from the right (the most recent proxy)
172- for ( let i = IPs . length - 1 ; i >= 0 ; i -- ) {
173- const curIp = IPs [ i ] ;
174- // If we hit an IP that is NOT private, check if it's the last one left
175- // or if it's a known public proxy (like Render's Azure IPs).
176- if ( ! isPrivateIp ( curIp ) ) {
177- // If we are at the very first IP (index 0), it's definitely the user.
178- if ( i === 0 ) return curIp ;
179-
180- // If this isn't the first IP, it might be Render's public proxy.
181- // We keep looping until we hit index 0.
182- continue ;
183- }
110+ // 1. Priority: The 'cf-connecting-ip' is provided by Render's edge.
111+ // This is the "gold standard" and is very hard to spoof.
112+ const cfIp = req . headers [ 'cf-connecting-ip' ] ;
113+ if ( cfIp ) return cfIp . trim ( ) ;
114+
115+ // 2. Fallback: Parse the X-Forwarded-For list.
116+ const xff = req . headers [ 'x-forwarded-for' ] ;
117+ if ( xff ) {
118+ // We split the list into an array of IPs.
119+ const IPs = xff . split ( ',' ) . map ( ip => ip . trim ( ) ) ;
120+
121+ // On Render, the user's real IP is ALWAYS the first one (index 0).
122+ // The others are Render/Azure/Cloudflare proxies.
123+ if ( IPs . length > 0 ) {
124+ return IPs [ 0 ] ;
184125 }
185- // If the loop finished or we want the most likely candidate:
186- return IPs [ 0 ] ;
187126 }
188127
189- // Fallback to socket address, cleaning the prefix if it exists
190- return ( req . socket . remoteAddress || "" ) . replace ( / ^ : : f f f f : / , '' ) ;
128+ // 3. Final Fallback: The direct connection IP (usually a proxy IP on Render)
129+ return ( req . socket . remoteAddress || "" ) . replace ( / ^ : : f f f f : / , '' ) . trim ( ) ;
191130}
192131
193132var ipBanReasons = {
0 commit comments