mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-03-20 09:13:37 +00:00
116 modules | 100 vuln types | 18 API routes | 18 frontend pages Major features: - VulnEngine: 100 vuln types, 526+ payloads, 12 testers, anti-hallucination prompts - Autonomous Agent: 3-stream auto pentest, multi-session (5 concurrent), pause/resume/stop - CLI Agent: Claude Code / Gemini CLI / Codex CLI inside Kali containers - Validation Pipeline: negative controls, proof of execution, confidence scoring, judge - AI Reasoning: ReACT engine, token budget, endpoint classifier, CVE hunter, deep recon - Multi-Agent: 5 specialists + orchestrator + researcher AI + vuln type agents - RAG System: BM25/TF-IDF/ChromaDB vectorstore, few-shot, reasoning templates - Smart Router: 20 providers (8 CLI OAuth + 12 API), tier failover, token refresh - Kali Sandbox: container-per-scan, 56 tools, VPN support, on-demand install - Full IA Testing: methodology-driven comprehensive pentest sessions - Notifications: Discord, Telegram, WhatsApp/Twilio multi-channel alerts - Frontend: React/TypeScript with 18 pages, real-time WebSocket updates
309 lines
12 KiB
Python
309 lines
12 KiB
Python
"""
|
|
NeuroSploit v3 - Multi-Channel Notification Manager
|
|
|
|
Sends scan event alerts to Discord, Telegram, and WhatsApp (Twilio).
|
|
Hooks into the existing WebSocket broadcast infrastructure as event source.
|
|
All channels are disabled by default (opt-in via .env).
|
|
Uses only aiohttp (already a dependency) for HTTP calls.
|
|
"""
|
|
|
|
import asyncio
|
|
import base64
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Any, Dict, List, Optional
|
|
from urllib.parse import quote
|
|
|
|
import aiohttp
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class NotificationEvent(Enum):
|
|
SCAN_STARTED = "scan_started"
|
|
VULN_FOUND = "vuln_found"
|
|
SCAN_COMPLETED = "scan_completed"
|
|
SCAN_FAILED = "scan_failed"
|
|
|
|
|
|
# Severity → Discord embed color
|
|
SEVERITY_COLORS = {
|
|
"critical": 0xFF0000,
|
|
"high": 0xFF6600,
|
|
"medium": 0xFFCC00,
|
|
"low": 0x33CC33,
|
|
"info": 0x3399FF,
|
|
}
|
|
|
|
|
|
class NotificationManager:
|
|
"""Async multi-channel notification dispatcher.
|
|
|
|
Sends fire-and-forget notifications to configured channels.
|
|
Never blocks the scan flow — all errors are swallowed and logged.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.reload_config()
|
|
|
|
def reload_config(self):
|
|
"""(Re)load configuration from environment variables."""
|
|
self.enabled = os.getenv("ENABLE_NOTIFICATIONS", "false").lower() == "true"
|
|
|
|
# Discord
|
|
self.discord_webhook = os.getenv("DISCORD_WEBHOOK_URL", "").strip()
|
|
|
|
# Telegram
|
|
self.telegram_token = os.getenv("TELEGRAM_BOT_TOKEN", "").strip()
|
|
self.telegram_chat_id = os.getenv("TELEGRAM_CHAT_ID", "").strip()
|
|
|
|
# WhatsApp (Twilio)
|
|
self.twilio_sid = os.getenv("TWILIO_ACCOUNT_SID", "").strip()
|
|
self.twilio_token = os.getenv("TWILIO_AUTH_TOKEN", "").strip()
|
|
self.twilio_from = os.getenv("TWILIO_FROM_NUMBER", "").strip()
|
|
self.twilio_to = os.getenv("TWILIO_TO_NUMBER", "").strip()
|
|
|
|
# Severity filter
|
|
raw = os.getenv("NOTIFICATION_SEVERITY_FILTER", "critical,high").strip()
|
|
self.severity_filter = set(s.strip() for s in raw.split(",") if s.strip())
|
|
|
|
@property
|
|
def has_discord(self) -> bool:
|
|
return bool(self.discord_webhook)
|
|
|
|
@property
|
|
def has_telegram(self) -> bool:
|
|
return bool(self.telegram_token and self.telegram_chat_id)
|
|
|
|
@property
|
|
def has_whatsapp(self) -> bool:
|
|
return bool(self.twilio_sid and self.twilio_token and self.twilio_from and self.twilio_to)
|
|
|
|
async def notify(self, event: NotificationEvent, data: Dict[str, Any]):
|
|
"""Send notification to all configured channels.
|
|
|
|
For VULN_FOUND events, respects the severity filter.
|
|
"""
|
|
if not self.enabled:
|
|
return
|
|
|
|
# Severity filter for vulnerability findings
|
|
if event == NotificationEvent.VULN_FOUND:
|
|
severity = data.get("severity", "").lower()
|
|
if severity not in self.severity_filter:
|
|
return
|
|
|
|
tasks = []
|
|
if self.has_discord:
|
|
tasks.append(self._send_discord(event, data))
|
|
if self.has_telegram:
|
|
tasks.append(self._send_telegram(event, data))
|
|
if self.has_whatsapp:
|
|
tasks.append(self._send_whatsapp(event, data))
|
|
|
|
if tasks:
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
# ── Discord ──────────────────────────────────────────────────────
|
|
|
|
async def _send_discord(self, event: NotificationEvent, data: Dict):
|
|
"""Send Discord webhook with rich embed."""
|
|
try:
|
|
embed = self._build_discord_embed(event, data)
|
|
payload = {"embeds": [embed]}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
self.discord_webhook,
|
|
json=payload,
|
|
timeout=aiohttp.ClientTimeout(total=10),
|
|
) as resp:
|
|
if resp.status not in (200, 204):
|
|
body = await resp.text()
|
|
logger.warning(f"Discord notification failed ({resp.status}): {body[:200]}")
|
|
except Exception as e:
|
|
logger.warning(f"Discord notification error: {e}")
|
|
|
|
def _build_discord_embed(self, event: NotificationEvent, data: Dict) -> Dict:
|
|
"""Build Discord embed object."""
|
|
ts = datetime.utcnow().isoformat()
|
|
|
|
if event == NotificationEvent.SCAN_STARTED:
|
|
return {
|
|
"title": "Scan Started",
|
|
"description": f"Target: `{data.get('target', 'unknown')}`",
|
|
"color": 0x3399FF,
|
|
"timestamp": ts,
|
|
"footer": {"text": "NeuroSploit v3"},
|
|
}
|
|
|
|
elif event == NotificationEvent.VULN_FOUND:
|
|
severity = data.get("severity", "medium").lower()
|
|
return {
|
|
"title": f"{severity.upper()}: {data.get('title', 'Vulnerability Found')}",
|
|
"description": data.get("description", "")[:500] or f"Endpoint: `{data.get('endpoint', '')}`",
|
|
"color": SEVERITY_COLORS.get(severity, 0xFFCC00),
|
|
"fields": [
|
|
{"name": "Severity", "value": severity.upper(), "inline": True},
|
|
{"name": "Type", "value": data.get("vulnerability_type", "unknown"), "inline": True},
|
|
{"name": "Endpoint", "value": f"`{data.get('endpoint', 'N/A')}`", "inline": False},
|
|
],
|
|
"timestamp": ts,
|
|
"footer": {"text": "NeuroSploit v3"},
|
|
}
|
|
|
|
elif event == NotificationEvent.SCAN_COMPLETED:
|
|
total = data.get("total_vulnerabilities", 0)
|
|
crit = data.get("critical", 0)
|
|
high = data.get("high", 0)
|
|
med = data.get("medium", 0)
|
|
return {
|
|
"title": "Scan Completed",
|
|
"description": (
|
|
f"**{total}** vulnerabilities found\n"
|
|
f"Critical: **{crit}** | High: **{high}** | Medium: **{med}**"
|
|
),
|
|
"color": 0x00CC00 if total == 0 else 0xFF6600,
|
|
"timestamp": ts,
|
|
"footer": {"text": "NeuroSploit v3"},
|
|
}
|
|
|
|
elif event == NotificationEvent.SCAN_FAILED:
|
|
return {
|
|
"title": "Scan Failed",
|
|
"description": f"Error: {data.get('error', 'Unknown error')[:500]}",
|
|
"color": 0xFF0000,
|
|
"timestamp": ts,
|
|
"footer": {"text": "NeuroSploit v3"},
|
|
}
|
|
|
|
return {"title": event.value, "color": 0x999999, "timestamp": ts}
|
|
|
|
# ── Telegram ─────────────────────────────────────────────────────
|
|
|
|
async def _send_telegram(self, event: NotificationEvent, data: Dict):
|
|
"""Send Telegram message via Bot API."""
|
|
try:
|
|
text = self._build_telegram_text(event, data)
|
|
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
|
|
payload = {
|
|
"chat_id": self.telegram_chat_id,
|
|
"text": text,
|
|
"parse_mode": "Markdown",
|
|
}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
url, json=payload,
|
|
timeout=aiohttp.ClientTimeout(total=10),
|
|
) as resp:
|
|
if resp.status != 200:
|
|
body = await resp.text()
|
|
logger.warning(f"Telegram notification failed ({resp.status}): {body[:200]}")
|
|
except Exception as e:
|
|
logger.warning(f"Telegram notification error: {e}")
|
|
|
|
def _build_telegram_text(self, event: NotificationEvent, data: Dict) -> str:
|
|
"""Build Telegram message text."""
|
|
if event == NotificationEvent.SCAN_STARTED:
|
|
return f"*Scan Started*\nTarget: `{data.get('target', 'unknown')}`"
|
|
|
|
elif event == NotificationEvent.VULN_FOUND:
|
|
sev = data.get("severity", "medium").upper()
|
|
return (
|
|
f"*{sev}: {data.get('title', 'Vulnerability Found')}*\n"
|
|
f"Type: {data.get('vulnerability_type', 'unknown')}\n"
|
|
f"Endpoint: `{data.get('endpoint', 'N/A')}`"
|
|
)
|
|
|
|
elif event == NotificationEvent.SCAN_COMPLETED:
|
|
total = data.get("total_vulnerabilities", 0)
|
|
crit = data.get("critical", 0)
|
|
high = data.get("high", 0)
|
|
return (
|
|
f"*Scan Completed*\n"
|
|
f"Vulnerabilities: *{total}*\n"
|
|
f"Critical: {crit} | High: {high}"
|
|
)
|
|
|
|
elif event == NotificationEvent.SCAN_FAILED:
|
|
return f"*Scan Failed*\nError: {data.get('error', 'Unknown')[:300]}"
|
|
|
|
return f"*{event.value}*"
|
|
|
|
# ── WhatsApp (Twilio) ────────────────────────────────────────────
|
|
|
|
async def _send_whatsapp(self, event: NotificationEvent, data: Dict):
|
|
"""Send WhatsApp message via Twilio API."""
|
|
try:
|
|
text = self._build_telegram_text(event, data) # Reuse text format
|
|
# Strip markdown for WhatsApp
|
|
text = text.replace("*", "").replace("`", "")
|
|
|
|
url = f"https://api.twilio.com/2010-04-01/Accounts/{self.twilio_sid}/Messages.json"
|
|
auth_str = base64.b64encode(
|
|
f"{self.twilio_sid}:{self.twilio_token}".encode()
|
|
).decode()
|
|
|
|
form_data = {
|
|
"From": f"whatsapp:{self.twilio_from}",
|
|
"To": f"whatsapp:{self.twilio_to}",
|
|
"Body": text,
|
|
}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
url,
|
|
data=form_data,
|
|
headers={"Authorization": f"Basic {auth_str}"},
|
|
timeout=aiohttp.ClientTimeout(total=10),
|
|
) as resp:
|
|
if resp.status not in (200, 201):
|
|
body = await resp.text()
|
|
logger.warning(f"WhatsApp notification failed ({resp.status}): {body[:200]}")
|
|
except Exception as e:
|
|
logger.warning(f"WhatsApp notification error: {e}")
|
|
|
|
# ── Test ─────────────────────────────────────────────────────────
|
|
|
|
async def test_channel(self, channel: str) -> Dict:
|
|
"""Send a test notification to a specific channel."""
|
|
test_data = {
|
|
"target": "https://example.com",
|
|
"title": "Test Notification",
|
|
"severity": "info",
|
|
"vulnerability_type": "test",
|
|
"endpoint": "/test",
|
|
"total_vulnerabilities": 0,
|
|
"critical": 0,
|
|
"high": 0,
|
|
"medium": 0,
|
|
"error": "This is a test",
|
|
}
|
|
event = NotificationEvent.SCAN_STARTED
|
|
|
|
try:
|
|
if channel == "discord":
|
|
if not self.has_discord:
|
|
return {"success": False, "error": "Discord webhook URL not configured"}
|
|
await self._send_discord(event, test_data)
|
|
elif channel == "telegram":
|
|
if not self.has_telegram:
|
|
return {"success": False, "error": "Telegram bot token or chat ID not configured"}
|
|
await self._send_telegram(event, test_data)
|
|
elif channel == "whatsapp":
|
|
if not self.has_whatsapp:
|
|
return {"success": False, "error": "Twilio credentials not configured"}
|
|
await self._send_whatsapp(event, test_data)
|
|
else:
|
|
return {"success": False, "error": f"Unknown channel: {channel}"}
|
|
return {"success": True, "message": f"Test notification sent to {channel}"}
|
|
except Exception as e:
|
|
return {"success": False, "error": str(e)}
|
|
|
|
|
|
# Global singleton
|
|
notification_manager = NotificationManager()
|