anta 1.10.0.dev2__tar.gz → 1.10.0.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/PKG-INFO +1 -1
  2. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/csv_reporter.py +3 -3
  3. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/md_reporter.py +50 -18
  4. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_117.py +2 -2
  5. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_140.py +2 -2
  6. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_142.py +2 -2
  7. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_146.py +1 -1
  8. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/sa_147.py +5 -5
  9. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/PKG-INFO +1 -1
  10. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/pyproject.toml +2 -2
  11. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/LICENSE +0 -0
  12. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/README.md +0 -0
  13. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/__init__.py +0 -0
  14. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/__init__.py +0 -0
  15. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/base.py +0 -0
  16. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/eos_versions.py +0 -0
  17. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/models.py +0 -0
  18. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/optional_commands.py +0 -0
  19. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/platforms.py +0 -0
  20. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/remediation.py +0 -0
  21. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/__init__.py +0 -0
  22. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/reporter/reporting.py +0 -0
  23. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/results.py +0 -0
  24. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_advisory/status.py +0 -0
  25. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_eos/__init__.py +0 -0
  26. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_eos/version.py +0 -0
  27. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/_runner.py +0 -0
  28. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/catalog.py +0 -0
  29. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/__init__.py +0 -0
  30. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/_main.py +0 -0
  31. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/check/__init__.py +0 -0
  32. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/check/commands.py +0 -0
  33. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/console.py +0 -0
  34. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/debug/__init__.py +0 -0
  35. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/debug/commands.py +0 -0
  36. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/debug/utils.py +0 -0
  37. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/exec/__init__.py +0 -0
  38. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/exec/commands.py +0 -0
  39. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/exec/utils.py +0 -0
  40. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/get/__init__.py +0 -0
  41. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/get/commands.py +0 -0
  42. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/get/utils.py +0 -0
  43. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/nrfu/__init__.py +0 -0
  44. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/nrfu/commands.py +0 -0
  45. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/nrfu/utils.py +0 -0
  46. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/psirt.py +0 -0
  47. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/cli/utils.py +0 -0
  48. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/constants.py +0 -0
  49. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/custom_types.py +0 -0
  50. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/decorators.py +0 -0
  51. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/device.py +0 -0
  52. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/__init__.py +0 -0
  53. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/avt.py +0 -0
  54. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/bfd.py +0 -0
  55. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/configuration.py +0 -0
  56. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/connectivity.py +0 -0
  57. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/cvx.py +0 -0
  58. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/evpn.py +0 -0
  59. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/flow_tracking.py +0 -0
  60. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/hardware.py +0 -0
  61. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/interfaces.py +0 -0
  62. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/logging.py +0 -0
  63. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/path_selection.py +0 -0
  64. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/__init__.py +0 -0
  65. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/bgp.py +0 -0
  66. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/generic.py +0 -0
  67. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/isis.py +0 -0
  68. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/routing/ospf.py +0 -0
  69. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/security.py +0 -0
  70. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/services.py +0 -0
  71. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/snmp.py +0 -0
  72. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/stun.py +0 -0
  73. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/system.py +0 -0
  74. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/input_models/vlan.py +0 -0
  75. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/inventory/__init__.py +0 -0
  76. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/inventory/exceptions.py +0 -0
  77. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/inventory/models.py +0 -0
  78. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/logger.py +0 -0
  79. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/models.py +0 -0
  80. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/py.typed +0 -0
  81. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/__init__.py +0 -0
  82. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/csv_reporter.py +0 -0
  83. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/jinja_reporter.py +0 -0
  84. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/md_reporter.py +0 -0
  85. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/reporter/table_reporter.py +0 -0
  86. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/result_manager/__init__.py +0 -0
  87. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/result_manager/models.py +0 -0
  88. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/runner.py +0 -0
  89. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/settings.py +0 -0
  90. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/__init__.py +0 -0
  91. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/aaa.py +0 -0
  92. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/advisories/__init__.py +0 -0
  93. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/avt.py +0 -0
  94. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/bfd.py +0 -0
  95. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/configuration.py +0 -0
  96. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/connectivity.py +0 -0
  97. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/cvx.py +0 -0
  98. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/evpn.py +0 -0
  99. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/field_notices.py +0 -0
  100. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/flow_tracking.py +0 -0
  101. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/greent.py +0 -0
  102. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/hardware.py +0 -0
  103. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/interfaces.py +0 -0
  104. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/lanz.py +0 -0
  105. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/logging.py +0 -0
  106. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/mlag.py +0 -0
  107. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/multicast.py +0 -0
  108. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/path_selection.py +0 -0
  109. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/profiles.py +0 -0
  110. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/ptp.py +0 -0
  111. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/__init__.py +0 -0
  112. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/bgp.py +0 -0
  113. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/generic.py +0 -0
  114. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/isis.py +0 -0
  115. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/routing/ospf.py +0 -0
  116. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/security.py +0 -0
  117. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/services.py +0 -0
  118. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/snmp.py +0 -0
  119. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/software.py +0 -0
  120. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/stp.py +0 -0
  121. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/stun.py +0 -0
  122. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/system.py +0 -0
  123. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/vlan.py +0 -0
  124. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tests/vxlan.py +0 -0
  125. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta/tools.py +0 -0
  126. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/SOURCES.txt +0 -0
  127. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/dependency_links.txt +0 -0
  128. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/entry_points.txt +0 -0
  129. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/requires.txt +0 -0
  130. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/anta.egg-info/top_level.txt +0 -0
  131. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/__init__.py +0 -0
  132. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_auth.py +0 -0
  133. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_constants.py +0 -0
  134. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_errors.py +0 -0
  135. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_models.py +0 -0
  136. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/_types.py +0 -0
  137. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/aio_portcheck.py +0 -0
  138. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/config_session.py +0 -0
  139. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/device.py +0 -0
  140. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/asynceapi/errors.py +0 -0
  141. {anta-1.10.0.dev2 → anta-1.10.0.dev3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anta
3
- Version: 1.10.0.dev2
3
+ Version: 1.10.0.dev3
4
4
  Summary: Arista Network Test Automation (ANTA) Framework
5
5
  Author-email: Arista Networks ANTA maintainers <anta-dev@arista.com>
6
6
  Maintainer-email: Arista Networks ANTA maintainers <anta-dev@arista.com>, Khelil Sator <ksator@arista.com>, Matthieu Tâche <mtache@arista.com>, Thomas Grimonet <tgrimonet@arista.com>, Guillaume Mulocher <gmulocher@arista.com>, Carl Baillargeon <carl.baillargeon@arista.com>
@@ -54,7 +54,7 @@ class SecurityAdvisoryReportCsv(ReportCsv):
54
54
  def _format_remediations(result: TestResult | AtomicTestResult) -> str:
55
55
  """Flatten advisory remediation entries into a plain-text CSV cell."""
56
56
  if isinstance(result, (_AdvisoryTestResult, _AdvisoryAtomicTestResult)):
57
- return "\n".join(result.remediations)
57
+ return ReportCsv.split_list_to_txt_list(result.remediations, "\\n")
58
58
  return ""
59
59
 
60
60
  @classmethod
@@ -70,9 +70,9 @@ class SecurityAdvisoryReportCsv(ReportCsv):
70
70
  str(result.name),
71
71
  result.test,
72
72
  cls._format_result(result),
73
- "\n".join(result.messages),
73
+ cls.split_list_to_txt_list(result.messages, "\\n"),
74
74
  cls._format_result(row_result),
75
- "\n".join(row_result.messages),
75
+ cls.split_list_to_txt_list(row_result.messages, "\\n"),
76
76
  cls._format_remediations(row_result),
77
77
  cls._format_remediations(result),
78
78
  f"SA{advisory.sa_number}",
@@ -34,7 +34,7 @@ SEVERITY_ICONS = {
34
34
  RESULT_ICONS = {
35
35
  AntaTestStatus.SUCCESS: "✅",
36
36
  AntaTestStatus.INCONCLUSIVE: "❓",
37
- AntaTestStatus.FAILURE: "",
37
+ AntaTestStatus.FAILURE: "🛑",
38
38
  AntaTestStatus.ERROR: "❗",
39
39
  AntaTestStatus.SKIPPED: "⏭️",
40
40
  AntaTestStatus.UNSET: "-",
@@ -81,9 +81,47 @@ class SecurityAdvisoryMDReportBase(MDReportBase):
81
81
  ]
82
82
  remediations.extend(atomic_remediations)
83
83
  unique_remediations = dict.fromkeys(remediations)
84
+ formatted_remediations = []
85
+ for remediation in unique_remediations:
86
+ vulnerability_ids = self._remediation_vulnerability_ids(result, remediation)
87
+ prefix = f"{', '.join(vulnerability_ids)}: " if vulnerability_ids else ""
88
+ formatted_remediations.append(f"{prefix}{remediation}")
84
89
  if atomic_remediations:
85
- return "<br>".join(f"•&nbsp;{self.safe_markdown(remediation)}" for remediation in unique_remediations)
86
- return self.safe_markdown("\n".join(unique_remediations)) or "-"
90
+ return "<br>".join(f"•&nbsp;{self.safe_markdown(remediation)}" for remediation in formatted_remediations)
91
+ return self.safe_markdown("\n".join(formatted_remediations)) or "-"
92
+
93
+ @staticmethod
94
+ def _remediation_vulnerability_ids(result: TestResult | AtomicTestResult, remediation: str) -> tuple[str, ...]:
95
+ """Return advisory-ordered vulnerability IDs sharing a parent remediation."""
96
+ if not isinstance(result, _AdvisoryTestResult):
97
+ return ()
98
+
99
+ associated_ids: set[str] = set()
100
+ for atomic in result.atomic_results:
101
+ if not isinstance(atomic, _AdvisoryAtomicTestResult) or remediation not in atomic.remediations:
102
+ continue
103
+ if not atomic.vulnerability_ids:
104
+ return ()
105
+ associated_ids.update(atomic.vulnerability_ids)
106
+ return tuple(vulnerability.id for vulnerability in result.advisory.vulnerabilities if vulnerability.id in associated_ids)
107
+
108
+ def format_findings(self, result: TestResult) -> str:
109
+ """Format findings and label associated atomic findings with vulnerability IDs."""
110
+ messages = list(result.messages)
111
+ if isinstance(result, _AdvisoryTestResult):
112
+ for atomic in result.atomic_results:
113
+ vulnerability_ids = _get_atomic_vulnerability_ids(atomic)
114
+ if not vulnerability_ids:
115
+ continue
116
+ prefix = f"{', '.join(vulnerability_ids)}: "
117
+ for message in atomic.messages:
118
+ inherited_message = f"{atomic.description} - {message}"
119
+ try:
120
+ index = messages.index(inherited_message)
121
+ except ValueError:
122
+ continue
123
+ messages[index] = f"{prefix}{message}"
124
+ return self.safe_markdown("<br>".join(messages)) or "-"
87
125
 
88
126
 
89
127
  class ANTASecurityAdvisoryReport(SecurityAdvisoryMDReportBase):
@@ -99,7 +137,7 @@ class ANTASecurityAdvisoryReport(SecurityAdvisoryMDReportBase):
99
137
  toc += "- [Advisory Assessment Summary](#advisory-assessment-summary)\n- [Security Advisory Details](#security-advisory-details)\n"
100
138
  for group in self.groups:
101
139
  advisory = group.advisory
102
- toc += f" - [SA{advisory.sa_number}: {self.safe_markdown(advisory.title)}](#sa-{advisory.sa_number.lower()})\n"
140
+ toc += f" - [{self.safe_markdown(advisory.title)}](#sa-{advisory.sa_number.lower()})\n"
103
141
  toc += "- [Run Overview](#run-overview)"
104
142
  self.mdfile.write(toc + "\n\n")
105
143
 
@@ -112,7 +150,7 @@ class AdvisoryAssessmentSummary(SecurityAdvisoryMDReportBase):
112
150
  "Security Advisory",
113
151
  "Severity",
114
152
  "Devices",
115
- "❌&nbsp;Affected",
153
+ "🛑&nbsp;Affected",
116
154
  "❓&nbsp;Inconclusive",
117
155
  "✅&nbsp;Mitigated",
118
156
  "✅&nbsp;Not Affected",
@@ -130,7 +168,7 @@ class AdvisoryAssessmentSummary(SecurityAdvisoryMDReportBase):
130
168
  """Generate one summary row per security advisory."""
131
169
  for group in self.groups:
132
170
  advisory = group.advisory
133
- advisory_link = f"[SA{advisory.sa_number}: {self.safe_markdown(advisory.title)}](#sa-{advisory.sa_number.lower()})"
171
+ advisory_link = f"[{self.safe_markdown(advisory.title)}](#sa-{advisory.sa_number.lower()})"
134
172
  severity = self.format_severity(group.severity)
135
173
  devices = len({result.name for result in group.results})
136
174
  yield (
@@ -181,7 +219,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
181
219
  heading = self.generate_table_heading(["Device", "Result", "Findings", "Remediations"])
182
220
  self.mdfile.write("\n".join(heading) + "\n")
183
221
  for result in group.results:
184
- findings = self.safe_markdown("<br>".join(result.messages)) or "-"
222
+ findings = self.format_findings(result)
185
223
  remediation = self.format_remediations(result)
186
224
  self.mdfile.write(f"| {self.safe_markdown(result.name)} | {self.format_advisory_result(result)} | {findings} | {remediation} |\n")
187
225
 
@@ -203,7 +241,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
203
241
 
204
242
  def _write_expanded_findings(self, group: AdvisoryResultGroup) -> None:
205
243
  """Write parent advisory results followed by their actual detailed issue results."""
206
- heading = self.generate_table_heading(["Device", "Description", "Vulnerability ID(s)", "Result", "Findings", "Remediations"])
244
+ heading = self.generate_table_heading(["Device", "Vulnerability ID(s)", "Result", "Findings", "Remediations"])
207
245
  self.mdfile.write("\n".join(heading) + "\n")
208
246
  vulnerability_by_id = {vulnerability.id: vulnerability for vulnerability in group.advisory.vulnerabilities}
209
247
  for result in group.results:
@@ -211,20 +249,14 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
211
249
  if has_details:
212
250
  findings = f"**Detailed findings:** {self._atomic_summary(result)}"
213
251
  if result.messages:
214
- findings += f"<br>**Overall evidence:** {self.safe_markdown('<br>'.join(result.messages))}"
252
+ findings += f"<br>**Overall evidence:** {self.format_findings(result)}"
215
253
  else:
216
254
  findings = self.safe_markdown("<br>".join(result.messages)) or "-"
217
- description = self.safe_markdown(result.description) or "-"
218
255
  remediation = self.format_remediations(result)
219
- self.mdfile.write(f"| {self.safe_markdown(result.name)} | {description} | - | {self.format_advisory_result(result)} | {findings} | {remediation} |\n")
256
+ self.mdfile.write(f"| {self.safe_markdown(result.name)} | - | {self.format_advisory_result(result)} | {findings} | {remediation} |\n")
220
257
  for index, atomic in enumerate(result.atomic_results):
221
258
  tree = "└──" if index == len(result.atomic_results) - 1 else "├──"
222
259
  vulnerability_ids = _get_atomic_vulnerability_ids(atomic)
223
- if vulnerability_ids:
224
- atomic_description = "<br>".join(self.safe_markdown(vulnerability_by_id[vulnerability_id].description) for vulnerability_id in vulnerability_ids)
225
- else:
226
- atomic_description = self.safe_markdown(atomic.description) or "-"
227
- description = f"&nbsp;&nbsp;{tree}&nbsp;{atomic_description}"
228
260
  vulnerabilities = (
229
261
  "<br>".join(
230
262
  f"{SEVERITY_ICONS[vulnerability_by_id[vulnerability_id].severity]}&nbsp;{self.safe_markdown(vulnerability_id)}"
@@ -235,7 +267,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
235
267
  )
236
268
  atomic_findings = self.safe_markdown("<br>".join(atomic.messages)) or "-"
237
269
  remediation = self.format_remediations(atomic)
238
- self.mdfile.write(f"| | {description} | {vulnerabilities} | {self.format_advisory_result(atomic)} | {atomic_findings} | {remediation} |\n")
270
+ self.mdfile.write(f"| &nbsp;&nbsp;{tree} | {vulnerabilities} | {self.format_advisory_result(atomic)} | {atomic_findings} | {remediation} |\n")
239
271
 
240
272
  def generate_section(self) -> None:
241
273
  """Generate detailed advisory metadata and findings."""
@@ -244,7 +276,7 @@ class SecurityAdvisoryDetails(SecurityAdvisoryMDReportBase):
244
276
  advisory = group.advisory
245
277
  anchor = f"sa-{advisory.sa_number.lower()}"
246
278
  title = self.safe_markdown(advisory.title)
247
- self.mdfile.write(f'### SA{advisory.sa_number}: {title} <a id="{anchor}"></a>\n\n')
279
+ self.mdfile.write(f'### {title} <a id="{anchor}"></a>\n\n')
248
280
  advisory_severity = group.severity
249
281
  severity = f"**Severity:** {SEVERITY_ICONS[advisory_severity]} {advisory_severity.value.title()}"
250
282
  description = self.safe_markdown(advisory.description)
@@ -62,7 +62,7 @@ ADVISORY = _AdvisoryMetadata(
62
62
  _AdvisoryVulnerability(
63
63
  id="CVE-2025-0936",
64
64
  severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
65
- description=("CVE-2025-0936: gNOI TransferToRemote credential exposure through OpenConfig accounting or tracing."),
65
+ description=("gNOI TransferToRemote credential exposure through OpenConfig accounting or tracing."),
66
66
  ),
67
67
  ),
68
68
  url=("https://www.arista.com/en/support/advisories-notices/security-advisory/21394-security-advisory-0117"),
@@ -289,7 +289,7 @@ class VerifySA117(OptionalCommandsMixin, _AntaAdvisoryTest):
289
289
  )
290
290
  vulnerability = ADVISORY.vulnerabilities[0]
291
291
  atomic_result = self.result.add(
292
- vulnerability.description,
292
+ f"Verify {vulnerability.id}.",
293
293
  vulnerability_ids=(vulnerability.id,),
294
294
  )
295
295
  project_advisory_status(atomic_result, status, message, remediation)
@@ -53,7 +53,7 @@ ADVISORY = _AdvisoryMetadata(
53
53
  _AdvisoryVulnerability(
54
54
  id="CVE-2026-10040",
55
55
  severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
56
- description="CVE-2026-10040: Secure Boot Software Image verification bypass.",
56
+ description="Secure Boot Software Image verification bypass.",
57
57
  ),
58
58
  ),
59
59
  url=("https://www.arista.com/en/support/advisories-notices/security-advisory/24074-security-advisory-0140"),
@@ -163,7 +163,7 @@ class VerifySA140(_AntaAdvisoryTest):
163
163
  )
164
164
  vulnerability = ADVISORY.vulnerabilities[0]
165
165
  atomic_result = self.result.add(
166
- vulnerability.description,
166
+ f"Verify {vulnerability.id}.",
167
167
  vulnerability_ids=(vulnerability.id,),
168
168
  )
169
169
  project_advisory_status(atomic_result, status, message, remediation)
@@ -226,7 +226,7 @@ ADVISORY = _AdvisoryMetadata(
226
226
  _AdvisoryVulnerability(
227
227
  id="CVE-2026-12546",
228
228
  severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
229
- description=("CVE-2026-12546: Next-hop redirection bypass for packets requiring exception handling."),
229
+ description=("Next-hop redirection bypass for packets requiring exception handling."),
230
230
  ),
231
231
  ),
232
232
  url=("https://www.arista.com/en/support/advisories-notices/security-advisory/24111-security-advisory-0142"),
@@ -732,7 +732,7 @@ class VerifySA142(OptionalCommandsMixin, _AntaAdvisoryTest):
732
732
  )
733
733
  vulnerability = ADVISORY.vulnerabilities[0]
734
734
  atomic_result = self.result.add(
735
- vulnerability.description,
735
+ f"Verify {vulnerability.id}.",
736
736
  vulnerability_ids=(vulnerability.id,),
737
737
  )
738
738
  project_advisory_status(atomic_result, status, message, remediation)
@@ -517,7 +517,7 @@ class VerifySA146(OptionalCommandsMixin, _AntaAdvisoryTest):
517
517
  )
