mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-26 11:44:37 +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
137 lines
5.7 KiB
Python
137 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Privileged Account Access Review agent — audits privileged accounts for
|
|
compliance with least-privilege and periodic recertification requirements."""
|
|
|
|
import argparse
|
|
import csv
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
|
|
def load_accounts(csv_path: str) -> list[dict]:
|
|
"""Load privileged account inventory from CSV."""
|
|
with open(csv_path, newline="", encoding="utf-8") as fh:
|
|
reader = csv.DictReader(fh)
|
|
return list(reader)
|
|
|
|
|
|
def check_stale_accounts(accounts: list[dict], max_days: int = 90) -> list[dict]:
|
|
"""Flag accounts not used within max_days."""
|
|
findings = []
|
|
cutoff = datetime.utcnow() - timedelta(days=max_days)
|
|
for acct in accounts:
|
|
last_used = acct.get("last_used", "")
|
|
if not last_used:
|
|
findings.append({"account": acct.get("username", ""), "issue": "no_last_used_date",
|
|
"severity": "high", "detail": "Account has no recorded last-used date"})
|
|
continue
|
|
try:
|
|
used_dt = datetime.strptime(last_used, "%Y-%m-%d")
|
|
if used_dt < cutoff:
|
|
findings.append({"account": acct.get("username", ""),
|
|
"issue": "stale_account", "severity": "high",
|
|
"detail": f"Last used {last_used}, exceeds {max_days}-day threshold"})
|
|
except ValueError:
|
|
findings.append({"account": acct.get("username", ""), "issue": "invalid_date",
|
|
"severity": "medium", "detail": f"Cannot parse last_used: {last_used}"})
|
|
return findings
|
|
|
|
|
|
def check_shared_accounts(accounts: list[dict]) -> list[dict]:
|
|
"""Detect shared/generic privileged accounts."""
|
|
shared_patterns = ["admin", "root", "service", "svc_", "shared", "generic", "temp"]
|
|
findings = []
|
|
for acct in accounts:
|
|
uname = acct.get("username", "").lower()
|
|
owner = acct.get("owner", "").strip()
|
|
for pat in shared_patterns:
|
|
if pat in uname and not owner:
|
|
findings.append({"account": acct.get("username", ""),
|
|
"issue": "shared_account_no_owner", "severity": "critical",
|
|
"detail": f"Appears shared (matches '{pat}') with no assigned owner"})
|
|
break
|
|
return findings
|
|
|
|
|
|
def check_excessive_privileges(accounts: list[dict]) -> list[dict]:
|
|
"""Flag accounts with overly broad privilege sets."""
|
|
high_risk_roles = {"domain admin", "enterprise admin", "schema admin",
|
|
"global admin", "super admin", "root"}
|
|
findings = []
|
|
for acct in accounts:
|
|
roles = {r.strip().lower() for r in acct.get("roles", "").split(";")}
|
|
overlap = roles & high_risk_roles
|
|
if overlap:
|
|
findings.append({"account": acct.get("username", ""),
|
|
"issue": "excessive_privilege", "severity": "critical",
|
|
"detail": f"Holds high-risk roles: {', '.join(sorted(overlap))}"})
|
|
return findings
|
|
|
|
|
|
def check_recertification(accounts: list[dict], cert_interval_days: int = 180) -> list[dict]:
|
|
"""Flag accounts overdue for recertification."""
|
|
cutoff = datetime.utcnow() - timedelta(days=cert_interval_days)
|
|
findings = []
|
|
for acct in accounts:
|
|
cert_date = acct.get("last_certified", "")
|
|
if not cert_date:
|
|
findings.append({"account": acct.get("username", ""),
|
|
"issue": "never_certified", "severity": "critical",
|
|
"detail": "Account has never been certified"})
|
|
continue
|
|
try:
|
|
cert_dt = datetime.strptime(cert_date, "%Y-%m-%d")
|
|
if cert_dt < cutoff:
|
|
findings.append({"account": acct.get("username", ""),
|
|
"issue": "overdue_recertification", "severity": "high",
|
|
"detail": f"Last certified {cert_date}, exceeds {cert_interval_days}-day cycle"})
|
|
except ValueError:
|
|
pass
|
|
return findings
|
|
|
|
|
|
def generate_report(accounts: list[dict], stale_days: int, cert_days: int) -> dict:
|
|
"""Run all checks and produce a consolidated JSON report."""
|
|
findings = []
|
|
findings.extend(check_stale_accounts(accounts, stale_days))
|
|
findings.extend(check_shared_accounts(accounts))
|
|
findings.extend(check_excessive_privileges(accounts))
|
|
findings.extend(check_recertification(accounts, cert_days))
|
|
|
|
severity_counts = {}
|
|
for f in findings:
|
|
severity_counts[f["severity"]] = severity_counts.get(f["severity"], 0) + 1
|
|
|
|
return {
|
|
"report": "privileged_account_access_review",
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
"total_accounts": len(accounts),
|
|
"total_findings": len(findings),
|
|
"severity_summary": severity_counts,
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Privileged Account Access Review Agent")
|
|
parser.add_argument("--input", required=True, help="CSV file with privileged account inventory")
|
|
parser.add_argument("--stale-days", type=int, default=90, help="Max days of inactivity (default: 90)")
|
|
parser.add_argument("--cert-days", type=int, default=180, help="Recertification interval in days (default: 180)")
|
|
parser.add_argument("--output", help="Output JSON file path")
|
|
args = parser.parse_args()
|
|
|
|
accounts = load_accounts(args.input)
|
|
report = generate_report(accounts, args.stale_days, args.cert_days)
|
|
|
|
output = json.dumps(report, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(output, encoding="utf-8")
|
|
print(f"Report written to {args.output}")
|
|
else:
|
|
print(output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|