encoding.vex
============

.. py:module:: encoding.vex

.. autoapi-nested-parse::

   Vulnerabilities Exploitability Exchange specifications.

   This specification package is based on
   https://www.cisa.gov/sites/default/files/2023-04/minimum-requirements-for-vex-508c.pdf.

   The document
   https://www.cisa.gov/sites/default/files/2023-01/VEX_Use_Cases_Aprill2022.pdf
   has also been used for a better understanding of some VEX implementations.



Classes
-------

.. autoapisummary::

   encoding.vex.ActionOrImpact
   encoding.vex.AuthorRole
   encoding.vex.Document
   encoding.vex.Justification
   encoding.vex.Metadata
   encoding.vex.Product
   encoding.vex.ProductId
   encoding.vex.ProductStatus
   encoding.vex.Statement
   encoding.vex.StatementMetadata
   encoding.vex.StatementStatus
   encoding.vex.SubProductId
   encoding.vex.Vulnerability


Module Contents
---------------

.. py:class:: ActionOrImpact(statement: str | None = None, timestamp: datetime.datetime | None = None)

   Bases: :py:obj:`e3.json.JsonData`


   An object to represent an action or impact for a statement status.

   For *status* :attr:`Status.NOT_AFFECTED`, if *justification* is not
   provided, then a VEX statement **MUST** provide an *impact_statement* that
   further explains how or why the listed *product_id* s are *not affected* by
   *vul_id*.

   If *justification* is provided, then a VEX statement **MAY** provide an
   *impact_statement*.

   :ivar str | None statement: The statement of this action or impact.
   :ivar datetime | None timestamp: The time at which the statement has
       been last modified.


   .. py:attribute:: timestamp
      :type:  datetime.datetime | None


   .. py:attribute:: statement
      :type:  str | None
      :value: None



   .. py:method:: __bool__() -> bool

      Check if this action or impact is defined.

      An action or impact without a statement is considered as False.

      :return: **True** if this action or impact has a statement defined to
          something else that :const:`None`, **False** else.



   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> ActionOrImpact
      :classmethod:



.. py:class:: AuthorRole(*args, **kwds)

   Bases: :py:obj:`enum.Enum`


   Role of the document author.

   The author role **MAY** specify the role of the *author*.

   The author role **MAY** use the *category of publisher* roles defined by
   CSAF 2.0:

   :cvar COORDINATOR: indicates individuals or organizations that manage a
       single vendor’s response or multiple vendors’ responses to a
       vulnerability, a security flaw, or an incident. This includes all
       Computer Emergency/Incident Response Teams (CERTs/CIRTs) or agents
       acting on the behalf of a researcher.
   :cvar DISCOVERER: indicates individuals or organizations that find
       vulnerabilities or security weaknesses. This includes all manner of
       researchers.
   :cvar TRANSLATOR: indicates individuals or organizations that translate
       VEX documents. This includes all manner of language translators, also
       those who work for the party issuing the original advisory.
   :cvar OTHER: indicates a catchall for everyone else. Currently, this
       includes editors, reviewers, forwarders, republishers, and miscellaneous
       contributors.
   :cvar USER: indicates anyone using a vendor’s product.
   :cvar VENDOR: indicates developers or maintainers of information system
       products or services. This includes all authoritative product vendors,
       Product Security Incident Response Teams (PSIRTs), and product resellers
       and distributors, including authoritative vendor partners.


   .. py:attribute:: COORDINATOR
      :value: 'coordinator'



   .. py:attribute:: DISCOVERER
      :value: 'discoverer'



   .. py:attribute:: OTHER
      :value: 'other'



   .. py:attribute:: TRANSLATOR
      :value: 'translator'



   .. py:attribute:: USER
      :value: 'user'



   .. py:attribute:: VENDOR
      :value: 'vendor'



   .. py:method:: from_value(value: str | AuthorRole | None, default: AuthorRole | str = OTHER) -> AuthorRole
      :classmethod:


      Create an author role enum from a given *value*.

      :return: An author role enum set according to *value* and *default*.

      :raise: :exc:`python:ValueError` If *value* is not one of the possible
          values of this enumerate, or if *default* has an invalid value
          **and** *value* is :const:`None`.