518
518
  vulnerability = ADVISORY.vulnerabilities[0]
519
519
  atomic_result = self.result.add(
520
- vulnerability.description,
520
+ f"Verify {vulnerability.id}.",
521
521
  vulnerability_ids=(vulnerability.id,),
522
522
  )
523
523
  project_advisory_status(atomic_result, status, message, remediation)
@@ -61,22 +61,22 @@ ADVISORY = _AdvisoryMetadata(
61
61
  _AdvisoryVulnerability(
62
62
  id="CVE-2026-59995",
63
63
  severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
64
- description="CVE-2026-59995: SFTP client issue when connecting to an untrusted server.",
64
+ description="SFTP client issue when connecting to an untrusted server.",
65
65
  ),
66
66
  _AdvisoryVulnerability(
67
67
  id="CVE-2026-59996",
68
68
  severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
69
- description=("CVE-2026-59996: SCP remote-to-remote client issue involving an untrusted server."),
69
+ description=("SCP remote-to-remote client issue involving an untrusted server."),
70
70
  ),
71
71
  _AdvisoryVulnerability(
72
72
  id="CVE-2026-60001",
73
73
  severity=_AdvisoryVulnerabilitySeverity.MEDIUM,
74
- description="CVE-2026-60001: OpenSSH server issue affecting accepted SSH connections.",
74
+ description="OpenSSH server issue affecting accepted SSH connections.",
75
75
  ),
76
76
  _AdvisoryVulnerability(
77
77
  id="CVE-2026-60002",
78
78
  severity=_AdvisoryVulnerabilitySeverity.CRITICAL,
79
- description=("CVE-2026-60002: SSH client issue when connecting to a malicious or compromised server."),
79
+ description=("SSH client issue when connecting to a malicious or compromised server."),
80
80
  ),
81
81
  ),
