Add CVE and CWE roles (#11781)

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
Hugo van Kemenade 2024-10-05 20:44:14 +03:00 committed by GitHub
parent f7fa020cc2
commit 09ab6edde5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 196 additions and 0 deletions

View File

@ -59,6 +59,9 @@ Features added
such as :py:mod:`time` or :py:mod:`datetime` in :file:`conf.py`.
See :ref:`the docs <config-copyright>` for further detail.
Patch by Adam Turner.
* #11781: Add roles for referencing CVEs (:rst:role:`:cve: <cve>`)
and CWEs (:rst:role:`:cwe: <cwe>`).
Patch by Hugo van Kemenade.
Bugs fixed
----------

View File

@ -249,6 +249,34 @@ There is also an :rst:role:`index` role to generate index entries.
The following roles generate external links:
.. rst:role:: cve
A reference to a `Common Vulnerabilities and Exposures`_ record.
This generates appropriate index entries.
The text "CVE *number*\ " is generated;
with a link to an online copy of the specified CVE.
You can link to a specific section by using ``:cve:`number#anchor```.
.. _Common Vulnerabilities and Exposures: https://www.cve.org/
For example: :cve:`2020-10735`
.. versionadded:: 8.1
.. rst:role:: cwe
A reference to a `Common Weakness Enumeration`_.
This generates appropriate index entries.
The text "CWE *number*\ " is generated; in the HTML output,
with a link to an online copy of the specified CWE.
You can link to a specific section by using ``:cwe:`number#anchor```.
.. _Common Weakness Enumeration: https://cwe.mitre.org/
For example: :cwe:`787`
.. versionadded:: 8.1
.. rst:role:: pep
A reference to a Python Enhancement Proposal. This generates appropriate

View File

@ -53,6 +53,10 @@ default_settings: dict[str, Any] = {
'image_loading': 'link',
'embed_stylesheet': False,
'cloak_email_addresses': True,
'cve_base_url': 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-',
'cve_references': None,
'cwe_base_url': 'https://cwe.mitre.org/data/definitions/',
'cwe_references': None,
'pep_base_url': 'https://peps.python.org/',
'pep_references': None,
'rfc_base_url': 'https://datatracker.ietf.org/doc/html/',

View File

@ -196,6 +196,94 @@ class AnyXRefRole(XRefRole):
return result
class CVE(ReferenceRole):
def run(self) -> tuple[list[Node], list[system_message]]:
target_id = f'index-{self.env.new_serialno("index")}'
entries = [
(
'single',
_('Common Vulnerabilities and Exposures; CVE %s') % self.target,
target_id,
'',
None,
)
]
index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
self.inliner.document.note_explicit_target(target)
try:
refuri = self.build_uri()
reference = nodes.reference(
'', '', internal=False, refuri=refuri, classes=['cve']
)
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
title = f'CVE {self.title}'
reference += nodes.strong(title, title)
except ValueError:
msg = self.inliner.reporter.error(
__('invalid CVE number %s') % self.target, line=self.lineno
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
return [index, target, reference], []
def build_uri(self) -> str:
base_url = self.inliner.document.settings.cve_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
return f'{base_url}{ret[0]}#{ret[1]}'
return f'{base_url}{ret[0]}'
class CWE(ReferenceRole):
def run(self) -> tuple[list[Node], list[system_message]]:
target_id = f'index-{self.env.new_serialno("index")}'
entries = [
(
'single',
_('Common Weakness Enumeration; CWE %s') % self.target,
target_id,
'',
None,
)
]
index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
self.inliner.document.note_explicit_target(target)
try:
refuri = self.build_uri()
reference = nodes.reference(
'', '', internal=False, refuri=refuri, classes=['cwe']
)
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
title = f'CWE {self.title}'
reference += nodes.strong(title, title)
except ValueError:
msg = self.inliner.reporter.error(
__('invalid CWE number %s') % self.target, line=self.lineno
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
return [index, target, reference], []
def build_uri(self) -> str:
base_url = self.inliner.document.settings.cwe_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
return f'{base_url}{int(ret[0])}.html#{ret[1]}'
return f'{base_url}{int(ret[0])}.html'
class PEP(ReferenceRole):
def run(self) -> tuple[list[Node], list[system_message]]:
target_id = 'index-%s' % self.env.new_serialno('index')
@ -454,12 +542,17 @@ specific_docroles: dict[str, RoleFunction] = {
'download': XRefRole(nodeclass=addnodes.download_reference),
# links to anything
'any': AnyXRefRole(warn_dangling=True),
# external links
'cve': CVE(),
'cwe': CWE(),
'pep': PEP(),
'rfc': RFC(),
# emphasised things
'guilabel': GUILabel(),
'menuselection': MenuSelection(),
'file': EmphasizedLiteral(),
'samp': EmphasizedLiteral(),
# other
'abbr': Abbreviation(),
'manpage': Manpage(),
}

View File

@ -160,6 +160,74 @@ def get_verifier(verify, verify_re):
@pytest.mark.parametrize(
('type', 'rst', 'html_expected', 'latex_expected'),
[
(
# cve role
'verify',
':cve:`2020-10735`',
(
'<p><span class="target" id="index-0"></span><a class="cve reference external" '
'href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735">'
'<strong>CVE 2020-10735</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Vulnerabilities and Exposures@\\spxentry{Common Vulnerabilities and Exposures}'
'!CVE 2020\\sphinxhyphen{}10735@\\spxentry{CVE 2020\\sphinxhyphen{}10735}}'
'\\sphinxhref{https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735}'
'{\\sphinxstylestrong{CVE 2020\\sphinxhyphen{}10735}}'
),
),
(
# cve role with anchor
'verify',
':cve:`2020-10735#id1`',
(
'<p><span class="target" id="index-0"></span><a class="cve reference external" '
'href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735#id1">'
'<strong>CVE 2020-10735#id1</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Vulnerabilities and Exposures@\\spxentry{Common Vulnerabilities and Exposures}'
'!CVE 2020\\sphinxhyphen{}10735\\#id1@\\spxentry{CVE 2020\\sphinxhyphen{}10735\\#id1}}'
'\\sphinxhref{https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735\\#id1}'
'{\\sphinxstylestrong{CVE 2020\\sphinxhyphen{}10735\\#id1}}'
),
),
(
# cwe role
'verify',
':cwe:`787`',
(
'<p><span class="target" id="index-0"></span><a class="cwe reference external" '
'href="https://cwe.mitre.org/data/definitions/787.html">'
'<strong>CWE 787</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Weakness Enumeration@\\spxentry{Common Weakness Enumeration}'
'!CWE 787@\\spxentry{CWE 787}}'
'\\sphinxhref{https://cwe.mitre.org/data/definitions/787.html}'
'{\\sphinxstylestrong{CWE 787}}'
),
),
(
# cwe role with anchor
'verify',
':cwe:`787#id1`',
(
'<p><span class="target" id="index-0"></span><a class="cwe reference external" '
'href="https://cwe.mitre.org/data/definitions/787.html#id1">'
'<strong>CWE 787#id1</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Weakness Enumeration@\\spxentry{Common Weakness Enumeration}'
'!CWE 787\\#id1@\\spxentry{CWE 787\\#id1}}'
'\\sphinxhref{https://cwe.mitre.org/data/definitions/787.html\\#id1}'
'{\\sphinxstylestrong{CWE 787\\#id1}}'
),
),
(
# pep role
'verify',