v3.3.0 GUI dashboard + reports + model expansion + root fix

Engine:
- Fix: inject IS_SANDBOX=1 so Claude Code's --dangerously-skip-permissions
  works under root (real backend runs were exiting rc=1 immediately)
- models: expand to 40 models / 13 providers, tagged CLI vs API
  (NVIDIA NIM, DeepSeek, Mistral, Qwen/DashScope, Groq, Together, OpenRouter,
  Ollama, Gemini) — Qwen/DeepSeek/Llama usable via API
- backends: on_start callback surfaces the exact argv ("what runs behind it")
- orchestrator: require a Playwright screenshot per confirmed finding; collect
  results/activity.json; auto-generate reports after a run
- report.py: HTML always + PDF via Typst engine (.typ source emitted too)

Web dashboard (webgui/, stdlib only — no npm/build):
- Sidebar dashboard (PentAGI-style): Run / Agents / Insights / Reports / Settings
- Multi-target runs; live execution console + per-task activity; finding cards
  with screenshots; backend+provider+model pickers (CLI & API)
- Agents tab: browse 213 + add new .md agents from the UI
- Insights: interactive RL-weight + severity charts
- Reports: download/preview PDF + HTML
- Settings/API: execution mode, per-provider API keys, orchestrator, verbosity
- Endpoints: /api/agents (GET/POST), /api/rl, /api/config, /api/reports,
  /reports/* + /shots/* static serving

Cleanup: retire replaced web stack (frontend React, FastAPI backend, core
orchestration, old test) to legacy/. Active engine + GUI are fully standalone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUP
2026-06-14 23:26:11 -03:00
parent 22a7302a35
commit a5badefc29
205 changed files with 809 additions and 199 deletions
+20
View File
@@ -0,0 +1,20 @@
from backend.models.scan import Scan
from backend.models.target import Target
from backend.models.prompt import Prompt
from backend.models.endpoint import Endpoint
from backend.models.vulnerability import Vulnerability, VulnerabilityTest
from backend.models.report import Report
from backend.models.agent_task import AgentTask
from backend.models.vuln_lab import VulnLabChallenge
__all__ = [
"Scan",
"Target",
"Prompt",
"Endpoint",
"Vulnerability",
"VulnerabilityTest",
"Report",
"AgentTask",
"VulnLabChallenge"
]
+94
View File
@@ -0,0 +1,94 @@
"""
NeuroSploit v3 - Agent Task Model
Tracks all agent activities during scans for dashboard visibility.
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Integer, DateTime, Text, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class AgentTask(Base):
"""Agent task record for tracking scan activities"""
__tablename__ = "agent_tasks"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
# Task identification
task_type: Mapped[str] = mapped_column(String(50)) # recon, analysis, testing, reporting
task_name: Mapped[str] = mapped_column(String(255)) # Human-readable name
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Tool information
tool_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # nmap, nuclei, claude, httpx, etc.
tool_category: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) # scanner, analyzer, ai, crawler
# Status tracking
status: Mapped[str] = mapped_column(String(20), default="pending") # pending, running, completed, failed, cancelled
# Timing
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # Duration in milliseconds
# Results
items_processed: Mapped[int] = mapped_column(Integer, default=0) # URLs tested, hosts scanned, etc.
items_found: Mapped[int] = mapped_column(Integer, default=0) # Endpoints found, vulns found, etc.
result_summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Brief summary of results
# Error handling
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Metadata
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationships
scan: Mapped["Scan"] = relationship("Scan", back_populates="agent_tasks")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"task_type": self.task_type,
"task_name": self.task_name,
"description": self.description,
"tool_name": self.tool_name,
"tool_category": self.tool_category,
"status": self.status,
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
"duration_ms": self.duration_ms,
"items_processed": self.items_processed,
"items_found": self.items_found,
"result_summary": self.result_summary,
"error_message": self.error_message,
"created_at": self.created_at.isoformat() if self.created_at else None
}
def start(self):
"""Mark task as started"""
self.status = "running"
self.started_at = datetime.utcnow()
def complete(self, items_processed: int = 0, items_found: int = 0, summary: str = None):
"""Mark task as completed"""
self.status = "completed"
self.completed_at = datetime.utcnow()
self.items_processed = items_processed
self.items_found = items_found
self.result_summary = summary
if self.started_at:
self.duration_ms = int((self.completed_at - self.started_at).total_seconds() * 1000)
def fail(self, error: str):
"""Mark task as failed"""
self.status = "failed"
self.completed_at = datetime.utcnow()
self.error_message = error
if self.started_at:
self.duration_ms = int((self.completed_at - self.started_at).total_seconds() * 1000)
+61
View File
@@ -0,0 +1,61 @@
"""
NeuroSploit v3 - Endpoint Model
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, DateTime, Text, JSON, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Endpoint(Base):
"""Discovered endpoint model"""
__tablename__ = "endpoints"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
target_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("targets.id", ondelete="SET NULL"), nullable=True)
# Endpoint details
url: Mapped[str] = mapped_column(Text)
method: Mapped[str] = mapped_column(String(10), default="GET")
path: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Parameters
parameters: Mapped[List] = mapped_column(JSON, default=list) # [{name, type, value}]
headers: Mapped[dict] = mapped_column(JSON, default=dict)
# Response info
response_status: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
content_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
content_length: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# Detection
technologies: Mapped[List] = mapped_column(JSON, default=list)
interesting: Mapped[bool] = mapped_column(default=False) # Marked as interesting for testing
# Timestamps
discovered_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationships
scan: Mapped["Scan"] = relationship("Scan", back_populates="endpoints")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"target_id": self.target_id,
"url": self.url,
"method": self.method,
"path": self.path,
"parameters": self.parameters,
"headers": self.headers,
"response_status": self.response_status,
"content_type": self.content_type,
"content_length": self.content_length,
"technologies": self.technologies,
"interesting": self.interesting,
"discovered_at": self.discovered_at.isoformat() if self.discovered_at else None
}
+44
View File
@@ -0,0 +1,44 @@
"""
NeuroSploit v3 - Prompt Model
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Boolean, DateTime, Text, JSON
from sqlalchemy.orm import Mapped, mapped_column
from backend.db.database import Base
import uuid
class Prompt(Base):
"""Prompt model for storing custom and preset prompts"""
__tablename__ = "prompts"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name: Mapped[str] = mapped_column(String(255))
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
content: Mapped[str] = mapped_column(Text)
# Categorization
is_preset: Mapped[bool] = mapped_column(Boolean, default=False)
category: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # pentest, bug_bounty, api, etc.
# Parsed vulnerabilities (extracted by AI)
parsed_vulnerabilities: Mapped[List] = mapped_column(JSON, default=list)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"name": self.name,
"description": self.description,
"content": self.content,
"is_preset": self.is_preset,
"category": self.category,
"parsed_vulnerabilities": self.parsed_vulnerabilities,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None
}
+49
View File
@@ -0,0 +1,49 @@
"""
NeuroSploit v3 - Report Model
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, DateTime, Text, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Report(Base):
"""Report model"""
__tablename__ = "reports"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
# Report details
title: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
format: Mapped[str] = mapped_column(String(20), default="html") # html, pdf, json
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Content
executive_summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Auto-generation flags
auto_generated: Mapped[bool] = mapped_column(Boolean, default=False) # True if auto-generated on scan completion/stop
is_partial: Mapped[bool] = mapped_column(Boolean, default=False) # True if generated from stopped/incomplete scan
# Timestamps
generated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationship
scan: Mapped["Scan"] = relationship("Scan", back_populates="reports")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"title": self.title,
"format": self.format,
"file_path": self.file_path,
"executive_summary": self.executive_summary,
"auto_generated": self.auto_generated,
"is_partial": self.is_partial,
"generated_at": self.generated_at.isoformat() if self.generated_at else None
}
+91
View File
@@ -0,0 +1,91 @@
"""
NeuroSploit v3 - Scan Model
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, Boolean, DateTime, Text, JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Scan(Base):
"""Scan model representing a penetration test scan"""
__tablename__ = "scans"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
status: Mapped[str] = mapped_column(String(50), default="pending") # pending, running, completed, failed, stopped
scan_type: Mapped[str] = mapped_column(String(50), default="full") # quick, full, custom
recon_enabled: Mapped[bool] = mapped_column(Boolean, default=True)
# Progress tracking
progress: Mapped[int] = mapped_column(Integer, default=0)
current_phase: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) # recon, testing, reporting
# Configuration
config: Mapped[dict] = mapped_column(JSON, default=dict)
# Custom prompt (if any)
custom_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
prompt_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True)
# Authentication for testing (IMPORTANT: Use responsibly with authorization)
auth_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) # none, cookie, header, basic, bearer
auth_credentials: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # Stores auth data securely
custom_headers: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # Additional HTTP headers
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
duration: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # Duration in seconds
# Error handling
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Statistics (updated during scan)
total_endpoints: Mapped[int] = mapped_column(Integer, default=0)
total_vulnerabilities: Mapped[int] = mapped_column(Integer, default=0)
critical_count: Mapped[int] = mapped_column(Integer, default=0)
high_count: Mapped[int] = mapped_column(Integer, default=0)
medium_count: Mapped[int] = mapped_column(Integer, default=0)
low_count: Mapped[int] = mapped_column(Integer, default=0)
info_count: Mapped[int] = mapped_column(Integer, default=0)
# Relationships
targets: Mapped[List["Target"]] = relationship("Target", back_populates="scan", cascade="all, delete-orphan")
endpoints: Mapped[List["Endpoint"]] = relationship("Endpoint", back_populates="scan", cascade="all, delete-orphan")
vulnerabilities: Mapped[List["Vulnerability"]] = relationship("Vulnerability", back_populates="scan", cascade="all, delete-orphan")
reports: Mapped[List["Report"]] = relationship("Report", back_populates="scan", cascade="all, delete-orphan")
agent_tasks: Mapped[List["AgentTask"]] = relationship("AgentTask", back_populates="scan", cascade="all, delete-orphan")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"name": self.name,
"status": self.status,
"scan_type": self.scan_type,
"recon_enabled": self.recon_enabled,
"progress": self.progress,
"current_phase": self.current_phase,
"config": self.config,
"custom_prompt": self.custom_prompt,
"prompt_id": self.prompt_id,
"auth_type": self.auth_type,
"auth_credentials": self.auth_credentials, # Careful: may contain sensitive data
"custom_headers": self.custom_headers,
"created_at": self.created_at.isoformat() if self.created_at else None,
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
"duration": self.duration,
"error_message": self.error_message,
"total_endpoints": self.total_endpoints,
"total_vulnerabilities": self.total_vulnerabilities,
"critical_count": self.critical_count,
"high_count": self.high_count,
"medium_count": self.medium_count,
"low_count": self.low_count,
"info_count": self.info_count
}
+47
View File
@@ -0,0 +1,47 @@
"""
NeuroSploit v3 - Target Model
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Integer, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class Target(Base):
"""Target URL model"""
__tablename__ = "targets"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
# URL details
url: Mapped[str] = mapped_column(String(2048))
hostname: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
port: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
protocol: Mapped[Optional[str]] = mapped_column(String(10), nullable=True)
path: Mapped[Optional[str]] = mapped_column(String(2048), nullable=True)
# Status
status: Mapped[str] = mapped_column(String(50), default="pending") # pending, scanning, completed, failed
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationship
scan: Mapped["Scan"] = relationship("Scan", back_populates="targets")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"url": self.url,
"hostname": self.hostname,
"port": self.port,
"protocol": self.protocol,
"path": self.path,
"status": self.status,
"created_at": self.created_at.isoformat() if self.created_at else None
}
+94
View File
@@ -0,0 +1,94 @@
"""
NeuroSploit v3 - Vulnerability Lab Challenge Model
Tracks isolated vulnerability testing sessions (labs, CTFs, PortSwigger, etc.)
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, Float, Boolean, DateTime, Text, JSON, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from backend.db.database import Base
import uuid
class VulnLabChallenge(Base):
"""Individual vulnerability lab/challenge test record"""
__tablename__ = "vuln_lab_challenges"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
# Target info
target_url: Mapped[str] = mapped_column(Text)
challenge_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
# Vulnerability scope
vuln_type: Mapped[str] = mapped_column(String(100)) # e.g. xss_reflected, sqli_union
vuln_category: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) # injection, auth, client_side, etc.
# Authentication
auth_type: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # cookie, bearer, basic, header
auth_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Execution state
status: Mapped[str] = mapped_column(String(20), default="pending") # pending, running, completed, failed, stopped
result: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # detected, not_detected, error
# Agent linkage
agent_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True)
scan_id: Mapped[Optional[str]] = mapped_column(String(36), nullable=True)
# Results
findings_count: Mapped[int] = mapped_column(Integer, default=0)
critical_count: Mapped[int] = mapped_column(Integer, default=0)
high_count: Mapped[int] = mapped_column(Integer, default=0)
medium_count: Mapped[int] = mapped_column(Integer, default=0)
low_count: Mapped[int] = mapped_column(Integer, default=0)
info_count: Mapped[int] = mapped_column(Integer, default=0)
# Findings detail (JSON list of finding summaries)
findings_detail: Mapped[List] = mapped_column(JSON, default=list)
# Timing
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
duration: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # seconds
# Notes
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Logs (JSON list of log entries persisted after completion)
logs: Mapped[List] = mapped_column(JSON, default=list)
# Endpoints discovered count
endpoints_count: Mapped[int] = mapped_column(Integer, default=0)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
def to_dict(self) -> dict:
return {
"id": self.id,
"target_url": self.target_url,
"challenge_name": self.challenge_name,
"vuln_type": self.vuln_type,
"vuln_category": self.vuln_category,
"auth_type": self.auth_type,
"status": self.status,
"result": self.result,
"agent_id": self.agent_id,
"scan_id": self.scan_id,
"findings_count": self.findings_count,
"critical_count": self.critical_count,
"high_count": self.high_count,
"medium_count": self.medium_count,
"low_count": self.low_count,
"info_count": self.info_count,
"findings_detail": self.findings_detail or [],
"started_at": self.started_at.isoformat() if self.started_at else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
"duration": self.duration,
"notes": self.notes,
"logs": self.logs or [],
"endpoints_count": self.endpoints_count,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
+149
View File
@@ -0,0 +1,149 @@
"""
NeuroSploit v3 - Vulnerability Models
"""
from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, Float, Boolean, DateTime, Text, JSON, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.db.database import Base
import uuid
class VulnerabilityTest(Base):
"""Individual vulnerability test record"""
__tablename__ = "vulnerability_tests"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
endpoint_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("endpoints.id", ondelete="SET NULL"), nullable=True)
# Test details
vulnerability_type: Mapped[str] = mapped_column(String(100)) # xss_reflected, sqli_union, etc.
payload: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Request/Response
request_data: Mapped[dict] = mapped_column(JSON, default=dict)
response_data: Mapped[dict] = mapped_column(JSON, default=dict)
# Result
is_vulnerable: Mapped[bool] = mapped_column(Boolean, default=False)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # 0.0 to 1.0
evidence: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Timestamps
tested_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"endpoint_id": self.endpoint_id,
"vulnerability_type": self.vulnerability_type,
"payload": self.payload,
"request_data": self.request_data,
"response_data": self.response_data,
"is_vulnerable": self.is_vulnerable,
"confidence": self.confidence,
"evidence": self.evidence,
"tested_at": self.tested_at.isoformat() if self.tested_at else None
}
class Vulnerability(Base):
"""Confirmed vulnerability model"""
__tablename__ = "vulnerabilities"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
scan_id: Mapped[str] = mapped_column(String(36), ForeignKey("scans.id", ondelete="CASCADE"))
test_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("vulnerability_tests.id", ondelete="SET NULL"), nullable=True)
# Vulnerability details
title: Mapped[str] = mapped_column(String(500))
vulnerability_type: Mapped[str] = mapped_column(String(100))
severity: Mapped[str] = mapped_column(String(20)) # critical, high, medium, low, info
# Scoring
cvss_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
cvss_vector: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
cwe_id: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
# Details
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
affected_endpoint: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Proof of Concept
poc_request: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
poc_response: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
poc_payload: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
poc_parameter: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) # Vulnerable parameter
poc_evidence: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Evidence of vulnerability
# Remediation
impact: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
remediation: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
references: Mapped[List] = mapped_column(JSON, default=list)
# AI Analysis
ai_analysis: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# PoC Code (executable proof-of-concept: HTML, Python, curl, etc.)
poc_code: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Screenshots (list of base64 data URIs or filesystem paths)
screenshots: Mapped[List] = mapped_column(JSON, default=list)
# Source URL and parameter (for finding_id reconstruction)
url: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
parameter: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
# Confidence & Proof (from ValidationJudge pipeline)
confidence_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100
confidence_breakdown: Mapped[dict] = mapped_column(JSON, default=dict) # {proof: X, impact: Y, controls: Z}
proof_of_execution: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Proof type + detail
# Validation status (manual review workflow)
validation_status: Mapped[str] = mapped_column(String(20), default="ai_confirmed")
# Values: "ai_confirmed" | "ai_rejected" | "validated" | "false_positive" | "pending_review"
ai_rejection_reason: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Relationships
scan: Mapped["Scan"] = relationship("Scan", back_populates="vulnerabilities")
def to_dict(self) -> dict:
"""Convert to dictionary"""
return {
"id": self.id,
"scan_id": self.scan_id,
"test_id": self.test_id,
"title": self.title,
"vulnerability_type": self.vulnerability_type,
"severity": self.severity,
"cvss_score": self.cvss_score,
"cvss_vector": self.cvss_vector,
"cwe_id": self.cwe_id,
"description": self.description,
"affected_endpoint": self.affected_endpoint,
"poc_request": self.poc_request,
"poc_response": self.poc_response,
"poc_payload": self.poc_payload,
"poc_parameter": self.poc_parameter,
"poc_evidence": self.poc_evidence,
"impact": self.impact,
"remediation": self.remediation,
"references": self.references,
"ai_analysis": self.ai_analysis,
"poc_code": self.poc_code,
"screenshots": self.screenshots or [],
"url": self.url,
"parameter": self.parameter,
"confidence_score": self.confidence_score,
"confidence_breakdown": self.confidence_breakdown or {},
"proof_of_execution": self.proof_of_execution,
"validation_status": self.validation_status or "ai_confirmed",
"ai_rejection_reason": self.ai_rejection_reason,
"created_at": self.created_at.isoformat() if self.created_at else None
}