.. py:class:: Document(metadata: Metadata, statements: list[Statement] | None = None)

   Bases: :py:obj:`e3.json.JsonData`


   Vulnerabilities Exploitability Exchange document.

   A VEX document is a binding of product information, vulnerability
   information, and the relevant status details relating them.

   Minimum data elements of a VEX document must include the VEX metadata,
   product details, vulnerability details, and product status.

   :ivar Metadata metadata: VEX metadata.
   :ivar list[Statement] statements: The list of statements defined for
       this VEX document.


   .. py:attribute:: FORMAT_JSON
      :type:  str
      :value: 'json'



   .. py:attribute:: FORMAT_YAML
      :type:  str
      :value: 'yaml'



   .. py:attribute:: FORMATS
      :type:  tuple


   .. py:attribute:: metadata
      :type:  Metadata


   .. py:attribute:: statements
      :type:  list[Statement]
      :value: []



   .. py:method:: add_statement(new_statement: Statement) -> None

      Add a new statement to this VEX document.

      If *statement* has an *_id* set to :const:`None`, the value of the statement
      *_id* is updated to ``<document id>/<statement vuln id>``.

      :param new_statement: The statement to add to this VEX document.



   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> Document
      :classmethod:



   .. py:method:: from_file(path: pathlib.Path) -> Document
      :classmethod:


      Create a VEX document from a file content.

      The function :func:`yaml.safe_load()` is used to read the file. If the
      file content is JSON, the YAML loader handles it safely and extracts
      data anyway.

      :param path: The path of a VEX document to initialise this document
          with.



   .. py:method:: save(path: pathlib.Path, output_format: str = FORMAT_JSON) -> None

      Save this document to a file with the given format.

      :param path: The path of the saved file.
      :param output_format: The file format. May be any of :attr:`FORMATS`.

      :raise: :exc:`python:ValueError` If *output_format* is not one of the
          possible :attr:`FORMATS`.



   .. py:method:: statement(cve_id: str) -> Statement | None

      Get the statement for given CVE ID.

      If this document does not contain any statement defining a vulnerability
      which ID is *cve_id*, :const:`None` is returned.

      :param cve_id: The ID of the CVE to retrieve in the statements of this
          document.

      :return: A :class:`Statement` object which defines the vulnerability
          *cve_id*, or :const:`None` if such a statement does not exist in this
          document.



.. py:class:: Justification(*args, **kwds)

   Bases: :py:obj:`enum.Enum`


   Justification for a statement status.

   For *status* :attr:`Status.NOT_AFFECTED`, a VEX statement **SHOULD** provide
   *justification*.

   If *justification* is not provided then *impact_statement* **MUST** be
   provided.

   :cvar str COMPONENT_NOT_PRESENT: The vulnerable *subcomponent_id* is not
       included in *product_id*.
   :cvar str INLINE_MITIGATIONS_ALREADY_EXIST: *product_id* includes built-in
       protections or features that prevent exploitation of the vulnerability.
       These built-in protections cannot be subverted by the attacker and
       cannot be configured or disabled by the user. These mitigations
       completely prevent exploitation based on known attack vectors.
   :cvar str NO_JUSTIFICATION: Use to state that there is no justification set
       yet.
   :cvar str VULNERABLE_CODE_CANNOT_BE_CONTROLLED_BY_ADVERSARY: The vulnerable
       code is present and used by *product_id* but cannot be controlled by an
       attacker to exploit the vulnerability.
   :cvar str VULNERABLE_CODE_NOT_IN_EXECUTE_PATH: The vulnerable code (likely
       in *subcomponent_id*) cannot be executed due to the way it is used
       by *product_id*. Typically, this case occurs when *product_id* includes
       the vulnerable code but does not call or otherwise use it.
   :cvar str VULNERABLE_CODE_NOT_PRESENT: The vulnerable *subcomponent_id* is
       included in *product_id* but the vulnerable code is not present.
       Typically, this case occurs when source code is configured or built in
       a way that excludes the vulnerable code.


   .. py:attribute:: COMPONENT_NOT_PRESENT
      :value: 'Component not present'



   .. py:attribute:: INLINE_MITIGATIONS_ALREADY_EXIST
      :value: 'Inline mitigations already exist'



   .. py:attribute:: VULNERABLE_CODE_CANNOT_BE_CONTROLLED_BY_ADVERSARY
      :value: 'Vulnerable code cannot be controlled by adversary'



   .. py:attribute:: VULNERABLE_CODE_NOT_IN_EXECUTE_PATH
      :value: 'Vulnerable code not in execute path'



   .. py:attribute:: VULNERABLE_CODE_NOT_PRESENT
      :value: 'Vulnerable code not present'



   .. py:attribute:: NO_JUSTIFICATION
      :value: 'No justification'



   .. py:method:: __bool__() -> bool

      Check if this justification is defined.

      :return: **False** only if justification is set to
          :attr:`Justification.NO_JUSTIFICATION`, else **True** is returned.



   .. py:method:: from_value(value: str | Justification | None, default: Justification | str = NO_JUSTIFICATION) -> Justification
      :classmethod:


      Create a justification enum from a given *value*.

      :return: A justification enum set according to *value* and *default*.

      :raise: :exc:`python:ValueError` If *value* is not one of the possible
          values of this enumerate, or if *default* has an invalid value
          **and** *value* is :const:`None`.



