#!/usr/bin/env python3 """Agent for API Security Posture Management - discovery, classification, and risk scoring.""" import json import argparse import re from datetime import datetime from collections import Counter, defaultdict def discover_apis_from_traffic(log_path): """Discover APIs from network traffic logs.""" apis = defaultdict(lambda: {"methods": set(), "consumers": set(), "count": 0, "status_codes": Counter()}) with open(log_path) as f: for line in f: try: entry = json.loads(line) except json.JSONDecodeError: continue path = entry.get("path", entry.get("endpoint", "")) method = entry.get("method", entry.get("http_method", "")) ip = entry.get("client_ip", "") status = entry.get("status_code", entry.get("status", 200)) normalized = re.sub(r"/[0-9a-f-]{8,}", "/{id}", path) normalized = re.sub(r"/\d+", "/{id}", normalized) apis[normalized]["methods"].add(method) apis[normalized]["consumers"].add(ip) apis[normalized]["count"] += 1 apis[normalized]["status_codes"][int(status)] += 1 result = [] for path, info in sorted(apis.items(), key=lambda x: -x[1]["count"]): result.append({ "path": path, "methods": sorted(info["methods"]), "unique_consumers": len(info["consumers"]), "total_requests": info["count"], "error_rate": round( sum(v for k, v in info["status_codes"].items() if k >= 400) / info["count"], 3), }) return result def classify_api_sensitivity(apis): """Classify APIs by data sensitivity level.""" sensitive_patterns = { "PII": [r"/users?(/|$)", r"/customers?(/|$)", r"/profile", r"/account"], "Financial": [r"/payments?", r"/billing", r"/invoices?", r"/transactions?"], "Auth": [r"/auth", r"/login", r"/token", r"/oauth", r"/password"], "Admin": [r"/admin", r"/management", r"/config", r"/settings"], "Health": [r"/health", r"/status", r"/metrics", r"/ping"], } classified = [] for api in apis: path = api["path"] categories = [] for category, patterns in sensitive_patterns.items(): if any(re.search(p, path, re.IGNORECASE) for p in patterns): categories.append(category) sensitivity = "HIGH" if any(c in categories for c in ["PII", "Financial", "Auth", "Admin"]) \ else "LOW" if "Health" in categories else "MEDIUM" classified.append({**api, "categories": categories, "sensitivity": sensitivity}) return classified def score_api_risk(apis): """Calculate risk score for each API endpoint.""" scored = [] for api in apis: risk_score = 0 factors = [] if api.get("sensitivity") == "HIGH": risk_score += 30 factors.append("high_sensitivity_data") if api.get("error_rate", 0) > 0.1: risk_score += 20 factors.append("high_error_rate") if api.get("unique_consumers", 0) > 100: risk_score += 10 factors.append("high_consumer_count") if "Auth" in api.get("categories", []): risk_score += 15 factors.append("authentication_endpoint") methods = api.get("methods", []) if any(m in methods for m in ["DELETE", "PUT", "PATCH"]): risk_score += 10 factors.append("state_changing_methods") severity = "CRITICAL" if risk_score >= 50 else "HIGH" if risk_score >= 30 else "MEDIUM" scored.append({**api, "risk_score": risk_score, "risk_factors": factors, "risk_level": severity}) return sorted(scored, key=lambda x: x["risk_score"], reverse=True) def check_api_security_controls(apis, spec_path=None): """Check security controls for discovered APIs.""" findings = [] spec_paths = set() if spec_path: try: import yaml with open(spec_path) as f: spec = yaml.safe_load(f) if spec_path.endswith((".yaml", ".yml")) else json.load(f) spec_paths = set(spec.get("paths", {}).keys()) except Exception: pass for api in apis: if spec_paths and api["path"] not in spec_paths: findings.append({ "path": api["path"], "issue": "undocumented_api", "severity": "HIGH", "recommendation": "Add to OpenAPI spec or deprecate", }) if api.get("sensitivity") == "HIGH" and api.get("error_rate", 0) > 0.05: findings.append({ "path": api["path"], "issue": "sensitive_endpoint_high_errors", "severity": "HIGH", }) return findings def main(): parser = argparse.ArgumentParser(description="API Security Posture Management Agent") parser.add_argument("--log", help="API traffic log (JSON lines)") parser.add_argument("--spec", help="OpenAPI spec for comparison") parser.add_argument("--output", default="api_posture_report.json") parser.add_argument("--action", choices=["discover", "classify", "score", "audit", "full"], default="full") args = parser.parse_args() report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} if args.log: apis = discover_apis_from_traffic(args.log) report["findings"]["discovered_apis"] = len(apis) print(f"[+] Discovered {len(apis)} API endpoints") if args.action in ("classify", "full"): apis = classify_api_sensitivity(apis) if args.action in ("score", "full"): apis = score_api_risk(apis) report["findings"]["api_inventory"] = apis[:200] if args.action in ("audit", "full"): f = check_api_security_controls(apis, args.spec) report["findings"]["security_gaps"] = f print(f"[+] Security gaps: {len(f)}") 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()