#!/usr/bin/env python3 """Agent for implementing and auditing API key security controls.""" import json import argparse import hashlib import secrets import re from datetime import datetime def generate_api_key(prefix="sk", length=32): """Generate a secure API key with prefix and checksum.""" random_part = secrets.token_hex(length) checksum = hashlib.sha256(random_part.encode()).hexdigest()[:6] key = f"{prefix}_{random_part}_{checksum}" key_hash = hashlib.sha256(key.encode()).hexdigest() return {"api_key": key, "key_hash": key_hash, "prefix": prefix, "entropy_bits": length * 8} def hash_api_key(api_key): """Hash an API key for secure storage using SHA-256.""" return hashlib.sha256(api_key.encode()).hexdigest() def scan_for_leaked_keys(file_path, patterns=None): """Scan files for leaked API key patterns.""" if patterns is None: patterns = [ (r"sk_live_[a-zA-Z0-9]{24,}", "Stripe live key"), (r"AKIA[0-9A-Z]{16}", "AWS access key"), (r"AIza[0-9A-Za-z_-]{35}", "Google API key"), (r"ghp_[a-zA-Z0-9]{36}", "GitHub PAT"), (r"xox[baprs]-[a-zA-Z0-9-]+", "Slack token"), (r"sk-[a-zA-Z0-9]{48}", "OpenAI key"), (r"[a-f0-9]{32}", "Generic hex key (32 char)"), ] findings = [] with open(file_path) as f: for i, line in enumerate(f, 1): for pattern, desc in patterns: matches = re.findall(pattern, line) for match in matches: findings.append({ "file": str(file_path), "line": i, "pattern": desc, "key_preview": match[:8] + "...", "severity": "CRITICAL", }) return findings def audit_key_rotation(key_inventory_path): """Audit API key age and rotation compliance.""" with open(key_inventory_path) as f: keys = json.load(f) findings = [] now = datetime.utcnow() for key in keys: created = datetime.fromisoformat(key.get("created_at", now.isoformat())) age_days = (now - created).days last_used = key.get("last_used_at") scopes = key.get("scopes", []) if age_days > 90: findings.append({ "key_id": key.get("id", ""), "owner": key.get("owner", ""), "age_days": age_days, "issue": "key_age_exceeds_90_days", "severity": "HIGH", }) if last_used: unused_days = (now - datetime.fromisoformat(last_used)).days if unused_days > 30: findings.append({ "key_id": key.get("id", ""), "owner": key.get("owner", ""), "unused_days": unused_days, "issue": "inactive_key", "severity": "MEDIUM", }) if not scopes or "*" in scopes: findings.append({ "key_id": key.get("id", ""), "owner": key.get("owner", ""), "scopes": scopes, "issue": "overly_broad_scope", "severity": "HIGH", }) return sorted(findings, key=lambda x: x.get("age_days", 0), reverse=True) def analyze_key_usage(usage_log_path): """Analyze API key usage patterns for anomalies.""" entries = [] with open(usage_log_path) as f: for line in f: try: entries.append(json.loads(line)) except json.JSONDecodeError: continue from collections import Counter, defaultdict key_ips = defaultdict(set) key_errors = Counter() for entry in entries: key_id = entry.get("api_key_id", "") ip = entry.get("client_ip", "") status = int(entry.get("status_code", 200)) key_ips[key_id].add(ip) if status >= 400: key_errors[key_id] += 1 findings = [] for key_id, ips in key_ips.items(): if len(ips) > 10: findings.append({ "key_id": key_id, "unique_ips": len(ips), "issue": "key_shared_across_many_ips", "severity": "HIGH", }) for key_id, errors in key_errors.most_common(10): if errors > 100: findings.append({ "key_id": key_id, "error_count": errors, "issue": "high_error_rate", "severity": "MEDIUM", }) return findings def main(): parser = argparse.ArgumentParser(description="API Key Security Controls Agent") parser.add_argument("--action", choices=[ "generate", "scan", "audit_rotation", "analyze_usage", "full" ], default="full") parser.add_argument("--file", help="File to scan for leaked keys") parser.add_argument("--inventory", help="Key inventory JSON") parser.add_argument("--usage-log", help="Key usage log (JSON lines)") parser.add_argument("--output", default="api_key_audit_report.json") args = parser.parse_args() report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}} if args.action == "generate": key = generate_api_key() report["generated_key"] = key print(f"[+] Generated key: {key['api_key'][:20]}...") if args.action in ("scan", "full") and args.file: f = scan_for_leaked_keys(args.file) report["findings"]["leaked_keys"] = f print(f"[+] Leaked keys found: {len(f)}") if args.action in ("audit_rotation", "full") and args.inventory: f = audit_key_rotation(args.inventory) report["findings"]["rotation_audit"] = f print(f"[+] Rotation issues: {len(f)}") if args.action in ("analyze_usage", "full") and args.usage_log: f = analyze_key_usage(args.usage_log) report["findings"]["usage_anomalies"] = f print(f"[+] Usage anomalies: {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()