82
82
  url=("https://www.arista.com/en/support/advisories-notices/security-advisory/24515-security-advisory-0147"),
@@ -428,7 +428,7 @@ class VerifySA147(OptionalCommandsMixin, _AntaAdvisoryTest):
428
428
  )
429
429
  for vulnerability, (status, message, remediation) in zip(ADVISORY.vulnerabilities, assessments, strict=True):
430
430
  atomic_result = self.result.add(
431
- vulnerability.description,
431
+ f"Verify {vulnerability.id}.",
432
432
  vulnerability_ids=(vulnerability.id,),
433
433
  )
434
434
  project_advisory_status(atomic_result, status, message, remediation)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anta
3
- Version: 1.10.0.dev2
3
+ Version: 1.10.0.dev3
4
4
  Summary: Arista Network Test Automation (ANTA) Framework
5
5
  Author-email: Arista Networks ANTA maintainers <anta-dev@arista.com>
6
6
  Maintainer-email: Arista Networks ANTA maintainers <anta-dev@arista.com>, Khelil Sator <ksator@arista.com>, Matthieu Tâche <mtache@arista.com>, Thomas Grimonet <tgrimonet@arista.com>, Guillaume Mulocher <gmulocher@arista.com>, Carl Baillargeon <carl.baillargeon@arista.com>
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "anta"
8
- version = "v1.10.0.dev2"
8
+ version = "v1.10.0.dev3"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Arista Networks ANTA maintainers", email = "anta-dev@arista.com" }]
11
11
  maintainers = [
@@ -121,7 +121,7 @@ namespaces = false
121
121
  # Version
122
122
  ################################
123
123
  [tool.bumpver]
124
- current_version = "1.10.0.dev2"
124
+ current_version = "1.10.0.dev3"
125
125
  version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
126
126
  commit_message = "bump: Version {old_version} -> {new_version}"
127
127
  commit = true
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes