Source code for debsbom.securityscan.scanner

# Copyright (C) 2026 Siemens
#
# SPDX-License-Identifier: MIT

from collections.abc import Iterable, Iterator
from dataclasses import dataclass
import json
import logging
from pathlib import Path
from debian.debian_support import version_compare

from ..dpkg.package import SourcePackage
from enum import IntEnum

logger = logging.getLogger(__name__)


[docs] class CveUrgency(IntEnum): HIGH = 0 MEDIUM = 1 LOW = 2 UNIMPORTANT = 3 END_OF_LIFE = 4 NOT_YET_ASSIGNED = 5 @classmethod def from_string(cls, s: str) -> "CveUrgency": return cls[s.upper().replace(" ", "_").replace("-", "_")] def __str__(self) -> str: return self.name.lower().replace("_", "-")
[docs] class CveStatus(IntEnum): RESOLVED = 0 UNDETERMINED = 1 OPEN = 2 @classmethod def from_string(cls, s: str) -> "CveStatus": return cls[s.upper()] def __str__(self) -> str: return self.name.lower()
[docs] @dataclass class CveEntry: cve: str debianbug: int | None description: str | None status: CveStatus fixed_version: str | None urgency: CveUrgency nodsa: str | None
[docs] @dataclass class ScanResultItem: package: SourcePackage vulnerability: CveEntry affected: bool
class CveTriage: def __init__(self, db, distro): self.db = db self.distro = distro def candidates(self, p: SourcePackage) -> Iterator[CveEntry]: vulns = self.db.get(p.name) if not vulns: return for k, v in vulns.items(): v_distr = v["releases"].get(self.distro) if not v_distr: continue yield CveEntry( cve=k, debianbug=v.get("debianbug"), description=v.get("description"), status=CveStatus.from_string(v_distr["status"]), fixed_version=v_distr.get("fixed_version"), urgency=CveUrgency.from_string(v_distr.get("urgency")), nodsa=v_distr.get("nodsa"), ) @staticmethod def affected_by(p: SourcePackage, c: CveEntry) -> bool: if c.status != CveStatus.RESOLVED or not c.fixed_version: return True elif version_compare(c.fixed_version, str(p.version)) > 0: return True return False
[docs] class SecurityScanner: """ A security scanner that checks source packages against a Debian security database for known vulnerabilities. """ def __init__(self, db: Path, distro: str = "trixie"): with open(db, "r") as f: self.ct = CveTriage(json.load(f), distro=distro) def scan( self, src_pkgs: Iterable[SourcePackage], min_urgency: CveUrgency = CveUrgency.NOT_YET_ASSIGNED, name_filter: str | None = None, ) -> Iterable[ScanResultItem]: for p in src_pkgs: if name_filter and p.name != name_filter: continue vulns = self.ct.candidates(p) for v in vulns: if v.urgency > min_urgency: continue yield ScanResultItem(package=p, vulnerability=v, affected=self.ct.affected_by(p, v))