.. py:class:: Metadata(author: str = AUTHOR, author_role: AuthorRole | str = AUTHOR_ROLE, tooling: str | None = None, version: int = VERSION, _id: str | None = None, created_on: datetime.datetime | None = None, last_updated_on: datetime.datetime | None = None, spec_version: str = SPEC_VERSION)

   Bases: :py:obj:`e3.json.JsonData`


   VEX metadata.

   To the greatest extent possible, VEX metadata is defined and maintained at
   the VEX document level.

   When appropriate and necessary, VEX metadata is defined at the VEX statement
   level.

   VEX document metadata **MAY** be synthesized or derived from VEX statement
   metadata; for example, *doc_time_last_updated* **MUST** be at least as
   recent as the newest *statement_time_last_updated*.

   VEX document metadata **MUST** accurately apply to all contained VEX
   statements.

   Must include: VEX Format Identifier, Identifier string for the VEX
   document, Author, Author role, Timestamp.

   :ivar str author: The author of the VEX document. The *author* is
       responsible for the content of the VEX document. A VEX document **MUST**
       identify the author. To describe tools or other mechanisms used to
       generate VEX content, consider *tooling*.
   :ivar AuthorRole author_role: The role of the other of this document. See
       :class:`AuthorRole`.
   :ivar str | None tooling: Document tooling **MAY** specify tools or
       automated mechanisms that generate VEX documents, VEX statements, or
       other VEX information. Contrast with *author*.
   :ivar int version: The version of a VEX document.
   :ivar str _id: The ID of a VEX document.
   :ivar datetime created_on: The time this VEX document was created at.
   :ivar datetime last_updated_on: The time this VEX document was updated.
   :ivar str spec_version: The VEX requirements version used by the parent
       document. By default, it is set to :attr:`SPEC_VERSION`.


   .. py:attribute:: AUTHOR
      :type:  str
      :value: 'AdaCore'



   .. py:attribute:: AUTHOR_ROLE
      :type:  AuthorRole


   .. py:attribute:: SPEC_VERSION
      :type:  str
      :value: '1.0.0'



   .. py:attribute:: VERSION
      :type:  int
      :value: 1



   .. py:attribute:: author
      :type:  str
      :value: 'AdaCore'



   .. py:attribute:: author_role
      :type:  AuthorRole


   .. py:attribute:: tooling
      :type:  str | None
      :value: None



   .. py:attribute:: version
      :type:  int
      :value: 1



   .. py:attribute:: _id
      :type:  str


   .. py:attribute:: created_on
      :type:  datetime.datetime


   .. py:attribute:: last_updated_on
      :type:  datetime.datetime


   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> Metadata
      :classmethod:



