Files
T
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

147 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""NERC CIP Compliance Agent - audits critical infrastructure against NERC CIP standards."""
import json
import argparse
import logging
import subprocess
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
NERC_CIP_CONTROLS = {
"CIP-002": {"name": "BES Cyber System Categorization", "checks": [
"asset_inventory_complete", "impact_ratings_assigned", "annual_review_documented"
]},
"CIP-003": {"name": "Security Management Controls", "checks": [
"cyber_security_policy_exists", "senior_manager_designated", "delegation_documented"
]},
"CIP-004": {"name": "Personnel and Training", "checks": [
"security_awareness_training", "role_based_training", "personnel_risk_assessment",
"access_revocation_process"
]},
"CIP-005": {"name": "Electronic Security Perimeters", "checks": [
"esp_defined", "eap_controls_configured", "remote_access_encrypted",
"interactive_remote_access_mfa"
]},
"CIP-006": {"name": "Physical Security", "checks": [
"physical_security_plan", "visitor_control_program", "psp_access_monitoring"
]},
"CIP-007": {"name": "System Security Management", "checks": [
"ports_services_minimized", "patch_management_process", "malware_prevention",
"security_event_monitoring", "system_access_controls"
]},
"CIP-008": {"name": "Incident Reporting and Response", "checks": [
"incident_response_plan", "incident_response_testing", "reporting_procedures"
]},
"CIP-009": {"name": "Recovery Plans", "checks": [
"recovery_plans_exist", "backup_procedures", "recovery_testing_annual"
]},
"CIP-010": {"name": "Configuration Change Management", "checks": [
"baseline_configurations", "change_management_process", "vulnerability_assessments"
]},
"CIP-011": {"name": "Information Protection", "checks": [
"information_classification", "bcsi_protection", "bcsi_disposal"
]},
"CIP-013": {"name": "Supply Chain Risk Management", "checks": [
"supply_chain_plan", "vendor_risk_assessment", "vendor_notification_process"
]},
}
def check_esp_controls(target_ip=None):
"""Check Electronic Security Perimeter controls via network scan."""
findings = []
if target_ip:
cmd = ["nmap", "-sS", "-p", "1-1024", "--open", target_ip, "-oX", "-"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
open_ports = result.stdout.count("state=\"open\"")
if open_ports > 10:
findings.append({"control": "CIP-005", "issue": f"{open_ports} open ports on ESP boundary",
"severity": "high"})
return findings
def check_system_hardening():
"""Check CIP-007 system hardening controls."""
findings = []
svc_cmd = ["systemctl", "list-units", "--type=service", "--state=running", "--no-pager"]
result = subprocess.run(svc_cmd, capture_output=True, text=True, timeout=120)
service_count = len([l for l in result.stdout.split("\n") if ".service" in l])
if service_count > 50:
findings.append({"control": "CIP-007-R1", "issue": f"{service_count} running services (minimize unused)",
"severity": "medium"})
patch_cmd = ["apt", "list", "--upgradable"] if subprocess.run(["which", "apt"], capture_output=True, timeout=120).returncode == 0 else ["yum", "check-update"]
result = subprocess.run(patch_cmd, capture_output=True, text=True, timeout=120)
pending = len([l for l in result.stdout.split("\n") if l.strip() and not l.startswith("Listing")])
if pending > 0:
findings.append({"control": "CIP-007-R2", "issue": f"{pending} pending security patches",
"severity": "high"})
return findings
def audit_compliance_status(evidence_file=None):
"""Audit compliance status against all NERC CIP controls."""
evidence = {}
if evidence_file:
with open(evidence_file) as f:
evidence = json.load(f)
results = {}
for cip_id, control in NERC_CIP_CONTROLS.items():
check_results = {}
for check in control["checks"]:
status = evidence.get(cip_id, {}).get(check, "not_assessed")
check_results[check] = status
passed = sum(1 for v in check_results.values() if v == "pass")
total = len(check_results)
results[cip_id] = {
"name": control["name"],
"checks": check_results,
"passed": passed,
"total": total,
"compliance_rate": round(passed / max(total, 1) * 100, 1),
}
return results
def generate_report(compliance, esp_findings, hardening_findings):
total_checks = sum(r["total"] for r in compliance.values())
total_passed = sum(r["passed"] for r in compliance.values())
all_findings = esp_findings + hardening_findings
report = {
"timestamp": datetime.utcnow().isoformat(),
"framework": "NERC CIP v6/v7",
"overall_compliance_rate": round(total_passed / max(total_checks, 1) * 100, 1),
"total_controls_assessed": total_checks,
"total_passed": total_passed,
"cip_standard_results": compliance,
"technical_findings": all_findings,
"high_severity_findings": len([f for f in all_findings if f.get("severity") == "high"]),
}
return report
def main():
parser = argparse.ArgumentParser(description="NERC CIP Compliance Audit Agent")
parser.add_argument("--evidence-file", help="JSON evidence file with control assessment results")
parser.add_argument("--target-ip", help="ESP boundary IP to scan")
parser.add_argument("--skip-hardening", action="store_true", help="Skip system hardening checks")
parser.add_argument("--output", default="nerc_cip_report.json")
args = parser.parse_args()
compliance = audit_compliance_status(args.evidence_file)
esp_findings = check_esp_controls(args.target_ip) if args.target_ip else []
hardening = check_system_hardening() if not args.skip_hardening else []
report = generate_report(compliance, esp_findings, hardening)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("NERC CIP compliance: %.1f%% (%d/%d), %d findings",
report["overall_compliance_rate"], report["total_passed"],
report["total_controls_assessed"], len(report["technical_findings"]))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()