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

146 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Agent for configuring and auditing NextDNS zero trust DNS filtering via API."""
import requests
import json
import argparse
from datetime import datetime, timezone
NEXTDNS_API = "https://api.nextdns.io"
def get_profile(api_key, profile_id):
"""Retrieve NextDNS profile configuration."""
headers = {"X-Api-Key": api_key}
resp = requests.get(f"{NEXTDNS_API}/profiles/{profile_id}", headers=headers, timeout=15)
resp.raise_for_status()
profile = resp.json()
print(f"[*] Profile: {profile.get('name', profile_id)}")
print(f" Security: {json.dumps(profile.get('security', {}), indent=2)[:200]}")
return profile
def audit_security_settings(api_key, profile_id):
"""Audit security features enabled on a NextDNS profile."""
headers = {"X-Api-Key": api_key}
resp = requests.get(f"{NEXTDNS_API}/profiles/{profile_id}/security", headers=headers, timeout=15)
resp.raise_for_status()
security = resp.json()
findings = []
checks = {
"threatIntelligenceFeeds": "Threat intelligence feeds",
"aiDetection": "AI-driven threat detection",
"googleSafeBrowsing": "Google Safe Browsing",
"cryptojacking": "Cryptojacking protection",
"dnsRebinding": "DNS rebinding protection",
"idnHomographs": "IDN homograph protection",
"typosquatting": "Typosquatting protection",
"dga": "DGA domain protection",
"nrd": "Newly registered domains blocking",
"ddns": "Dynamic DNS blocking",
"csam": "CSAM blocking",
}
for key, label in checks.items():
enabled = security.get(key, False)
status = "ENABLED" if enabled else "DISABLED"
if not enabled:
findings.append({"feature": label, "key": key, "severity": "MEDIUM"})
print(f" [{'+' if enabled else '!'}] {label}: {status}")
print(f"\n[*] {len(findings)} security features disabled")
return findings
def get_query_logs(api_key, profile_id, limit=100):
"""Retrieve recent DNS query logs for analysis."""
headers = {"X-Api-Key": api_key}
params = {"limit": limit}
resp = requests.get(f"{NEXTDNS_API}/profiles/{profile_id}/logs",
headers=headers, params=params, timeout=15)
resp.raise_for_status()
logs = resp.json().get("data", [])
blocked = [l for l in logs if l.get("status") == "blocked"]
print(f"[*] Query logs: {len(logs)} total, {len(blocked)} blocked")
for entry in blocked[:10]:
print(f" [BLOCKED] {entry.get('domain')} - reason: {entry.get('reasons', ['?'])[0]}")
return logs
def get_analytics(api_key, profile_id, period="last30d"):
"""Retrieve DNS analytics and threat statistics."""
headers = {"X-Api-Key": api_key}
endpoints = {
"queries": f"/profiles/{profile_id}/analytics/status",
"domains": f"/profiles/{profile_id}/analytics/domains",
"blocked_reasons": f"/profiles/{profile_id}/analytics/blockedReasons",
}
analytics = {}
for name, path in endpoints.items():
resp = requests.get(f"{NEXTDNS_API}{path}", headers=headers,
params={"from": f"-{period}"}, timeout=15)
if resp.status_code == 200:
analytics[name] = resp.json()
if "queries" in analytics:
data = analytics["queries"].get("data", [])
total = sum(d.get("queries", 0) for d in data)
blocked = sum(d.get("blockedQueries", 0) for d in data)
print(f"[*] Analytics ({period}): {total} queries, {blocked} blocked "
f"({blocked/total*100:.1f}%)" if total else "[*] No query data")
return analytics
def check_denylist(api_key, profile_id):
"""Check configured denylists and custom blocked domains."""
headers = {"X-Api-Key": api_key}
resp = requests.get(f"{NEXTDNS_API}/profiles/{profile_id}/denylist",
headers=headers, timeout=15)
resp.raise_for_status()
denylist = resp.json()
entries = denylist.get("data", [])
print(f"[*] Denylist entries: {len(entries)}")
for e in entries[:20]:
print(f" {e.get('id', 'unknown')}: active={e.get('active', True)}")
return entries
def generate_report(profile, findings, logs, analytics, output_path):
"""Generate NextDNS audit report."""
report = {
"audit_date": datetime.now(timezone.utc).isoformat(),
"profile": profile.get("name", "unknown"),
"security_findings": findings,
"blocked_queries_sample": [l for l in logs if l.get("status") == "blocked"][:20],
"analytics_summary": analytics,
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[*] Report saved to {output_path}")
def main():
parser = argparse.ArgumentParser(description="NextDNS Zero Trust DNS Audit Agent")
parser.add_argument("action", choices=["audit", "logs", "analytics", "denylist", "full-audit"])
parser.add_argument("--api-key", required=True, help="NextDNS API key")
parser.add_argument("--profile", required=True, help="NextDNS profile ID")
parser.add_argument("-o", "--output", default="nextdns_audit.json")
args = parser.parse_args()
if args.action == "audit":
get_profile(args.api_key, args.profile)
audit_security_settings(args.api_key, args.profile)
elif args.action == "logs":
get_query_logs(args.api_key, args.profile)
elif args.action == "analytics":
get_analytics(args.api_key, args.profile)
elif args.action == "denylist":
check_denylist(args.api_key, args.profile)
elif args.action == "full-audit":
prof = get_profile(args.api_key, args.profile)
findings = audit_security_settings(args.api_key, args.profile)
logs = get_query_logs(args.api_key, args.profile)
analytics = get_analytics(args.api_key, args.profile)
generate_report(prof, findings, logs, analytics, args.output)
if __name__ == "__main__":
main()