.. py:class:: Product(products: list[ProductId], supplier: str, subcomponents: list[SubProductId] | None = None)

   Bases: :py:obj:`e3.json.JsonData`


   Product details.

   :ivar list[ProductID] products: Product details **MUST** include one or more
       *product_id* and **MAY** include one or more *subcomponent_id*.
   :ivar str supplier: Product details **SHOULD** identify the *supplier* of
       *product_id* or *subcomponent_id*. *supplier* **MUST** clearly indicate
       the *product_id* or *subcomponent_id* to which *supplier* applies. For
       example:

       - [supplier]/[product_id]
       - [supplier]/[subcomponent_id]

   :ivar list[ProductId] | None subcomponents: A VEX statement **MAY** include
       one or more identifiers for subcomponents associated with vulnerability
       details.

       A VEX statement asserts the *status* of *product_id* with respect to
       *vul_id*. A VEX statement **MAY** also convey that *subcomponent_id* is
       included in *product_id*. A common VEX use case is to convey that
       *subcomponent_id* is **affected** by *vul_id* while *product_id* is
       **not_affected** by *vul_id*.

       *subcomponent_id* **MAY** be derived from *product_id*, particularly if
       *product_id* is associated with SBOM or other references that convey
       dependencies.

       *subcomponent_id* **MAY** be derived from *vul_id* or *vul_description*.


   .. py:attribute:: products
      :type:  list[ProductId]


   .. py:attribute:: subcomponents
      :type:  list[SubProductId] | None
      :value: None



   .. py:attribute:: supplier
      :type:  str


   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> Product
      :classmethod:



   .. py:method:: subcomponent(_id: str, version: str) -> SubProductId | None

      Get a subcomponent with given ID if any.

      :param _id: The ID of the subcomponent to look for.
      :param version: The version of the subcomponent to look for.

      :return: A matching subcomponent ID, or :const:`None` if no such subcomponent
          could be found.



.. py:class:: ProductId(_id: str, version: str)

   Bases: :py:obj:`e3.json.JsonData`


   VEX statement product identifier.

   The specifications say:

       *product_id* **MUST** identify the product or component that *vul_id*
       and *status* applies to.

       *product_id* **MAY** specify a set of products or components and
       **MUST** specify at least one of:

       - *subcomponent_id*
       - A component (often a subcomponent of a product)
       - A product, for example, a final good assembled
       - A set of products or components, for example, a product line or family
       - A supplier (indicating the set of all products or components from the
         supplier)

   :ivar str _id: The ID of the product.
   :ivar str version: Version, or version range of the product.


   .. py:attribute:: _id


   .. py:attribute:: version


   .. py:method:: as_dict() -> dict[str, Any]


.. py:class:: ProductStatus(*args, **kwds)

   Bases: :py:obj:`enum.Enum`


   Status information about a vulnerability in that product.

   :cvar NOT_AFFECTED: No remediation is required regarding this vulnerability.
   :cvar AFFECTED: Actions are recommended to remediate or address this
       vulnerability.
   :cvar FIXED: These product versions contain a fix for the vulnerability.
   :cvar UNDER_INVESTIGATION: It is not yet known whether these product
       versions are affected by the vulnerability. An update will be provided
       in a later release.


   .. py:attribute:: NOT_AFFECTED
      :value: 'Not affected'



   .. py:attribute:: AFFECTED
      :value: 'Affected'



   .. py:attribute:: FIXED
      :value: 'Fixed'



   .. py:attribute:: UNDER_INVESTIGATION
      :value: 'Under investigation'



   .. py:method:: from_value(value: str | ProductStatus | None, default: ProductStatus | str = UNDER_INVESTIGATION) -> ProductStatus
      :classmethod:


      Create a product status enum from a given *value*.

      :return: A product status enum set according to *value* and *default*.

      :raise: :exc:`python:ValueError` If *value* is not one of the possible
          values of this enumerate, or if *default* has an invalid value
          **and** *value* is :const:`None`.



