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
This commit is contained in:
mukul975
2026-03-19 13:26:49 +01:00
parent 63b442d347
commit c47eed6a64
900 changed files with 23085 additions and 2720 deletions
@@ -1,28 +1,210 @@
# API Reference: Ransomware backup strategy audit
# API Reference: Ransomware Backup Strategy Audit
## API Endpoints
S3: list_buckets, get_bucket_versioning; AWS Backup: list_backup_plans; 3-2-1 rule
## Libraries Used
| Library | Purpose |
|---------|---------|
| `boto3` | AWS SDK for S3, AWS Backup, and IAM auditing |
| `json` | Parse backup policies and compliance data |
| `subprocess` | Run local backup verification commands |
| `datetime` | Calculate backup age and RPO/RTO compliance |
## Installation
```bash
pip install boto3 subprocess
pip install boto3
```
## Libraries
| Library | Use |
|---------|-----|
| `boto3` | boto3 SDK/client |
| `subprocess` | subprocess SDK/client |
## Authentication
| Method | Header |
|--------|--------|
| Bearer Token | `Authorization: Bearer <token>` |
| API Key | `X-API-Key: <key>` |
```python
import boto3
import os
session = boto3.Session(
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
region_name=os.environ.get("AWS_REGION", "us-east-1"),
)
s3 = session.client("s3")
backup = session.client("backup")
iam = session.client("iam")
```
## AWS S3 Backup Audit
### Check Bucket Versioning (Ransomware Recovery)
```python
def audit_s3_versioning():
findings = []
buckets = s3.list_buckets()["Buckets"]
for bucket in buckets:
name = bucket["Name"]
versioning = s3.get_bucket_versioning(Bucket=name)
status = versioning.get("Status", "Disabled")
mfa_delete = versioning.get("MFADelete", "Disabled")
if status != "Enabled":
findings.append({
"bucket": name,
"issue": "Versioning not enabled",
"severity": "high",
"remediation": "Enable versioning for ransomware recovery",
})
if mfa_delete != "Enabled":
findings.append({
"bucket": name,
"issue": "MFA Delete not enabled",
"severity": "medium",
"remediation": "Enable MFA Delete to prevent bulk deletion",
})
return findings
```
### Check Object Lock (Immutable Backups)
```python
def check_object_lock(bucket_name):
try:
config = s3.get_object_lock_configuration(Bucket=bucket_name)
lock = config["ObjectLockConfiguration"]
rule = lock.get("Rule", {}).get("DefaultRetention", {})
return {
"bucket": bucket_name,
"object_lock_enabled": lock.get("ObjectLockEnabled") == "Enabled",
"retention_mode": rule.get("Mode", "NONE"),
"retention_days": rule.get("Days", 0),
}
except s3.exceptions.ClientError:
return {"bucket": bucket_name, "object_lock_enabled": False}
```
### Check Cross-Region Replication
```python
def check_cross_region_replication(bucket_name):
try:
repl = s3.get_bucket_replication(Bucket=bucket_name)
rules = repl["ReplicationConfiguration"]["Rules"]
return {
"bucket": bucket_name,
"replication_enabled": True,
"destinations": [
r["Destination"]["Bucket"] for r in rules if r["Status"] == "Enabled"
],
}
except s3.exceptions.ClientError:
return {"bucket": bucket_name, "replication_enabled": False}
```
## AWS Backup Service
### List Backup Plans
```python
def list_backup_plans():
plans = backup.list_backup_plans()["BackupPlansList"]
result = []
for plan in plans:
detail = backup.get_backup_plan(BackupPlanId=plan["BackupPlanId"])
rules = detail["BackupPlan"]["Rules"]
result.append({
"name": plan["BackupPlanName"],
"id": plan["BackupPlanId"],
"rules": [
{
"name": r["RuleName"],
"schedule": r.get("ScheduleExpression"),
"lifecycle_delete_days": r.get("Lifecycle", {}).get("DeleteAfterDays"),
"lifecycle_cold_days": r.get("Lifecycle", {}).get("MoveToColdStorageAfterDays"),
"target_vault": r["TargetBackupVaultName"],
}
for r in rules
],
})
return result
```
### Audit Backup Vault Access Policy
```python
def audit_vault_access(vault_name):
try:
policy = backup.get_backup_vault_access_policy(BackupVaultName=vault_name)
policy_doc = json.loads(policy["Policy"])
# Check for overly permissive policies
findings = []
for stmt in policy_doc.get("Statement", []):
if stmt.get("Effect") == "Allow" and stmt.get("Principal") == "*":
findings.append({
"vault": vault_name,
"issue": "Vault policy allows public access",
"severity": "critical",
})
return findings
except backup.exceptions.ClientError:
return [{"vault": vault_name, "issue": "No access policy set", "severity": "medium"}]
```
### List Recovery Points (Check Backup Freshness)
```python
from datetime import datetime, timezone
def check_backup_freshness(vault_name, max_age_hours=24):
recovery_points = backup.list_recovery_points_by_backup_vault(
BackupVaultName=vault_name, MaxResults=100
)["RecoveryPoints"]
stale = []
for rp in recovery_points:
age = datetime.now(timezone.utc) - rp["CreationDate"]
if age.total_seconds() > max_age_hours * 3600:
stale.append({
"resource": rp["ResourceArn"],
"last_backup": rp["CreationDate"].isoformat(),
"age_hours": round(age.total_seconds() / 3600),
"status": rp["Status"],
})
return stale
```
## 3-2-1 Backup Rule Audit
```python
def audit_321_rule(bucket_name):
"""Verify the 3-2-1 backup rule: 3 copies, 2 media types, 1 offsite."""
versioning = s3.get_bucket_versioning(Bucket=bucket_name)
replication = check_cross_region_replication(bucket_name)
object_lock = check_object_lock(bucket_name)
score = {
"three_copies": versioning.get("Status") == "Enabled",
"two_media": replication["replication_enabled"],
"one_offsite": replication["replication_enabled"],
"immutable": object_lock["object_lock_enabled"],
}
score["compliant"] = all([score["three_copies"], score["two_media"], score["one_offsite"]])
return score
```
## Output Format
```json
{"timestamp": "ISO-8601", "findings": [], "risk_level": "HIGH"}
{
"audit_date": "2025-01-15",
"backup_strategy": {
"total_buckets": 15,
"versioning_enabled": 12,
"object_lock_enabled": 5,
"cross_region_replication": 8,
"three_two_one_compliant": 4
},
"backup_plans": 3,
"recovery_points_stale": 2,
"findings": [
{
"resource": "critical-data-bucket",
"issue": "No Object Lock — vulnerable to ransomware deletion",
"severity": "high",
"remediation": "Enable S3 Object Lock in COMPLIANCE mode"
}
]
}
```
@@ -1,61 +1,295 @@
#!/usr/bin/env python3
"""Ransomware backup strategy audit."""
import argparse, json, sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
requests = None
"""Ransomware backup strategy audit agent.
def audit_config(target, token):
findings = []
if not requests: return [{"error": "requests required"}]
headers = {"Authorization": f"Bearer {token}"}
Audits backup infrastructure for ransomware resilience by checking
3-2-1 backup rule compliance, air-gapped/immutable backup presence,
backup encryption status, recovery point objectives (RPO), and
backup integrity verification schedules.
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone, timedelta
def check_veeam_backups(server_url, token):
"""Check Veeam backup status via REST API."""
try:
resp = requests.get(f"{target}/api/v1/status", headers=headers, timeout=10)
if resp.status_code == 200:
data = resp.json()
if not data.get("enabled", True):
findings.append({"check": "Service Status", "status": "DISABLED", "severity": "CRITICAL"})
elif resp.status_code == 401:
findings.append({"check": "Authentication", "status": "UNAUTHORIZED", "severity": "HIGH"})
except requests.RequestException as e:
findings.append({"error": str(e)})
import requests
except ImportError:
return [{"check": "Veeam API", "status": "SKIP", "severity": "INFO",
"detail": "requests library not installed"}]
findings = []
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
# Check backup jobs
try:
resp = requests.get(f"{server_url}/api/v1/jobs", headers=headers, timeout=30)
resp.raise_for_status()
jobs = resp.json().get("data", [])
for job in jobs:
job_name = job.get("name", "Unknown")
last_result = job.get("lastResult", "None")
schedule_enabled = job.get("scheduleEnabled", False)
if last_result == "Failed":
findings.append({
"check": f"Backup job: {job_name}",
"status": "FAIL",
"severity": "CRITICAL",
"detail": "Last backup failed",
})
elif not schedule_enabled:
findings.append({
"check": f"Backup job: {job_name}",
"status": "WARN",
"severity": "HIGH",
"detail": "Schedule disabled",
})
else:
findings.append({
"check": f"Backup job: {job_name}",
"status": "PASS",
"severity": "INFO",
"detail": f"Last result: {last_result}",
})
except Exception as e:
findings.append({"check": "Veeam job check", "status": "ERROR",
"severity": "HIGH", "detail": str(e)})
return findings
def check_compliance(target, token):
def check_restic_repository(repo_path, password_file=None):
"""Audit a Restic backup repository for integrity and freshness."""
findings = []
if not requests: return []
headers = {"Authorization": f"Bearer {token}"}
restic_bin = None
for name in ["restic", "restic.exe"]:
for d in os.environ.get("PATH", "").split(os.pathsep):
if os.path.isfile(os.path.join(d, name)):
restic_bin = os.path.join(d, name)
break
if not restic_bin:
findings.append({"check": "Restic binary", "status": "SKIP",
"severity": "INFO", "detail": "restic not found"})
return findings
env = dict(os.environ)
env["RESTIC_REPOSITORY"] = repo_path
if password_file:
env["RESTIC_PASSWORD_FILE"] = password_file
# Check snapshots
try:
resp = requests.get(f"{target}/api/v1/compliance", headers=headers, timeout=10)
if resp.status_code == 200:
for item in resp.json().get("checks", []):
if item.get("status") != "PASS":
findings.append({"check": item.get("name"), "status": item.get("status"),
"severity": item.get("severity", "MEDIUM")})
except requests.RequestException:
pass
result = subprocess.run(
[restic_bin, "snapshots", "--json"],
capture_output=True, text=True, timeout=120, env=env,
)
if result.returncode == 0:
snapshots = json.loads(result.stdout)
if not snapshots:
findings.append({"check": "Snapshots exist", "status": "FAIL",
"severity": "CRITICAL", "detail": "No snapshots found"})
else:
latest = max(snapshots, key=lambda s: s.get("time", ""))
latest_time = latest.get("time", "")[:19]
findings.append({"check": "Latest snapshot", "status": "PASS",
"severity": "INFO",
"detail": f"{latest_time} ({len(snapshots)} total)"})
try:
latest_dt = datetime.fromisoformat(latest_time.replace("Z", "+00:00"))
age_hours = (datetime.now(timezone.utc) - latest_dt).total_seconds() / 3600
if age_hours > 48:
findings.append({"check": "Backup freshness", "status": "FAIL",
"severity": "HIGH",
"detail": f"Latest backup is {age_hours:.0f}h old (>48h)"})
elif age_hours > 24:
findings.append({"check": "Backup freshness", "status": "WARN",
"severity": "MEDIUM",
"detail": f"Latest backup is {age_hours:.0f}h old (>24h)"})
except (ValueError, TypeError):
pass
else:
findings.append({"check": "Repository access", "status": "FAIL",
"severity": "CRITICAL", "detail": result.stderr[:200]})
except subprocess.TimeoutExpired:
findings.append({"check": "Repository access", "status": "FAIL",
"severity": "HIGH", "detail": "Command timed out"})
# Check repository integrity
try:
result = subprocess.run(
[restic_bin, "check", "--read-data-subset=1%"],
capture_output=True, text=True, timeout=300, env=env,
)
if result.returncode == 0:
findings.append({"check": "Repository integrity", "status": "PASS",
"severity": "INFO", "detail": "Integrity check passed"})
else:
findings.append({"check": "Repository integrity", "status": "FAIL",
"severity": "CRITICAL", "detail": "Integrity check failed"})
except subprocess.TimeoutExpired:
findings.append({"check": "Repository integrity", "status": "WARN",
"severity": "MEDIUM", "detail": "Integrity check timed out"})
return findings
def audit_321_rule(backup_config):
"""Audit compliance with the 3-2-1 backup rule."""
findings = []
copies = backup_config.get("copies", 0)
media_types = backup_config.get("media_types", [])
offsite_locations = backup_config.get("offsite_locations", [])
immutable = backup_config.get("immutable_backup", False)
air_gapped = backup_config.get("air_gapped", False)
# 3 copies
if copies >= 3:
findings.append({"check": "3-2-1: At least 3 copies", "status": "PASS",
"severity": "INFO", "detail": f"{copies} copies"})
else:
findings.append({"check": "3-2-1: At least 3 copies", "status": "FAIL",
"severity": "CRITICAL",
"detail": f"Only {copies} copies (need 3)"})
# 2 different media types
if len(media_types) >= 2:
findings.append({"check": "3-2-1: 2 different media types", "status": "PASS",
"severity": "INFO", "detail": ", ".join(media_types)})
else:
findings.append({"check": "3-2-1: 2 different media types", "status": "FAIL",
"severity": "HIGH",
"detail": f"Only {len(media_types)} type(s): {', '.join(media_types)}"})
# 1 offsite copy
if offsite_locations:
findings.append({"check": "3-2-1: 1 offsite copy", "status": "PASS",
"severity": "INFO", "detail": ", ".join(offsite_locations)})
else:
findings.append({"check": "3-2-1: 1 offsite copy", "status": "FAIL",
"severity": "CRITICAL", "detail": "No offsite backup"})
# Immutable backup (ransomware protection)
if immutable:
findings.append({"check": "Immutable backup", "status": "PASS",
"severity": "INFO", "detail": "WORM/immutable storage enabled"})
else:
findings.append({"check": "Immutable backup", "status": "FAIL",
"severity": "CRITICAL",
"detail": "No immutable backup - vulnerable to ransomware encryption"})
# Air-gapped backup
if air_gapped:
findings.append({"check": "Air-gapped backup", "status": "PASS",
"severity": "INFO", "detail": "Offline/air-gapped copy exists"})
else:
findings.append({"check": "Air-gapped backup", "status": "WARN",
"severity": "HIGH",
"detail": "No air-gapped backup - consider tape or offline storage"})
# Encryption
if backup_config.get("encrypted", False):
findings.append({"check": "Backup encryption", "status": "PASS",
"severity": "INFO"})
else:
findings.append({"check": "Backup encryption", "status": "FAIL",
"severity": "HIGH", "detail": "Backups are not encrypted"})
return findings
def format_summary(all_findings):
"""Print audit summary."""
print(f"\n{'='*60}")
print(f" Ransomware Backup Strategy Audit")
print(f"{'='*60}")
severity_counts = {}
for f in all_findings:
sev = f.get("severity", "INFO")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
pass_count = sum(1 for f in all_findings if f.get("status") == "PASS")
fail_count = sum(1 for f in all_findings if f.get("status") == "FAIL")
warn_count = sum(1 for f in all_findings if f.get("status") == "WARN")
print(f" Checks : {len(all_findings)}")
print(f" Passed : {pass_count}")
print(f" Failed : {fail_count}")
print(f" Warnings : {warn_count}")
print(f"\n By Severity:")
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
count = severity_counts.get(sev, 0)
if count > 0:
print(f" {sev:10s}: {count}")
print(f"\n Detailed Results:")
for f in all_findings:
status_icon = "OK" if f["status"] == "PASS" else "!!" if f["status"] == "FAIL" else "~~"
detail = f.get("detail", "")
print(f" [{status_icon}] [{f['severity']:8s}] {f['check']}"
+ (f": {detail}" if detail else ""))
return severity_counts
def main():
p = argparse.ArgumentParser(description="Ransomware backup strategy audit")
p.add_argument("--target", required=True, help="Target URL")
p.add_argument("--token", required=True, help="API token")
p.add_argument("--output", "-o", help="Output JSON report")
p.add_argument("--verbose", "-v", action="store_true")
a = p.parse_args()
print("[*] Ransomware backup strategy audit")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}
report["findings"].extend(audit_config(a.target, a.token))
report["findings"].extend(check_compliance(a.target, a.token))
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
report["risk_level"] = "HIGH" if high else "MEDIUM" if report["findings"] else "LOW"
print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}")
if a.output:
with open(a.output, "w") as f: json.dump(report, f, indent=2)
else:
parser = argparse.ArgumentParser(
description="Ransomware backup strategy audit agent"
)
parser.add_argument("--config", help="Backup configuration JSON file")
parser.add_argument("--restic-repo", help="Restic repository path to audit")
parser.add_argument("--restic-password-file", help="Restic password file")
parser.add_argument("--veeam-url", help="Veeam server URL")
parser.add_argument("--veeam-token", help="Veeam API token")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
all_findings = []
if args.config:
with open(args.config, "r") as f:
backup_config = json.load(f)
all_findings.extend(audit_321_rule(backup_config))
if args.restic_repo:
all_findings.extend(check_restic_repository(args.restic_repo, args.restic_password_file))
if args.veeam_url and args.veeam_token:
all_findings.extend(check_veeam_backups(args.veeam_url, args.veeam_token))
if not all_findings:
print("[!] No audit sources specified. Use --config, --restic-repo, or --veeam-url.",
file=sys.stderr)
parser.print_help()
sys.exit(1)
severity_counts = format_summary(all_findings)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "Ransomware Backup Audit",
"findings": all_findings,
"severity_counts": severity_counts,
"risk_level": (
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
else "HIGH" if severity_counts.get("HIGH", 0) > 0
else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()