Files
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

93 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Nozomi Networks OT Traffic Analysis Agent - monitors ICS protocols and detects anomalies."""
import json
import argparse
import logging
import subprocess
from collections import defaultdict
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def nozomi_api(base_url, token, endpoint):
cmd = ["curl", "-s", "-k", "-H", f"Authorization: Bearer {token}", f"{base_url}/api/v1{endpoint}"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return json.loads(result.stdout) if result.stdout else {}
def get_alerts(base_url, token):
return nozomi_api(base_url, token, "/alerts?limit=500")
def get_assets(base_url, token):
return nozomi_api(base_url, token, "/assets?limit=500")
def get_sessions(base_url, token):
return nozomi_api(base_url, token, "/sessions?limit=500")
def analyze_ot_protocols(sessions):
protocol_counts = defaultdict(int)
ot_protocols = {"modbus", "s7comm", "dnp3", "opcua", "ethernet/ip", "bacnet", "profinet"}
ot_sessions = []
for session in sessions:
proto = session.get("protocol", "").lower()
protocol_counts[proto] += 1
if proto in ot_protocols:
ot_sessions.append({"source": session.get("source_ip", ""), "destination": session.get("destination_ip", ""),
"protocol": proto, "bytes": session.get("bytes_total", 0)})
return {"protocol_distribution": dict(protocol_counts), "ot_sessions": len(ot_sessions), "total_sessions": len(sessions)}
def detect_anomalies(alerts):
anomaly_types = defaultdict(list)
for alert in alerts:
anomaly_types[alert.get("type_id", "unknown")].append({
"description": alert.get("description", ""), "risk": alert.get("risk", ""),
"source": alert.get("source_ip", ""), "timestamp": alert.get("created_at", ""),
})
return {cat: {"count": len(items), "samples": items[:3]} for cat, items in anomaly_types.items()}
def audit_asset_inventory(assets):
by_type = defaultdict(int)
by_vendor = defaultdict(int)
for asset in assets:
by_type[asset.get("type", "unknown")] += 1
by_vendor[asset.get("vendor", "unknown")] += 1
return {"total_assets": len(assets), "by_type": dict(by_type),
"by_vendor": dict(sorted(by_vendor.items(), key=lambda x: x[1], reverse=True)[:10])}
def generate_report(alerts, sessions, assets, base_url):
return {
"timestamp": datetime.utcnow().isoformat(), "nozomi_url": base_url,
"alert_summary": {"total": len(alerts), "critical": sum(1 for a in alerts if a.get("risk") == "critical")},
"protocol_analysis": analyze_ot_protocols(sessions),
"anomalies": detect_anomalies(alerts),
"asset_inventory": audit_asset_inventory(assets),
}
def main():
parser = argparse.ArgumentParser(description="Nozomi Networks OT Traffic Analysis Agent")
parser.add_argument("--nozomi-url", required=True, help="Nozomi Guardian URL")
parser.add_argument("--token", required=True, help="API bearer token")
parser.add_argument("--output", default="nozomi_ot_report.json")
args = parser.parse_args()
alerts = get_alerts(args.nozomi_url, args.token)
sessions = get_sessions(args.nozomi_url, args.token)
assets = get_assets(args.nozomi_url, args.token)
report = generate_report(alerts, sessions, assets, args.nozomi_url)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Nozomi: %d alerts, %d sessions, %d assets", len(alerts), len(sessions), len(assets))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()