.. py:class:: Statement(metadata: StatementMetadata, status: StatementStatus, vulnerability: Vulnerability, product: Product)

   Bases: :py:obj:`e3.json.JsonData`


   VEX statement.

   A VEX statement is a declaration that **MUST** convey a single *status* that
   applies to a single *vul_id* for one or more *product_id* s.

   A VEX statement **MUST** be logically contained within a VEX document.

   A VEX statement **MUST** exist only within one VEX document, that is, VEX
   statements are logically local to their containing VEX document.

   :ivar Metadata metadata: The metadata defining this VEX statement.
   :ivar Status status: The status of this VEX statement.
   :ivar Vulnerability vulnerability: The details of the unique vulnerability
       defined for this VEX statement.
   :ivar Product product: The details of the products for the given
       vulnerability of this VEX statement.


   .. py:attribute:: metadata
      :type:  StatementMetadata


   .. py:attribute:: status
      :type:  StatementStatus


   .. py:attribute:: vulnerability
      :type:  Vulnerability


   .. py:attribute:: product
      :type:  Product


   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> Statement
      :classmethod:



.. py:class:: StatementMetadata(version: int | None = None, _id: str | None = None, first_issued_on: datetime.datetime | None = None, last_updated_on: datetime.datetime | None = None)

   Bases: :py:obj:`e3.json.JsonData`


   Metadata for a VEX statement.

   To the extent possible, VEX metadata is stored in VEX documents. Certain
   metadata is specific to VEX statements.

   :ivar int version: indicates the version of the VEX statement.

       - A VEX statement **MUST** provide one *version*.
       - *version* **MUST** clearly convey positive incremental change.
       - *version* MUST be incremented when any content within the VEX
         statement changes.
       - *version* **MAY** be derived from or otherwise be related to
         *document_version*.
   :ivar str | None _id: *_id* uniquely identifies a VEX statement within a VEX
       document.

       - A VEX statement **MUST** be able to be specifically referenced within
         a VEX document.
       - A VEX statement **SHOULD** provide one *_id*.
       - *_id* **SHOULD** be created within the *author* and *doc_id*
         namespaces and **MAY** be generated from other VEX information, for
         example, ``author/doc_id/statement_id``.
       - *_id* **MAY** minimally be an index of VEX statements within the scope
         of *doc_id*.
   :ivar datetime first_issued_on: A VEX statement **MUST** provide the date
       and time that the VEX statement was first issued.

       - *first_issued_on* **MAY** be derived from or otherwise related to
         *doc_time_first_issued*.
       - *first_issued_on* **MAY** be derived from or otherwise related to
         *impact_statement_time* or *action_statement_time*.
   :ivar datetime last_updated_on: A VEX statement **MUST** provide the date
       and time that the VEX statement was last modified.

       - *last_updated_on* **MUST** initially be equivalent to
         *first_issued_on*.
       - *last_updated_on* **MAY** be derived from or otherwise related to
         *impact_statement_time* or *action_statement_time*.
       - *last_updated_on* **MUST** be equivalent to or newer than the most
         recent *impact_statement_time* or *action_statement_time*.


   .. py:attribute:: _id
      :type:  str | None
      :value: None



   .. py:attribute:: version
      :type:  int


   .. py:attribute:: first_issued_on
      :type:  datetime.datetime


   .. py:attribute:: last_updated_on
      :type:  datetime.datetime


   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> StatementMetadata
      :classmethod:



