:py:mod:`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. Module Contents --------------- 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 .. 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: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] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> ActionOrImpact :classmethod: Deserialize object from dictionary representation. .. 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*. :param value: the value to convert to an AuthorRole :param default: default AuthorRole to use if value is None :return: An author role enum set according to *value* and *default*. :raise 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 :value: () .. py:method:: add_statement(new_statement: Statement) -> None Add a new statement to this VEX document. If *new_statement* has an *_id* set to :const:`None`, the value of the statement *_id* is updated to ``/``. :param new_statement: The statement to add to this VEX document. .. py:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> Document :classmethod: Deserialize object from dictionary representation. .. 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. :return: a new Document instance loaded from the file .. 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 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*. :param value: the value to convert to a Justification :param default: default Justification to use if value is None :return: A justification enum set according to *value* and *default*. :raise 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:property:: id :type: str Return the identifier of this metadata. .. 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:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> Metadata :classmethod: Deserialize object from dictionary representation. .. 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:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> Product :classmethod: Deserialize object from dictionary representation. .. 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:property:: id :type: str Return the identifier of this product. .. py:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. 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*. :param value: the value to convert to a ProductStatus :param default: default ProductStatus to use if value is None :return: A product status enum set according to *value* and *default*. :raise 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:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> Statement :classmethod: Deserialize object from dictionary representation. .. 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:property:: id :type: str | None Return the identifier of this statement metadata. .. py:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> StatementMetadata :classmethod: Deserialize object from dictionary representation. .. 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:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> StatementStatus :classmethod: Deserialize object from dictionary representation. .. 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:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation. .. py:method:: from_dict(obj: dict) -> SubProductId :classmethod: Deserialize object from dictionary representation. .. 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:property:: id :type: str Return the identifier of this vulnerability. .. py:method:: as_dict() -> dict[str, Any] Serialize object to dictionary representation.