162 lines
4.4 KiB
Python
162 lines
4.4 KiB
Python
"""
|
|
Base scanner interface and result structures.
|
|
|
|
All scanner implementations should inherit from BaseScanner
|
|
and return ScannerResult objects.
|
|
"""
|
|
|
|
import logging
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List, Optional
|
|
from enum import Enum
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ScannerStatus(str, Enum):
|
|
"""Status of a scanner execution."""
|
|
SUCCESS = "success"
|
|
PARTIAL = "partial"
|
|
FAILED = "failed"
|
|
SKIPPED = "skipped"
|
|
|
|
|
|
@dataclass
|
|
class IssueData:
|
|
"""
|
|
Represents a single issue found by a scanner.
|
|
|
|
Attributes:
|
|
category: Issue category (security, performance, etc.)
|
|
severity: Issue severity (critical, high, medium, low, info)
|
|
title: Brief title of the issue
|
|
description: Detailed description
|
|
tool: The scanner that found this issue
|
|
affected_url: Specific URL affected (optional)
|
|
remediation: Suggested fix (optional)
|
|
raw_data: Original scanner data (optional)
|
|
"""
|
|
category: str
|
|
severity: str
|
|
title: str
|
|
description: str
|
|
tool: str
|
|
affected_url: Optional[str] = None
|
|
remediation: Optional[str] = None
|
|
raw_data: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
@dataclass
|
|
class MetricData:
|
|
"""
|
|
Represents a single metric measured by a scanner.
|
|
|
|
Attributes:
|
|
name: Internal name (e.g., 'first_contentful_paint_ms')
|
|
display_name: Human-readable name
|
|
value: Numeric value
|
|
unit: Unit of measurement
|
|
source: The scanner that measured this
|
|
score: Normalized score (0-1) if available
|
|
"""
|
|
name: str
|
|
display_name: str
|
|
value: float
|
|
unit: str
|
|
source: str
|
|
score: Optional[float] = None
|
|
|
|
|
|
@dataclass
|
|
class ScannerResult:
|
|
"""
|
|
Result of a scanner execution.
|
|
|
|
Attributes:
|
|
scanner_name: Name of the scanner
|
|
status: Execution status
|
|
issues: List of issues found
|
|
metrics: List of metrics measured
|
|
scores: Dictionary of category scores
|
|
raw_data: Original scanner output
|
|
error_message: Error details if failed
|
|
"""
|
|
scanner_name: str
|
|
status: ScannerStatus
|
|
issues: List[IssueData] = field(default_factory=list)
|
|
metrics: List[MetricData] = field(default_factory=list)
|
|
scores: Dict[str, int] = field(default_factory=dict)
|
|
raw_data: Optional[Dict[str, Any]] = None
|
|
error_message: Optional[str] = None
|
|
|
|
|
|
class BaseScanner(ABC):
|
|
"""
|
|
Abstract base class for all scanners.
|
|
|
|
Each scanner implementation must implement the `run` method
|
|
which performs the actual scan and returns a ScannerResult.
|
|
"""
|
|
|
|
name: str = "base"
|
|
|
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
"""
|
|
Initialize the scanner with optional configuration.
|
|
|
|
Args:
|
|
config: Scanner-specific configuration dictionary
|
|
"""
|
|
self.config = config or {}
|
|
self.logger = logging.getLogger(f"scanner.{self.name}")
|
|
|
|
@abstractmethod
|
|
def run(self, url: str) -> ScannerResult:
|
|
"""
|
|
Run the scanner against the given URL.
|
|
|
|
Args:
|
|
url: The URL to scan
|
|
|
|
Returns:
|
|
ScannerResult with findings, metrics, and status
|
|
"""
|
|
pass
|
|
|
|
def is_available(self) -> bool:
|
|
"""
|
|
Check if the scanner service/tool is available.
|
|
|
|
Returns:
|
|
True if the scanner can be used, False otherwise
|
|
"""
|
|
return True
|
|
|
|
def _create_error_result(self, error: Exception) -> ScannerResult:
|
|
"""
|
|
Create a failed result from an exception.
|
|
|
|
Args:
|
|
error: The exception that occurred
|
|
|
|
Returns:
|
|
ScannerResult with failed status
|
|
"""
|
|
self.logger.error(f"Scanner {self.name} failed: {error}")
|
|
return ScannerResult(
|
|
scanner_name=self.name,
|
|
status=ScannerStatus.FAILED,
|
|
error_message=str(error),
|
|
issues=[
|
|
IssueData(
|
|
category="scanner",
|
|
severity="info",
|
|
title=f"{self.name.title()} scan failed",
|
|
description=f"The {self.name} scanner encountered an error: {error}",
|
|
tool=self.name,
|
|
remediation="Check scanner service configuration and availability."
|
|
)
|
|
]
|
|
)
|