.. py:class:: StatementStatus(status: ProductStatus | str | None = ProductStatus.UNDER_INVESTIGATION, impact: ActionOrImpact | None = None, justification: Justification | str | None = Justification.NO_JUSTIFICATION, action: ActionOrImpact | None = None, notes: str | None = None)

   Bases: :py:obj:`e3.json.JsonData`


   Statement status.

   A VEX statement **MUST** provide one *status* that applies to all contained
   *product_id* s with respect to *vul_id*.

   The statement status is made of a product status (not affected,fixed ...),
   and *impact* depending on the *status* and *justification*.

   For **affected* products, an *action* **MUST** be defined which **SHOULD**
   describe actions to remediate or mitigate *vul_id*.

   :ivar ProductStatus status: The current status of this statement.
   :ivar ActionOrImpact impact: For *status*
       :attr:`ProductStatus.NOT_AFFECTED`, if *justification* is not provided,
       then a VEX statement **MUST** provide an *impact statement*  that
       further explains how or why the listed product ids are
       :attr:`ProductStatus.NOT_AFFECTED` by given vulnerability.

       If *justification* is provided, then a VEX statement **MAY** provide an
       *impact statement*.

       An *impact statement* **MAY** include an *impact statement* time, recording
       when the *impact statement* was issued.
   :ivar Justification justification: Justification for the current *status*.
   :ivar ActionOrImpact action: For *status* :attr:`ProductStatus.AFFECTED`, a
       VEX statement **MUST** include one *action statement* that **SHOULD**
       describe actions to remediate or mitigate given vulnerability.

       An *action statement* **MAY** include *action statement time* recording
       when the *action statement* was issued.
   :ivar str | None notes: Status notes **MAY** convey information about how
       *status* was determined and **MAY** reference other VEX information.


   .. py:attribute:: action_statement_time
      :type:  datetime.datetime


   .. py:attribute:: status
      :type:  ProductStatus


   .. py:attribute:: impact
      :type:  ActionOrImpact


   .. py:attribute:: action
      :type:  ActionOrImpact


   .. py:attribute:: justification
      :type:  Justification


   .. py:attribute:: notes
      :type:  str | None
      :value: None



   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> StatementStatus
      :classmethod:



.. py:class:: SubProductId(_id: str, version: str, platforms: list[str], status: StatementStatus | None)

   Bases: :py:obj:`ProductId`


   Sub-product ID.

   :ivar str _id: see :class:`ProductId`
   :ivar str version: see :class:`ProductId`
   :ivar list[str] platforms: The list of platform for this sub-product ID.
   :ivar StatementStatus status: The status of the sub product.


   .. py:attribute:: platforms
      :type:  list[str]


   .. py:attribute:: status
      :type:  StatementStatus | None


   .. py:method:: as_dict() -> dict[str, Any]


   .. py:method:: from_dict(obj: dict) -> SubProductId
      :classmethod:



.. py:class:: Vulnerability(_id: str, component: str, description: str, score: float | None = None, vector: str | None = None, version: str | None = None, source: str | None = None, url: str | None = None)

   Bases: :py:obj:`e3.json.JsonData`


   Statement vulnerability details.

   Vulnerability details identify and provide information about the
   vulnerability in a VEX statement.

   :ivar str _id: Identifies the vulnerability in a VEX statement.

       - A VEX statement **MUST** specify one *_id*.
       - *_id* **SHOULD** use existing, readily available, and well-known
         identifiers such as: CVE, the Global Security Database (GSD), or a
         supplier’s vulnerability identification system. It is expected that
         vulnerability identification systems are external to and maintained
         separately from VEX.
       - *_id* **MAY** be URIs or URLs.
       - *_id* **MAY** be arbitrary and **MAY** be created by the *author*.
   :ivar str description: A VEX statement **MUST** include or reference one
       *description* that corresponds to *_id*.

       *description* **MUST** either be included in the VEX statement or made
       available to VEX consumers (for example, through a URL).
   :ivar str component: The name of the component this vulnerability applies to.
   :ivar float | None score: The base score for this vulnerability as defined
       by https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator and the CVE
       vector. Set to :const:`None` if there is no such computed metric for this
       vulnerability.
   :ivar str | None vector: The CVE score vector are defined by
       https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator. Set to :const:`None` if
       no such vector is defined.
   :ivar str | None version: The version(s) of *component* for which this
       vulnerability is defined.
   :ivar str | None source: The emitter of the above *score* and *vector* if
       any.
   :ivar str | None url: An url to get details on this vulnerability.


   .. py:attribute:: _id
      :type:  str


   .. py:attribute:: component
      :type:  str


   .. py:attribute:: description
      :type:  str


   .. py:attribute:: score
      :type:  float | None
      :value: None



   .. py:attribute:: vector
      :type:  str | None
      :value: None



   .. py:attribute:: version
      :type:  str | None
      :value: None



   .. py:attribute:: source
      :type:  str | None
      :value: None



   .. py:attribute:: url
      :type:  str | None
      :value: None



   .. py:method:: as_dict() -> dict[str, Any]


