cwe.py 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. """CWE data the plugin ships: the weaknesses, and the Simplified Mapping entry each belongs to."""
  2. from __future__ import annotations
  3. from dataclasses import dataclass
  4. from pathlib import Path
  5. from typing import TypedDict, cast
  6. from . import strictjson
  7. UNCATEGORIZED = "Uncategorized"
  8. class CategoryNames(TypedDict):
  9. """One entry's names in cwe-categories.json."""
  10. name: str
  11. title: str
  12. class CatalogFile(TypedDict):
  13. """The shape of cwe-categories.json."""
  14. cwe_version: str
  15. categories: dict[str, CategoryNames]
  16. category_of: dict[str, int | None]
  17. @dataclass(frozen=True)
  18. class Category:
  19. """One entry of the view: its CWE number, its common name, its full catalog title."""
  20. number: int
  21. name: str
  22. title: str
  23. @property
  24. def id(self) -> str:
  25. """The entry's CWE id, `CWE-<number>`."""
  26. return f"CWE-{self.number}"
  27. @dataclass(frozen=True)
  28. class Catalog:
  29. """One CWE release: the view's entries, and for every weakness the entry it rolls up to."""
  30. version: str
  31. categories: dict[int, Category]
  32. category_of: dict[int, int | None]
  33. @classmethod
  34. def load(cls, path: Path) -> Catalog:
  35. """The catalog stored at `path`."""
  36. raw = cast("CatalogFile", strictjson.load(path))
  37. return cls(
  38. raw["cwe_version"],
  39. {
  40. int(number): Category(int(number), names["name"], names["title"])
  41. for number, names in raw["categories"].items()
  42. },
  43. {int(number): category for number, category in raw["category_of"].items()},
  44. )
  45. def category(self, cwe: int) -> Category | None:
  46. """The entry `cwe` rolls up to; None when it reaches none or is not a known weakness."""
  47. number = self.category_of.get(cwe)
  48. return self.categories[number] if number is not None else None
  49. def defines(self, cwe: int) -> bool:
  50. """Whether the release defines weakness `cwe` at all; category() is None either way."""
  51. return cwe in self.category_of
  52. def id_number(cwe_id: str) -> int:
  53. """The number of a canonical `CWE-<number>` id, the spelling every validated finding carries."""
  54. return int(cwe_id.removeprefix("CWE-"))
  55. catalog = Catalog.load(Path(__file__).resolve().with_name("cwe-categories.json"))