Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal

- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
This commit is contained in:
mukul975
2026-03-11 00:22:12 +01:00
parent 27c6414ca5
commit c21af3347e
1244 changed files with 61622 additions and 723 deletions
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Anthropic Agent Skills Contributors
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,47 @@
# API Reference: Securing Historian Server in OT Environment
## Common Historian Ports
| Port | Service | Risk if Exposed |
|------|---------|----------------|
| 5450 | PI Data Archive | SDK/API data access |
| 5457 | PI AF Server | Asset Framework access |
| 443 | HTTPS | Web API (acceptable if TLS) |
| 80 | HTTP | Cleartext credentials/data |
| 1433 | MS SQL | Direct database queries |
| 3389 | RDP | Remote admin access |
| 135/445 | RPC/SMB | Lateral movement target |
| 502 | Modbus | Industrial protocol |
## Purdue Model Placement
| Level | Systems | Historian Role |
|-------|---------|---------------|
| 0-1 | Field devices, PLCs | Data source |
| 2 | HMI, SCADA | Data source |
| 3 | Site Operations | OT Historian location |
| 3.5 | DMZ | Replica historian |
| 4-5 | Enterprise | Consumer of DMZ data |
## Authentication Methods
| Method | Security Level | Recommendation |
|--------|---------------|----------------|
| PI Trust (IP-based) | Insecure | Migrate immediately |
| piadmin default | Insecure | Disable account |
| Windows Integrated | Recommended | Use AD groups/PI Mappings |
| Certificate-based | Strong | For inter-server comms |
## Python Libraries
| Library | Version | Purpose |
|---------|---------|---------|
| `socket` | stdlib | Port scanning |
| `json` | stdlib | Report generation |
| `pathlib` | stdlib | File handling |
## References
- OSIsoft PI Security Guide: https://docs.aveva.com/
- IEC 62443: Industrial Automation Security
- NIST SP 800-82: Guide to ICS Security
@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""Agent for securing historian servers in OT environments.
Audits network exposure, authentication configuration, data
integrity protections, and DMZ replication architecture of
process historian servers (OSIsoft PI, AVEVA, Honeywell PHD).
"""
import json
import socket
import sys
from pathlib import Path
from datetime import datetime
HISTORIAN_PORTS = {
5450: ("PI Data Archive", "PI SDK/API connections"),
5457: ("PI AF Server", "PI Asset Framework"),
5459: ("PI Notifications", "Notification Service"),
443: ("HTTPS", "Web API / PI Vision"),
80: ("HTTP", "Unsecured web interface"),
1433: ("MS SQL Server", "Direct database access"),
5432: ("PostgreSQL", "Direct database access"),
3389: ("RDP", "Remote Desktop"),
135: ("RPC", "Windows RPC"),
445: ("SMB", "Windows File Sharing"),
8080: ("HTTP Alt", "Alternative web interface"),
502: ("Modbus", "Industrial protocol"),
}
UNNECESSARY_PORTS = {80, 135, 445, 3389, 8080}
class HistorianSecurityAgent:
"""Audits OT historian server security configuration."""
def __init__(self, historian_ip, historian_type="PI",
output_dir="./historian_audit"):
self.ip = historian_ip
self.hist_type = historian_type
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.findings = []
def check_network_exposure(self):
"""Scan historian for exposed network services."""
exposed = []
for port, (service, desc) in HISTORIAN_PORTS.items():
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
result = sock.connect_ex((self.ip, port))
sock.close()
if result == 0:
exposed.append({
"port": port, "service": service, "description": desc,
"unnecessary": port in UNNECESSARY_PORTS,
})
except (socket.error, OSError):
pass
for svc in exposed:
if svc["unnecessary"]:
self.findings.append({
"severity": "high",
"category": "Network Exposure",
"title": f"Unnecessary service: {svc['service']} (port {svc['port']})",
"remediation": f"Disable {svc['service']} or restrict via firewall",
})
if any(s["port"] == 80 for s in exposed):
self.findings.append({
"severity": "high",
"category": "Encryption",
"title": "HTTP (unencrypted) web interface exposed",
"remediation": "Redirect HTTP to HTTPS and disable port 80",
})
return exposed
def check_authentication(self):
"""Evaluate historian authentication configuration risks."""
checks = [
{
"check": "PI Trust Authentication",
"severity": "critical",
"risk": "IP-based auth without credentials",
"remediation": "Migrate to Windows Integrated Security",
},
{
"check": "Default piadmin account",
"severity": "critical",
"risk": "Default admin may have weak password",
"remediation": "Disable piadmin, use named Windows accounts",
},
{
"check": "Anonymous SDK access",
"severity": "high",
"risk": "Unauthenticated PI SDK connections",
"remediation": "Require authentication for all connections",
},
{
"check": "Shared service accounts",
"severity": "medium",
"risk": "Non-attributable access to historian data",
"remediation": "Use individual named accounts with PI Mappings",
},
]
for c in checks:
self.findings.append({
"severity": c["severity"],
"category": "Authentication",
"title": f"Review: {c['check']}",
"detail": c["risk"],
"remediation": c["remediation"],
})
return checks
def check_data_integrity(self):
"""Evaluate data integrity protections."""
checks = [
{
"check": "Audit trail for data modifications",
"severity": "high",
"detail": "Historical data edits must be logged with before/after values",
"remediation": "Enable PI audit trail for all modifications",
},
{
"check": "Backup integrity verification",
"severity": "medium",
"detail": "Backups should be tested regularly for recovery",
"remediation": "Implement automated backup verification quarterly",
},
{
"check": "Tag security enforcement",
"severity": "high",
"detail": "Per-tag access control must restrict write access",
"remediation": "Configure tag-level security for critical process points",
},
]
for c in checks:
self.findings.append({
"severity": c["severity"],
"category": "Data Integrity",
"title": c["check"],
"detail": c["detail"],
"remediation": c["remediation"],
})
return checks
def check_dmz_architecture(self):
"""Evaluate historian DMZ replication architecture."""
checks = [
{
"check": "Data diode or unidirectional gateway",
"severity": "high",
"detail": "OT-to-IT replication should use hardware-enforced unidirectional flow",
"remediation": "Deploy Waterfall or equivalent data diode between OT and DMZ",
},
{
"check": "Enterprise direct access to OT historian",
"severity": "critical",
"detail": "Enterprise users must never connect directly to OT historian",
"remediation": "Route all access through DMZ historian replica",
},
]
for c in checks:
self.findings.append({
"severity": c["severity"],
"category": "DMZ Architecture",
"title": c["check"],
"detail": c["detail"],
"remediation": c["remediation"],
})
return checks
def generate_report(self):
exposed = self.check_network_exposure()
auth = self.check_authentication()
integrity = self.check_data_integrity()
dmz = self.check_dmz_architecture()
severity_counts = {}
for f in self.findings:
sev = f["severity"]
severity_counts[sev] = severity_counts.get(sev, 0) + 1
report = {
"report_date": datetime.utcnow().isoformat(),
"historian_ip": self.ip,
"historian_type": self.hist_type,
"exposed_services": exposed,
"severity_summary": severity_counts,
"total_findings": len(self.findings),
"findings": self.findings,
}
out = self.output_dir / "historian_audit_report.json"
with open(out, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 2:
print("Usage: agent.py <historian_ip> [--type PI|AVEVA|PHD]")
sys.exit(1)
ip = sys.argv[1]
hist_type = "PI"
if "--type" in sys.argv:
hist_type = sys.argv[sys.argv.index("--type") + 1]
agent = HistorianSecurityAgent(ip, hist_type)
agent.generate_report()
if __name__ == "__main__":
main()