@@ -66,14 +66,56 @@ def cleanup_rate_limit_storage():
6666 if not rate_limit_storage [key ]:
6767 del rate_limit_storage [key ]
6868
69- def get_client_ip ():
70- """Get client IP address, considering proxy headers"""
71- if request .headers .get ('X-Forwarded-For' ):
72- return request .headers .get ('X-Forwarded-For' ).split (',' )[0 ].strip ()
73- elif request .headers .get ('X-Real-IP' ):
74- return request .headers .get ('X-Real-IP' )
75- else :
76- return request .remote_addr
69+ def is_valid_ip (ip ):
70+ """Validate IP address format"""
71+ import ipaddress
72+ try :
73+ ipaddress .ip_address (ip )
74+ return True
75+ except ValueError :
76+ return False
77+
78+ def get_rate_limiting_identifiers ():
79+ """
80+ Get multiple identifiers for rate limiting without requiring proxy knowledge
81+ Uses defense-in-depth approach instead of perfect IP detection
82+ """
83+ import hashlib
84+
85+ # 1. ALWAYS use the direct connection IP (most reliable)
86+ remote_ip = request .remote_addr
87+ identifiers = [f"direct_{ remote_ip } " ]
88+
89+ # 2. If proxy headers exist, use them BUT with lower trust
90+ forwarded_for = request .headers .get ('X-Forwarded-For' )
91+ if forwarded_for :
92+ # Take first IP from chain
93+ client_ip = forwarded_for .split (',' )[0 ].strip ()
94+ if is_valid_ip (client_ip ) and client_ip != remote_ip :
95+ identifiers .append (f"proxy_{ client_ip } " )
96+
97+ real_ip = request .headers .get ('X-Real-IP' )
98+ if real_ip and is_valid_ip (real_ip ) and real_ip != remote_ip :
99+ identifiers .append (f"realip_{ real_ip } " )
100+
101+ # 3. Create a session-based identifier (harder to spoof)
102+ user_agent = request .headers .get ('User-Agent' , '' )[:100 ] # Limit length
103+ session_fingerprint = hashlib .md5 (f"{ remote_ip } :{ user_agent } " .encode ()).hexdigest ()[:12 ]
104+ identifiers .append (f"session_{ session_fingerprint } " )
105+
106+ return identifiers
107+
108+ def enhanced_rate_limit_check (identifiers , limit ):
109+ """
110+ Check rate limits across multiple identifiers
111+ ANY identifier hitting the limit blocks the request
112+ """
113+ for identifier in identifiers :
114+ allowed , count , _ = check_rate_limit (identifier , limit )
115+ if not allowed :
116+ return False , identifier , count , limit
117+
118+ return True , None , 0 , limit
77119
78120def check_rate_limit (key , limit ):
79121 """Check if the request should be rate limited"""
@@ -129,6 +171,10 @@ def decorated_function(*args, **kwargs):
129171 return jsonify ({
130172 'status' : 'error' ,
131173 'message' : f'Rate limit exceeded for IP address. This IP has made { ip_count } requests in the last 3 hours. Limit is { ip_limit } requests per 3 hours.' ,
174+ 'rate_limit_info' : {
175+ 'limit_type' : 'authenticated_ return jsonify ({
176+ 'status' : 'error' ,
177+ 'message' : f'Rate limit exceeded for IP address. This IP has made { ip_count } requests in the last 3 hours. Limit is { ip_limit } requests per 3 hours.' ,
132178 'rate_limit_info' : {
133179 'limit_type' : 'authenticated_ip' ,
134180 'current_count' : ip_count ,
@@ -1674,37 +1720,63 @@ def ai_system_info():
16741720def ai_rate_limit_status ():
16751721 """
16761722 Check current rate limit status for AI endpoints
1677- Useful for debugging and transparency
1723+ Now uses secure IP detection to prevent spoofing
16781724 """
16791725 try :
16801726 cleanup_rate_limit_storage ()
1681- client_ip = get_client_ip ()
1727+ ip_identifiers = get_multiple_ip_identifiers ()
1728+ remote_ip = request .remote_addr
16821729 current_time = time .time ()
16831730
1731+ # Check for potential spoofing attempts
1732+ forwarded_for = request .headers .get ('X-Forwarded-For' )
1733+ real_ip = request .headers .get ('X-Real-IP' )
1734+ is_spoofing_attempt = (forwarded_for or real_ip ) and not is_trusted_proxy (remote_ip , {
1735+ '127.0.0.1' , '::1' , '10.0.0.0/8' , '172.16.0.0/12' , '192.168.0.0/16'
1736+ })
1737+
16841738 status = {
16851739 'status' : 'success' ,
1686- 'client_ip' : client_ip ,
1740+ 'security_info' : {
1741+ 'remote_ip' : remote_ip ,
1742+ 'client_ips' : ip_identifiers ,
1743+ 'spoofing_detected' : is_spoofing_attempt ,
1744+ 'headers_checked' : {
1745+ 'x_forwarded_for' : forwarded_for ,
1746+ 'x_real_ip' : real_ip
1747+ } if is_spoofing_attempt else None
1748+ },
16871749 'rate_limits' : {
16881750 'unauthenticated' : {
16891751 'limit' : UNAUTHENTICATED_LIMIT ,
16901752 'window_hours' : 3 ,
1691- 'requests_made' : 0
1753+ 'requests_made' : 0 ,
1754+ 'ip_breakdown' : {}
16921755 },
16931756 'authenticated' : {
16941757 'limit' : AUTHENTICATED_LIMIT ,
16951758 'window_hours' : 3 ,
16961759 'user_requests_made' : 0 ,
1697- 'ip_requests_made' : 0
1760+ 'ip_requests_made' : 0 ,
1761+ 'ip_breakdown' : {}
16981762 }
16991763 }
17001764 }
17011765
1702- # Check unauthenticated rate limit
1703- unauth_key = f"ai_unauth_ip_{ client_ip } "
1704- unauth_count = sum (count for timestamp , count in rate_limit_storage [unauth_key ]
1705- if timestamp > current_time - RATE_LIMIT_WINDOW )
1706- status ['rate_limits' ]['unauthenticated' ]['requests_made' ] = unauth_count
1707- status ['rate_limits' ]['unauthenticated' ]['remaining' ] = max (0 , UNAUTHENTICATED_LIMIT - unauth_count )
1766+ # Check unauthenticated rate limits for all IP identifiers
1767+ total_unauth_requests = 0
1768+ for ip_addr in ip_identifiers :
1769+ unauth_key = f"ai_unauth_ip_{ ip_addr } "
1770+ unauth_count = sum (count for timestamp , count in rate_limit_storage [unauth_key ]
1771+ if timestamp > current_time - RATE_LIMIT_WINDOW )
1772+ total_unauth_requests += unauth_count
1773+ status ['rate_limits' ]['unauthenticated' ]['ip_breakdown' ][ip_addr ] = {
1774+ 'requests_made' : unauth_count ,
1775+ 'remaining' : max (0 , UNAUTHENTICATED_LIMIT - unauth_count )
1776+ }
1777+
1778+ status ['rate_limits' ]['unauthenticated' ]['requests_made' ] = total_unauth_requests
1779+ status ['rate_limits' ]['unauthenticated' ]['remaining' ] = max (0 , UNAUTHENTICATED_LIMIT - total_unauth_requests )
17081780
17091781 # Check if user is authenticated
17101782 auth_header = request .headers .get ('Authorization' )
@@ -1713,19 +1785,27 @@ def ai_rate_limit_status():
17131785 try :
17141786 user_data = verify_token (token )
17151787 if user_data :
1716- # Check authenticated rate limits
17171788 user_key = f"ai_auth_user_{ user_data ['user_id' ]} "
1718- ip_key = f"ai_auth_ip_{ client_ip } "
17191789
17201790 user_count = sum (count for timestamp , count in rate_limit_storage [user_key ]
17211791 if timestamp > current_time - RATE_LIMIT_WINDOW )
1722- ip_count = sum (count for timestamp , count in rate_limit_storage [ip_key ]
1723- if timestamp > current_time - RATE_LIMIT_WINDOW )
1792+
1793+ # Check IP-based limits for all identifiers
1794+ total_auth_ip_requests = 0
1795+ for ip_addr in ip_identifiers :
1796+ ip_key = f"ai_auth_ip_{ ip_addr } "
1797+ ip_count = sum (count for timestamp , count in rate_limit_storage [ip_key ]
1798+ if timestamp > current_time - RATE_LIMIT_WINDOW )
1799+ total_auth_ip_requests += ip_count
1800+ status ['rate_limits' ]['authenticated' ]['ip_breakdown' ][ip_addr ] = {
1801+ 'requests_made' : ip_count ,
1802+ 'remaining' : max (0 , AUTHENTICATED_LIMIT - ip_count )
1803+ }
17241804
17251805 status ['rate_limits' ]['authenticated' ]['user_requests_made' ] = user_count
1726- status ['rate_limits' ]['authenticated' ]['ip_requests_made' ] = ip_count
1806+ status ['rate_limits' ]['authenticated' ]['ip_requests_made' ] = total_auth_ip_requests
17271807 status ['rate_limits' ]['authenticated' ]['user_remaining' ] = max (0 , AUTHENTICATED_LIMIT - user_count )
1728- status ['rate_limits' ]['authenticated' ]['ip_remaining' ] = max (0 , AUTHENTICATED_LIMIT - ip_count )
1808+ status ['rate_limits' ]['authenticated' ]['ip_remaining' ] = max (0 , AUTHENTICATED_LIMIT - total_auth_ip_requests )
17291809 status ['authenticated_user' ] = {
17301810 'user_id' : user_data ['user_id' ],
17311811 'username' : user_data ['username' ]
0 commit comments