"""Abstract base class and shared data structures for AYN detectors.""" from __future__ import annotations import logging from abc import ABC, abstractmethod from dataclasses import dataclass, field from pathlib import Path from typing import List, Optional logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Detection result # --------------------------------------------------------------------------- @dataclass class DetectionResult: """A single detection produced by a detector. Attributes ---------- threat_name: Short identifier for the threat (e.g. ``"Trojan.Miner.XMRig"``). threat_type: Category string — ``VIRUS``, ``MALWARE``, ``SPYWARE``, ``MINER``, ``ROOTKIT``, ``HEURISTIC``, etc. severity: One of ``CRITICAL``, ``HIGH``, ``MEDIUM``, ``LOW``. confidence: How confident the detector is in the finding (0–100). details: Human-readable explanation. detector_name: Which detector produced this result. """ threat_name: str threat_type: str severity: str confidence: int details: str detector_name: str # --------------------------------------------------------------------------- # Abstract base # --------------------------------------------------------------------------- class BaseDetector(ABC): """Interface that every AYN detector must implement. Detectors receive a file path (and optionally pre-read content / hash) and return zero or more :class:`DetectionResult` instances. """ # ------------------------------------------------------------------ # Identity # ------------------------------------------------------------------ @property @abstractmethod def name(self) -> str: """Machine-friendly detector identifier.""" ... @property @abstractmethod def description(self) -> str: """One-line human-readable summary.""" ... # ------------------------------------------------------------------ # Detection # ------------------------------------------------------------------ @abstractmethod def detect( self, file_path: str | Path, file_content: Optional[bytes] = None, file_hash: Optional[str] = None, ) -> List[DetectionResult]: """Run detection logic against a single file. Parameters ---------- file_path: Path to the file on disk. file_content: Optional pre-read bytes of the file (avoids double-read). file_hash: Optional pre-computed SHA-256 hex digest. Returns ------- list[DetectionResult] Empty list when the file is clean. """ ... # ------------------------------------------------------------------ # Helpers # ------------------------------------------------------------------ def _read_content( self, file_path: Path, file_content: Optional[bytes], max_bytes: int = 10 * 1024 * 1024, ) -> bytes: """Return *file_content* if provided, otherwise read from disk. Reads at most *max_bytes* to avoid unbounded memory usage. """ if file_content is not None: return file_content with open(file_path, "rb") as fh: return fh.read(max_bytes) def _log(self, msg: str, *args) -> None: logger.info("[%s] " + msg, self.name, *args) def _warn(self, msg: str, *args) -> None: logger.warning("[%s] " + msg, self.name, *args) def _error(self, msg: str, *args) -> None: logger.error("[%s] " + msg, self.name, *args)