Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

193 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Agent for implementing API abuse detection with rate limiting analysis."""
import json
import argparse
from datetime import datetime
from collections import defaultdict, Counter
def load_access_logs(log_path):
"""Load API access logs from JSON lines."""
entries = []
with open(log_path) as f:
for line in f:
try:
entries.append(json.loads(line))
except json.JSONDecodeError:
continue
return entries
def detect_brute_force(logs, failure_threshold=10, window_minutes=5):
"""Detect brute force attacks by counting auth failures per IP."""
ip_failures = defaultdict(list)
for entry in logs:
status = entry.get("status_code", entry.get("status", 0))
if int(status) in (401, 403):
ip = entry.get("client_ip", entry.get("ip", ""))
ts = entry.get("timestamp", "")
ip_failures[ip].append(ts)
findings = []
for ip, timestamps in ip_failures.items():
if len(timestamps) >= failure_threshold:
findings.append({
"client_ip": ip,
"auth_failures": len(timestamps),
"severity": "CRITICAL" if len(timestamps) > 50 else "HIGH",
"category": "brute_force",
"first_seen": timestamps[0],
"last_seen": timestamps[-1],
})
return sorted(findings, key=lambda x: x["auth_failures"], reverse=True)
def detect_api_scraping(logs, threshold=500):
"""Detect API scraping by high request volume per IP."""
ip_counts = Counter()
ip_endpoints = defaultdict(set)
for entry in logs:
ip = entry.get("client_ip", entry.get("ip", ""))
endpoint = entry.get("path", entry.get("endpoint", ""))
ip_counts[ip] += 1
ip_endpoints[ip].add(endpoint)
findings = []
for ip, count in ip_counts.items():
if count >= threshold:
findings.append({
"client_ip": ip,
"total_requests": count,
"unique_endpoints": len(ip_endpoints[ip]),
"severity": "HIGH",
"category": "api_scraping",
})
return sorted(findings, key=lambda x: x["total_requests"], reverse=True)
def detect_credential_stuffing(logs, threshold=20):
"""Detect credential stuffing: many unique usernames from single IP."""
ip_users = defaultdict(set)
for entry in logs:
if entry.get("path", "").endswith(("/login", "/auth", "/signin")):
ip = entry.get("client_ip", entry.get("ip", ""))
user = entry.get("username", entry.get("user", ""))
if user:
ip_users[ip].add(user)
findings = []
for ip, users in ip_users.items():
if len(users) >= threshold:
findings.append({
"client_ip": ip,
"unique_usernames": len(users),
"severity": "CRITICAL",
"category": "credential_stuffing",
})
return sorted(findings, key=lambda x: x["unique_usernames"], reverse=True)
def detect_rate_limit_bypass(logs):
"""Detect attempts to bypass rate limiting."""
findings = []
ip_ua_combos = defaultdict(set)
for entry in logs:
ip = entry.get("client_ip", entry.get("ip", ""))
ua = entry.get("user_agent", "")
ip_ua_combos[ip].add(ua)
for ip, agents in ip_ua_combos.items():
if len(agents) >= 10:
findings.append({
"client_ip": ip,
"unique_user_agents": len(agents),
"severity": "HIGH",
"category": "ua_rotation",
"reason": "Rotating User-Agent to bypass rate limits",
})
ip_429_count = Counter()
for entry in logs:
if int(entry.get("status_code", entry.get("status", 0))) == 429:
ip = entry.get("client_ip", entry.get("ip", ""))
ip_429_count[ip] += 1
for ip, count in ip_429_count.items():
if count >= 50:
findings.append({
"client_ip": ip,
"rate_limit_hits": count,
"severity": "MEDIUM",
"category": "rate_limit_persistence",
"reason": "Continuing requests after rate limiting",
})
return findings
def generate_rate_limit_config(logs):
"""Generate recommended rate limit configuration based on traffic patterns."""
endpoint_counts = Counter()
for entry in logs:
path = entry.get("path", entry.get("endpoint", ""))
endpoint_counts[path] += 1
auth_endpoints = [p for p in endpoint_counts if any(
k in p for k in ["login", "auth", "signin", "register", "password"])]
config = {
"global": {"requests_per_minute": 100, "burst": 20},
"auth_endpoints": {
"endpoints": auth_endpoints,
"requests_per_minute": 10,
"burst": 3,
"block_duration_seconds": 300,
},
"sensitive_endpoints": {
"endpoints": ["/api/admin", "/api/users", "/api/export"],
"requests_per_minute": 30,
"burst": 5,
},
}
return config
def main():
parser = argparse.ArgumentParser(description="API Abuse Detection Agent")
parser.add_argument("--log", required=True, help="API access log (JSON lines)")
parser.add_argument("--output", default="api_abuse_report.json")
parser.add_argument("--action", choices=[
"brute_force", "scraping", "stuffing", "bypass", "config", "full_analysis"
], default="full_analysis")
args = parser.parse_args()
logs = load_access_logs(args.log)
report = {"generated_at": datetime.utcnow().isoformat(), "total_requests": len(logs),
"findings": {}}
print(f"[+] Loaded {len(logs)} API requests")
if args.action in ("brute_force", "full_analysis"):
f = detect_brute_force(logs)
report["findings"]["brute_force"] = f
print(f"[+] Brute force sources: {len(f)}")
if args.action in ("scraping", "full_analysis"):
f = detect_api_scraping(logs)
report["findings"]["api_scraping"] = f
print(f"[+] Scraping sources: {len(f)}")
if args.action in ("stuffing", "full_analysis"):
f = detect_credential_stuffing(logs)
report["findings"]["credential_stuffing"] = f
print(f"[+] Credential stuffing sources: {len(f)}")
if args.action in ("bypass", "full_analysis"):
f = detect_rate_limit_bypass(logs)
report["findings"]["bypass_attempts"] = f
print(f"[+] Rate limit bypass attempts: {len(f)}")
if args.action in ("config", "full_analysis"):
config = generate_rate_limit_config(logs)
report["findings"]["recommended_config"] = config
print("[+] Rate limit config generated")
with open(args.output, "w") as fout:
json.dump(report, fout, indent=2, default=str)
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()