mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
c47eed6a64
- 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
132 lines
5.4 KiB
Python
132 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for conducting access review and certification using identity governance APIs."""
|
|
|
|
import json
|
|
import argparse
|
|
import csv
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
|
|
def load_access_data(csv_path):
|
|
"""Load access entitlement data from CSV export."""
|
|
with open(csv_path) as f:
|
|
reader = csv.DictReader(f)
|
|
data = list(reader)
|
|
print(f"[*] Loaded {len(data)} entitlement records from {csv_path}")
|
|
return data
|
|
|
|
|
|
def identify_orphaned_accounts(records):
|
|
"""Find accounts with no manager or terminated status."""
|
|
findings = []
|
|
for r in records:
|
|
if not r.get("manager") or r.get("status", "").lower() == "terminated":
|
|
findings.append({"user": r.get("username"), "status": r.get("status"),
|
|
"manager": r.get("manager", "NONE"), "severity": "HIGH",
|
|
"issue": "Orphaned/terminated account with active access"})
|
|
print(f"\n[*] Orphaned/terminated accounts: {len(findings)}")
|
|
for f in findings[:10]:
|
|
print(f" [!] {f['user']} (status={f['status']}, manager={f['manager']})")
|
|
return findings
|
|
|
|
|
|
def check_sod_violations(records, sod_rules):
|
|
"""Check for separation of duties violations."""
|
|
user_entitlements = {}
|
|
for r in records:
|
|
user = r.get("username", "")
|
|
ent = r.get("entitlement", "")
|
|
user_entitlements.setdefault(user, set()).add(ent)
|
|
findings = []
|
|
for user, ents in user_entitlements.items():
|
|
for rule in sod_rules:
|
|
if rule["role_a"] in ents and rule["role_b"] in ents:
|
|
findings.append({"user": user, "conflict": f"{rule['role_a']} + {rule['role_b']}",
|
|
"severity": "CRITICAL", "rule": rule.get("name", "")})
|
|
print(f"\n[*] SoD violations: {len(findings)}")
|
|
for f in findings[:10]:
|
|
print(f" [!] {f['user']}: {f['conflict']}")
|
|
return findings
|
|
|
|
|
|
def identify_excessive_access(records, threshold=10):
|
|
"""Find users with entitlement counts above threshold."""
|
|
user_counts = {}
|
|
for r in records:
|
|
user = r.get("username", "")
|
|
user_counts[user] = user_counts.get(user, 0) + 1
|
|
excessive = [{"user": u, "count": c, "severity": "MEDIUM"}
|
|
for u, c in user_counts.items() if c > threshold]
|
|
excessive.sort(key=lambda x: -x["count"])
|
|
print(f"\n[*] Users with >{threshold} entitlements: {len(excessive)}")
|
|
for e in excessive[:10]:
|
|
print(f" [!] {e['user']}: {e['count']} entitlements")
|
|
return excessive
|
|
|
|
|
|
def check_last_used(records, stale_days=90):
|
|
"""Find entitlements not used within the stale period."""
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=stale_days)
|
|
stale = []
|
|
for r in records:
|
|
last_used = r.get("last_used", "")
|
|
if last_used:
|
|
try:
|
|
lu_dt = datetime.fromisoformat(last_used.replace("Z", "+00:00"))
|
|
if lu_dt < cutoff:
|
|
stale.append({"user": r.get("username"), "entitlement": r.get("entitlement"),
|
|
"last_used": last_used, "severity": "MEDIUM"})
|
|
except ValueError:
|
|
pass
|
|
print(f"\n[*] Stale entitlements (>{stale_days} days unused): {len(stale)}")
|
|
return stale
|
|
|
|
|
|
def generate_report(orphaned, sod, excessive, stale, output_path):
|
|
"""Generate access review report."""
|
|
report = {"review_date": datetime.now(timezone.utc).isoformat(),
|
|
"summary": {"orphaned_accounts": len(orphaned), "sod_violations": len(sod),
|
|
"excessive_access": len(excessive), "stale_entitlements": len(stale)},
|
|
"orphaned": orphaned, "sod_violations": sod,
|
|
"excessive_access": excessive[:50], "stale_entitlements": stale[:50]}
|
|
with open(output_path, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
total = len(orphaned) + len(sod) + len(excessive) + len(stale)
|
|
print(f"\n[*] Report saved to {output_path} | Total findings: {total}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Access Review and Certification Agent")
|
|
parser.add_argument("action", choices=["orphaned", "sod", "excessive", "stale", "full-review"])
|
|
parser.add_argument("--data", required=True, help="CSV file with access entitlements")
|
|
parser.add_argument("--sod-rules", help="JSON file with SoD rules")
|
|
parser.add_argument("--threshold", type=int, default=10, help="Excessive access threshold")
|
|
parser.add_argument("--stale-days", type=int, default=90)
|
|
parser.add_argument("-o", "--output", default="access_review.json")
|
|
args = parser.parse_args()
|
|
|
|
records = load_access_data(args.data)
|
|
sod_rules = []
|
|
if args.sod_rules:
|
|
with open(args.sod_rules) as f:
|
|
sod_rules = json.load(f)
|
|
|
|
if args.action == "orphaned":
|
|
identify_orphaned_accounts(records)
|
|
elif args.action == "sod":
|
|
check_sod_violations(records, sod_rules)
|
|
elif args.action == "excessive":
|
|
identify_excessive_access(records, args.threshold)
|
|
elif args.action == "stale":
|
|
check_last_used(records, args.stale_days)
|
|
elif args.action == "full-review":
|
|
o = identify_orphaned_accounts(records)
|
|
s = check_sod_violations(records, sod_rules)
|
|
e = identify_excessive_access(records, args.threshold)
|
|
st = check_last_used(records, args.stale_days)
|
|
generate_report(o, s, e, st, args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|