Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -190,8 +190,10 @@ def _flush_request_log():
|
|
| 190 |
except Exception as e:
|
| 191 |
print(f"Warning: Failed to flush request log: {e}")
|
| 192 |
|
| 193 |
-
def _get_recent_requests_from_log(ip_address: str) ->
|
| 194 |
-
"""Get recent requests for an IP from the log file (with caching)
|
|
|
|
|
|
|
| 195 |
now_time = time.time()
|
| 196 |
now_dt = datetime.now(timezone.utc)
|
| 197 |
|
|
@@ -210,11 +212,14 @@ def _get_recent_requests_from_log(ip_address: str) -> list:
|
|
| 210 |
_request_log_cache["data"] = log_data
|
| 211 |
_request_log_cache["ts"] = now_time
|
| 212 |
except Exception:
|
| 213 |
-
# If log doesn't exist or there's an error, return empty
|
| 214 |
-
return []
|
| 215 |
|
| 216 |
# Filter for this IP and recent requests
|
| 217 |
recent_timestamps = []
|
|
|
|
|
|
|
|
|
|
| 218 |
for row in log_data:
|
| 219 |
if row.get('ip_address') == ip_address:
|
| 220 |
try:
|
|
@@ -224,18 +229,22 @@ def _get_recent_requests_from_log(ip_address: str) -> list:
|
|
| 224 |
# Only consider requests from the last hour
|
| 225 |
if age_seconds < 3600:
|
| 226 |
recent_timestamps.append(age_seconds)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
except (ValueError, KeyError):
|
| 228 |
continue
|
| 229 |
|
| 230 |
-
return recent_timestamps
|
| 231 |
|
| 232 |
-
def _check_suspicious_activity(ip_address: str, order_number: str) -> list:
|
| 233 |
"""Check for suspicious patterns and return list of flags"""
|
| 234 |
flags = []
|
| 235 |
now_dt = datetime.now(timezone.utc)
|
| 236 |
|
| 237 |
# Get recent requests from the persistent log
|
| 238 |
-
recent_request_ages = _get_recent_requests_from_log(ip_address)
|
| 239 |
|
| 240 |
# Also check the buffer for requests not yet written to the log
|
| 241 |
for entry in _request_log_buffer:
|
|
@@ -245,6 +254,9 @@ def _check_suspicious_activity(ip_address: str, order_number: str) -> list:
|
|
| 245 |
age_seconds = (now_dt - timestamp).total_seconds()
|
| 246 |
if age_seconds < 3600:
|
| 247 |
recent_request_ages.append(age_seconds)
|
|
|
|
|
|
|
|
|
|
| 248 |
except (ValueError, KeyError):
|
| 249 |
continue
|
| 250 |
|
|
@@ -255,16 +267,35 @@ def _check_suspicious_activity(ip_address: str, order_number: str) -> list:
|
|
| 255 |
if total_requests > 20:
|
| 256 |
flags.append("HIGH_FREQUENCY")
|
| 257 |
|
| 258 |
-
# Flag: More than
|
| 259 |
last_minute = sum(1 for age in recent_request_ages if age < 60) + 1
|
| 260 |
-
if last_minute >
|
| 261 |
flags.append("RAPID_REQUESTS")
|
| 262 |
|
| 263 |
-
# Flag: More than
|
| 264 |
last_5_min = sum(1 for age in recent_request_ages if age < 300) + 1
|
| 265 |
-
if last_5_min >
|
| 266 |
flags.append("BURST_PATTERN")
|
| 267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
return flags
|
| 269 |
|
| 270 |
# -------------------------
|
|
@@ -289,7 +320,16 @@ def claim_c_key(
|
|
| 289 |
order_number = order_number.strip()
|
| 290 |
|
| 291 |
# Check for suspicious activity patterns
|
| 292 |
-
suspicious_flags = _check_suspicious_activity(ip_address, order_number)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
if not order_number:
|
| 295 |
_log_request(order_number, ip_address, user_agent, False, "Empty order number", suspicious_flags)
|
|
@@ -417,3 +457,4 @@ with gr.Blocks(title="API") as demo:
|
|
| 417 |
demo.queue()
|
| 418 |
demo.launch()
|
| 419 |
|
|
|
|
|
|
| 190 |
except Exception as e:
|
| 191 |
print(f"Warning: Failed to flush request log: {e}")
|
| 192 |
|
| 193 |
+
def _get_recent_requests_from_log(ip_address: str) -> tuple:
|
| 194 |
+
"""Get recent requests for an IP from the log file (with caching)
|
| 195 |
+
Returns: (list of ages in seconds, set of user_agents, set of order_numbers)
|
| 196 |
+
"""
|
| 197 |
now_time = time.time()
|
| 198 |
now_dt = datetime.now(timezone.utc)
|
| 199 |
|
|
|
|
| 212 |
_request_log_cache["data"] = log_data
|
| 213 |
_request_log_cache["ts"] = now_time
|
| 214 |
except Exception:
|
| 215 |
+
# If log doesn't exist or there's an error, return empty data
|
| 216 |
+
return [], set(), set()
|
| 217 |
|
| 218 |
# Filter for this IP and recent requests
|
| 219 |
recent_timestamps = []
|
| 220 |
+
user_agents = set()
|
| 221 |
+
order_numbers = set()
|
| 222 |
+
|
| 223 |
for row in log_data:
|
| 224 |
if row.get('ip_address') == ip_address:
|
| 225 |
try:
|
|
|
|
| 229 |
# Only consider requests from the last hour
|
| 230 |
if age_seconds < 3600:
|
| 231 |
recent_timestamps.append(age_seconds)
|
| 232 |
+
if row.get('user_agent'):
|
| 233 |
+
user_agents.add(row['user_agent'])
|
| 234 |
+
if row.get('order_number'):
|
| 235 |
+
order_numbers.add(row['order_number'])
|
| 236 |
except (ValueError, KeyError):
|
| 237 |
continue
|
| 238 |
|
| 239 |
+
return recent_timestamps, user_agents, order_numbers
|
| 240 |
|
| 241 |
+
def _check_suspicious_activity(ip_address: str, order_number: str, user_agent: str = "unknown") -> list:
|
| 242 |
"""Check for suspicious patterns and return list of flags"""
|
| 243 |
flags = []
|
| 244 |
now_dt = datetime.now(timezone.utc)
|
| 245 |
|
| 246 |
# Get recent requests from the persistent log
|
| 247 |
+
recent_request_ages, user_agents_from_ip, order_numbers_from_ip = _get_recent_requests_from_log(ip_address)
|
| 248 |
|
| 249 |
# Also check the buffer for requests not yet written to the log
|
| 250 |
for entry in _request_log_buffer:
|
|
|
|
| 254 |
age_seconds = (now_dt - timestamp).total_seconds()
|
| 255 |
if age_seconds < 3600:
|
| 256 |
recent_request_ages.append(age_seconds)
|
| 257 |
+
user_agents_from_ip.add(entry.get('user_agent', 'unknown'))
|
| 258 |
+
if entry.get('order_number'):
|
| 259 |
+
order_numbers_from_ip.add(entry['order_number'])
|
| 260 |
except (ValueError, KeyError):
|
| 261 |
continue
|
| 262 |
|
|
|
|
| 267 |
if total_requests > 20:
|
| 268 |
flags.append("HIGH_FREQUENCY")
|
| 269 |
|
| 270 |
+
# Flag: More than 3 requests in the last minute
|
| 271 |
last_minute = sum(1 for age in recent_request_ages if age < 60) + 1
|
| 272 |
+
if last_minute > 3:
|
| 273 |
flags.append("RAPID_REQUESTS")
|
| 274 |
|
| 275 |
+
# Flag: More than 5 requests in the last 5 minutes
|
| 276 |
last_5_min = sum(1 for age in recent_request_ages if age < 300) + 1
|
| 277 |
+
if last_5_min > 5:
|
| 278 |
flags.append("BURST_PATTERN")
|
| 279 |
|
| 280 |
+
# Flag: Suspicious user agent patterns (low false positive risk)
|
| 281 |
+
ua_lower = user_agent.lower()
|
| 282 |
+
suspicious_ua_patterns = [
|
| 283 |
+
'bot', 'crawler', 'spider', 'scraper', 'curl', 'wget',
|
| 284 |
+
'python-requests', 'python-urllib', 'go-http-client',
|
| 285 |
+
'java/', 'okhttp', 'axios', 'node-fetch'
|
| 286 |
+
]
|
| 287 |
+
if any(pattern in ua_lower for pattern in suspicious_ua_patterns):
|
| 288 |
+
flags.append("SUSPICIOUS_USER_AGENT")
|
| 289 |
+
|
| 290 |
+
# Flag: Multiple different user agents from same IP (enumeration attempt)
|
| 291 |
+
# Only flag if we have enough data to be confident
|
| 292 |
+
if len(user_agents_from_ip) >= 3 and user_agent not in user_agents_from_ip:
|
| 293 |
+
flags.append("MULTIPLE_USER_AGENTS")
|
| 294 |
+
|
| 295 |
+
# Flag: Trying many different order numbers from same IP
|
| 296 |
+
if order_number and len(order_numbers_from_ip) >= 5 and order_number not in order_numbers_from_ip:
|
| 297 |
+
flags.append("ORDER_ENUMERATION")
|
| 298 |
+
|
| 299 |
return flags
|
| 300 |
|
| 301 |
# -------------------------
|
|
|
|
| 320 |
order_number = order_number.strip()
|
| 321 |
|
| 322 |
# Check for suspicious activity patterns
|
| 323 |
+
suspicious_flags = _check_suspicious_activity(ip_address, order_number, user_agent)
|
| 324 |
+
|
| 325 |
+
# Apply exponential delay for suspicious requests (anti-abuse measure)
|
| 326 |
+
# This significantly slows down automated attacks while having minimal impact on legitimate users
|
| 327 |
+
# Delay formula: 2^(number_of_flags) seconds, capped at 30 seconds
|
| 328 |
+
# Examples: 1 flag = 2s, 2 flags = 4s, 3 flags = 8s, 4 flags = 16s, 5+ flags = 30s
|
| 329 |
+
if suspicious_flags:
|
| 330 |
+
delay_seconds = 2 ** len(suspicious_flags)
|
| 331 |
+
delay_seconds = min(delay_seconds, 30) # Cap at 30 seconds
|
| 332 |
+
time.sleep(delay_seconds)
|
| 333 |
|
| 334 |
if not order_number:
|
| 335 |
_log_request(order_number, ip_address, user_agent, False, "Empty order number", suspicious_flags)
|
|
|
|
| 457 |
demo.queue()
|
| 458 |
demo.launch()
|
| 459 |
|
| 460 |
+
|