File 1112-adds-SBOM-documentation-and-verification-of-sbom.patch of Package erlang

From cb6155b9d919e56f23876c34df8eafee35cb6d0b Mon Sep 17 00:00:00 2001
From: Kiko Fernandez-Reyes <kiko@erlang.org>
Date: Thu, 18 Sep 2025 14:17:46 +0200
Subject: [PATCH 2/5] adds SBOM documentation and verification of sbom

adds SBOM documentation for internal and public audience. explains what
is covered by the sbom, how to remove packages, etc. it also states how
to verify the sbom using sigstore or gh
---
 HOWTO/SBOM.md             | 529 ++++++++++++++++++++++++++++++++++++--
 scripts/license-header.es |   1 +
 system/doc/docs.exs       |   1 +
 system/doc/guides         |   1 +
 system/doc/guides.license |  19 ++
 system/doc/sbom/sbom.md   | 227 ++++++++++++++++
 6 files changed, 759 insertions(+), 19 deletions(-)
 create mode 100644 system/doc/guides.license
 create mode 100644 system/doc/sbom/sbom.md

diff --git a/HOWTO/SBOM.md b/HOWTO/SBOM.md
index 44f7c29283..4f8f6c1286 100644
--- a/HOWTO/SBOM.md
+++ b/HOWTO/SBOM.md
@@ -29,7 +29,7 @@ different use cases, and some examples include checking license compliance,
 dependencies for vulnerabilities using databases such as
 [CVE](https://www.cve.org/) and [OSV](https://osv.dev/), among others.
 
-Erlang/OTP has multiple third-party dependencies. Some are vendored into the 
+Erlang/OTP has multiple third-party dependencies. Some are vendored into the
 source code of Erlang/OTP:
 - pcre (`erts/emulator/pcre`)
 - zlib (`erts/emulator/zlib`)
@@ -38,9 +38,12 @@ source code of Erlang/OTP:
 - zstd (`erts/emulator/zstd`)
 - others
 
-The Erlang/OTP project provides source SBOMs starting with OTP-28. Below we detail
+The Erlang/OTP project provides source SBOMs starting with OTP 28. Below we detail
 the steps necessary to run yourself the generation of the Erlang/OTP source SBOM.
 
+For information about the structure of the source SBOM, please follow
+[Software Bill-of-Materials (SBOM)](`e:system:sbom.md`)).
+
 ## Source Software Bill-of-Materials
 
 Erlang/OTP provides a source SBOM in each release. Here we provide details of
@@ -51,13 +54,22 @@ how to generate yourself an Erlang/OTP source SBOM.
 The simplest way to generate a source SBOM for Erlang/OTP is to use [oss-review-toolkit]() (ORT)
 with the configuration files found in this repo (`.ort/config/config.yml` and `.ort.yml`).
 
-- `.ort/config/config.yml` contains configuration information towards ORT, e.g., 
+- `.ort/config/config.yml` contains configuration information towards ORT, e.g.,
   configuration of the scanner to use, advisor, etc.
 - `.ort.yml` contains configuration information for this specific project, e.g.,
   which files to exclude from scanner, curation of licenses, etc.
 
 To run ORT locally, we detail the steps running ORT from source and from Docker.
 
+**Tip**
+In cases where one needs to test the `.github/scripts/otp-compliance.es` script,
+it is useful to download `scan-result.json` from any pull request in Erlang/OTP.
+This file can be found in `Save ORT Scanner cache` step inside the `Create SBOM` Github task.
+The scanning phase may take 2 h to run locally.
+By downloading a recent scan, one can run generation of the SPDX report immediately,
+and test the results running `.github/scripts/otp-compliance.es sbom otp-info --sbom-file ort/cli/bom.spdx.json --input-file ort/cli/scan-result.json`.
+
+
 #### Steps From Source
 
 1. Install `oss-review-toolkit` (ORT) and `scancode`.
@@ -66,18 +78,18 @@ To run ORT locally, we detail the steps running ORT from source and from Docker.
    ```bash
    ./gradlew cli:run --args="-c .ort/config/config.yml analyze -i . -o . -f JSON --repository-configuration-file=.ort.yml"
    ```
-   
+
 3. Run the ORT scanner (`scancode` is needed):
 
    ```
    ./gradlew cli:run --args="-c .ort/config/config.yml scan -o . -f JSON -i analyzer-result.json"
    ```
-   
+
 4. Generate ORT SPDX report
    ```
    ./gradlew cli:run --args="report -i cli/scan-result.json -o . -f SpdxDocument -O SpdxDocument=outputFileFormats=JSON"
    ```
-   
+
 5. From the Erlang/OTP repo, run the following escript to fix some known issues from the generated SPDX:
 
    ```bash
@@ -87,26 +99,26 @@ To run ORT locally, we detail the steps running ORT from source and from Docker.
 #### Steps From Docker
 
 1. Run the ORT analyzer (some paths may need tweaking depending on where you are) and choose an appropriate version, in this example `51.0.0`:
-   
+
    ```bash
    docker run -v $(PWD):/sbom ghcr.io/oss-review-toolkit/ort:51.0.0 -c .ort/config/config.yml analyze -i . -o . -f JSON --repository-configuration-file=.ort.yml
    ```
-   
+
 2. Run the ORT scanner:
-   
+
    ```bash
    docker run -v $(PWD):/sbom ghcr.io/oss-review-toolkit/ort:51.0.0 -c .ort/config/config.yml scan -o . -f JSON -i analyzer-result.json
    ```
-   
+
 3. Generate ORT SPDX report
 
    ```bash
    docker run -v $(PWD):/sbom ghcr.io/oss-review-toolkit/ort:51.0.0 report -i cli/scan-result.json -o . -f SpdxDocument -O SpdxDocument=outputFileFormats=JSON
    ```
-   
+
 4. From the Erlang/OTP repo, run the following escript to fix some known issues from the generated SPDX,
    and to separate Erlang/OTP applications into their own SPDX Packages:
-   
+
    ```bash
    .github/scripts/otp-compliance.es sbom otp-info --sbom-file ort/cli/bom.spdx.json --input-file ort/cli/scan-result.json
    ```
@@ -140,7 +152,7 @@ some dependencies cannot be known beforehand.
 The escript `otp-compliance.es` detects Erlang/OTP applications using a simple
 heuristic. Anything that contains an `.app.src` will be place in its own SPDX
 Package. To detect the correct version for each Erlang application, you must ensure that
-`application:get_all_key(AppName)` can run without issues. 
+`application:get_all_key(AppName)` can run without issues.
 
 For example, one cannot expect `wx` application to work if the system was not
 build with the required dependencies.
@@ -162,11 +174,11 @@ up the new application, but these files will not be part of their own SPDX Packa
 Delete the code and any remaining `.app.src` files. Make sure that the application was
 not part of a dependency in other `.app.src` files.
 
-Re-run the source SBOM generation steps ([Erlang/OTP source SBOM]). 
+Re-run the source SBOM generation steps ([Erlang/OTP source SBOM]).
 
 ### Update SPDX Vendor Packages
 
-Vendor packages are identified by a JSON `vendor.info` file that contains fields to identify the vendor dependency. 
+Vendor packages are identified by a JSON `vendor.info` file that contains fields to identify the vendor dependency.
 
 Each `vendor.info` file will implicitly generate a [SPDX](https://spdx.dev/) Package (within the source SBOM) to separate vendor libraries from Erlang/OTP applications.
 
@@ -194,8 +206,8 @@ This file may be a list of JSON objects. For simplicity, we document the fields
 ```
 
 Fields summary:
-- `ID`: represents the `id` of the third party using the following format: `<SPDX-TOP-LEVEL-PACKAGE>-<VENDOR-ID>`. 
-        In the SPDX generation, `SPDX-TOP-LEVEL-PACKAGE` indicates the SPDX Package under which this vendor is a part of. For example, we write `erts-asmjit` for the vendor library `asmjit` that is a part of the `erts` SPDX Package. 
+- `ID`: represents the `id` of the third party using the following format: `<SPDX-TOP-LEVEL-PACKAGE>-<VENDOR-ID>`.
+        In the SPDX generation, `SPDX-TOP-LEVEL-PACKAGE` indicates the SPDX Package under which this vendor is a part of. For example, we write `erts-asmjit` for the vendor library `asmjit` that is a part of the `erts` SPDX Package.
         Top-level packages are:
         - `erts`
         - All Erlang apps, e.g., `stdlib`, `ssl`, `common_test`, etc.
@@ -233,7 +245,486 @@ to make sure that the new vendored dependency gets updated as it should.
 
 ### Delete a Vendor Application
 
-Delete the code and any remaining `vendor.info` files. 
-Re-run the source SBOM generation steps ([Erlang/OTP source SBOM]). 
+Delete the code and any remaining `vendor.info` files.
+Re-run the source SBOM generation steps ([Erlang/OTP source SBOM]).
 
 Delete the proper sections in [`renovate.json5`](../renovate.json5).
+
+## VEX
+
+VEX files allow to communicate which vulnerabilities are false positives and which ones are actual vulnerabilities. VEX files are important to explicitly state that some vendor dependencies are (not) vulnerabilities in your software.
+
+Erlang/OTP has chosen to communicate VEX information using the OpenVEX implementation.
+
+### Dependencies
+
+Install `vexctl`, which is written in Go.
+
+#### Installing Go
+
+An easy way to install go on Ubuntu is to type the following:
+
+```bash
+sudo snap install go --classic
+```
+
+and add to your PATH the `snap` apps (e.g., to your `.bashrc`),
+
+```bash
+export PATH=$PATH:/snap/bin
+```
+
+Alternatively, install [Go from source or binaries](https://go.dev/doc/install).
+
+#### Installing vexctl
+
+**Automatic**
+
+```
+go install github.com/openvex/vexctl@latest
+```
+
+**From source**
+
+Download and install `vextctl` as follows
+
+```bash
+git clone https://github.com/openvex/vexctl.git
+cd vexctl
+make
+```
+
+Add to your path the binary,
+
+```bash
+export PATH=$PATH:<your-path-to-vexctl>/vexctl/
+```
+
+### HOW-TO
+
+Erlang/OTP will maintain VEX files for the latests three releases.
+Because of this, Erlang/OTP will always contain the latest information in the `master` branch.
+Any OpenVEX file in other branches is considered outdated.
+
+The OpenVEX files are located in `vex/otp-26.openvex.json`, `vex/otp-27.openvex.json`, and `vex/otp-28.openvex.json` (e.g.). These files are generated from the `make/openvex.table` and the script `.github/scripts/otp-compliance.es`.
+
+- `make/openvex.table` contains all known CVEs on a per release basis, with top-level objects for `otp-XX` branches, where each `otp-XX` object has as value a list of dependencies with their CVE and the status.
+
+  Example:
+
+  ```json
+      "otp-28":
+      [
+          {
+              "pkg:github/openssl/openssl@636dfadc70ce26f2473870570bfd9ec352806b1d" : "CVE-2025-4575",
+              "status": {"not_affected": "vulnerable_code_not_present"}
+          },
+
+          {
+              "pkg:github/PCRE2Project/pcre2@2dce7761b1831fd3f82a9c2bd5476259d945da4d": "OSV-2025-300",
+              "status": {"not_affected": "vulnerable_code_not_present"}
+          },
+          ...
+      ]
+  ```
+
+The `status` corresponds to the possible status from the [OpenVEX specification](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md).
+In case of `not_affected`, a reason must be provided (similar to the [specification](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md)).
+**The `make/openvex.table` is considered to be an append-only structure, where one should not do modifications to existing data nor removal**.
+Changes should be done via `.github/scripts/otp-compliance.es` applied on the `openvex.table`. The main reason is to use
+`openvex.table` as a simple source of truth without boilerplate, since VEX statements can be long due to the way in which one
+must express range versions for a vulnerability.
+
+In the example above, `pkg:github/openssl/openssl@636dfadc70ce26f2473870570bfd9ec352806b1d` corresponds to
+the package URL for the OpenSSL version with commit `636dfadc70ce26f2473870570bfd9ec352806b1d`, which corresponds
+to the version of OpenSSL used in OTP-X. Starting from OTP 28, this information can be found in the corresponding
+`vendor.info` file for OpenSSL (e.g., `/erts/emulator/openssl/vendor.info` in the `sha` field).
+
+### Further Format Details of openvex.table
+
+The file `openvex.table` is a subset of fields of the OpenVEX specification.
+The format is a JSON object that contains OTP VEX statements. A top-level valid field is the
+OTP version key (e.g., `otp-29`), followed by a list of objects. Each JSON object can have the following structure.
+
+- A key with a Purl, which uniquely identifies the application that the statement talks about,
+  and a CVE string as value.
+- A key `status` with value `Status :: "affected" | "fixed" | "under_investigation | "not_affected" | Affected`,
+  where `Affected` is an object explained below.
+- `Affected` is an object that may have the following keys
+  - `affected` with value string that explains mitigation strategies
+  - `fixed` with value where the fix was introduced.
+
+**Example**
+
+Assume the following ficticious case, where we want to report `CVE-2023-48795` on OTP 23.
+
+```json
+{
+  "otp-23": [
+    {
+      "pkg:otp/ssh@4.10.1": "CVE-2023-48795",
+      "status":
+          { "affected": "Mitigation: If strict KEX availability cannot be ensured on both connection sides, affected encryption modes(CHACHA and CBC) can be disabled with standard ssh configuration. This will provide protection against vulnerability, but at a cost of affecting interoperability"
+          }
+    },
+    {
+      "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+      "status": { "affected": "Mitigation message, update to the next release"}
+    }
+  ]
+}
+```
+
+
+### Use Cases
+
+In all the cases explained below, the running of the tool `.github/scripts/otp-compliance.es vex` does not commit changes.
+One has to execute the output commands to introduce changes.
+
+
+**Code Generation**
+
+Erlang/OTP creates releases (e.g., OTP-28) and also divides apps in different versions, which means that one can declare
+vulnerabilities in multiple ways.
+
+Example, should we mention that the `ssh` app in OTP-23 is vulnerable or that `ssh-4.10.1` which released in OTP-23 is vulnerable, or both?
+To help us produce accurate results, Erlang/OTP favours to write the exact application version in which a bug was introduced or detected,
+and the exact app version in which the app was fixed, within its OTP release.
+
+For example, if we place in `openvex.table`:
+
+```json
+{
+  "otp-23": [
+    {
+      "pkg:otp/ssh@4.10.1": "CVE-2023-48795",
+      "status": { "affected": "Mitigation: If strict KEX availability cannot be ensured on both connection sides, affected encryption modes(CHACHA and CBC) can be disabled with standard ssh configuration. This will provide protection against vulnerability, but at a cost of affecting interoperability",
+                  "fixed": ["pkg:otp/ssh@4.11.1.6"]
+                }
+    }
+  ]
+}
+```
+
+and execute the script
+
+```bash
+.github/scripts/otp-compliance.es vex run -b otp-23 | bash
+```
+
+Generates the following VEX commands
+
+```bash
+vexctl add --in-place vex/otp-23.openvex.json --product='pkg:otp/erlang@23.1,pkg:otp/erlang@23.1.1,pkg:otp/erlang@23.1.2,pkg:otp/erlang@23.1.3,pkg:otp/erlang@23.1.4,pkg:otp/erlang@23.1.5,pkg:otp/erlang@23.2,pkg:otp/erlang@23.2.1,pkg:otp/erlang@23.2.2,pkg:otp/erlang@23.2.3,pkg:otp/erlang@23.2.4,pkg:otp/erlang@23.2.5,pkg:otp/erlang@23.2.6,pkg:otp/erlang@23.2.7,pkg:otp/erlang@23.3,pkg:otp/erlang@23.3.1,pkg:otp/erlang@23.3.2,pkg:otp/erlang@23.3.3,pkg:otp/erlang@23.3.4,pkg:otp/erlang@23.3.4.1,pkg:otp/erlang@23.3.4.2,pkg:otp/erlang@23.3.4.3,pkg:otp/erlang@23.3.4.4,pkg:otp/erlang@23.3.4.5,pkg:otp/erlang@23.3.4.6,pkg:otp/erlang@23.3.4.7,pkg:otp/erlang@23.3.4.8,pkg:otp/erlang@23.3.4.9,pkg:otp/erlang@23.3.4.10,pkg:otp/erlang@23.3.4.11,pkg:otp/erlang@23.3.4.12,pkg:otp/erlang@23.3.4.13,pkg:otp/erlang@23.3.4.14,pkg:otp/ssh@4.10.1,pkg:otp/ssh@4.10.2,pkg:otp/ssh@4.10.3,pkg:otp/ssh@4.10.4,pkg:otp/ssh@4.10.5,pkg:otp/ssh@4.10.6,pkg:otp/ssh@4.10.7,pkg:otp/ssh@4.10.8,pkg:otp/ssh@4.11,pkg:otp/ssh@4.11.1,pkg:otp/ssh@4.11.1.1,pkg:otp/ssh@4.11.1.2,pkg:otp/ssh@4.11.1.3,pkg:otp/ssh@4.11.1.4,pkg:otp/ssh@4.11.1.5' --vuln='CVE-2023-48795' --status='affected' --action-statement='Mitigation: If strict KEX availability cannot be ensured on both connection sides, affected encryption modes(CHACHA and CBC) can be disabled with standard ssh configuration. This will provide protection against vulnerability, but at a cost of affecting interoperability'
+
+vexctl add --in-place vex/otp-23.openvex.json --product='pkg:otp/erlang@23.3.4.19,pkg:otp/erlang@23.3.4.18,pkg:otp/erlang@23.3.4.17,pkg:otp/erlang@23.3.4.16,pkg:otp/erlang@23.3.4.15,pkg:otp/ssh@4.11.1.6' --vuln='CVE-2023-48795' --status='fixed'
+```
+
+The first command in the script has figured out the exact OTP versions that are vulnerable from the range of affected and fixed exact versions,
+as well as created the range of `ssh` applications that are affected by the vulnerability.
+
+The second command simply states which OTP application versions are fixed.
+
+Below we continue with how to initialize and use the tool to report various states,
+and show examples for Erlang/OTP applications and third party application on which Erlang/OTP builds upon.
+
+#### Init
+
+This will only be needed once, but if you need to initialize and provide existing known CVEs, you can use `.github/scripts/otp-compliance.es`.
+
+The first time that we generate OpenVEX statements we call `.github/scripts/otp-compliance.es vex init --input-file make/openvex.table -b otp-28`. This init script outputs instructions to execute in the shell, which invokes commands from `vexctl` ([Installation steps here](https://github.com/openvex/vexctl)). You can run and execute the scripts as follows, `.github/scripts/otp-compliance.es vex init --input-file make/openvex.table -b otp-28 | bash` (if you use bash).
+
+The script is idempotent, meaning that running consecutive times the script will not change its input.
+Because of this, you run this command only for a new OTP release, and for coming CVEs you use `.github/scripts/otp-compliance.es vex run ...`.
+This last command will not update the time and assumes that the `otp-XX.openvex.json` exists (because the `init` command must be run first).
+
+**Example for new Release**
+
+To release VEX files for a new release, OTP-29, add the name branch to `make/openvex.table` (assuming there are known CVEs):
+
+```
+{
+    "otp-29": []
+}
+```
+
+Execute the script to create the VEX statements for OTP-29:
+
+```bash
+.github/scripts/otp-compliance.es vex init --input-file make/openvex.table -b otp-29
+```
+
+There are no known vulnerabilities, so this VEX statement can be published as is.
+
+
+#### Add `under_investigation`
+
+For vendor CVEs, it may make sense to communicate with the ecosystem that a CVE for vendor X is under investigation.
+If it is trivial to know whether we are affected, one could skip reporting `under_investigation` and add directly the `fixed`, or `vulnerable` statements.
+
+To update or insert VEX statements for OTP-29, update the `make/openvex.table` and run:
+
+```bash
+.github/scripts/otp-compliance.es vex run --input-file make/openvex.table -b otp-29
+```
+
+The script will output commands to run (similar to a dry-run). Once piped to `bash`, they are executed.
+
+```
+.github/scripts/otp-compliance.es vex run --input-file make/openvex.table -b otp-29 | bash
+```
+
+Add and commit the changes.
+
+**Example**
+
+`make/openvex.table` contains:
+
+```
+{
+    "otp-29": []
+}
+```
+
+Lets assume there is `FIKA-2026-BROD` detected in `zlib`. We can issue an `under_investigation` statement updating the `make/openvex.table`
+
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          }
+    ]
+}
+```
+
+Execute the command below to update the OpenVEX statements.
+
+```bash
+.github/scripts/otp-compliance.es vex run --input-file make/openvex.table -b otp-29 | bash
+```
+
+Erlang/OTP should not issue an `under_investigation` unless it is known that it will take some days to understand if Erlang/OTP is vulnerable to a vendor dependency.
+
+#### Add `not_affected`
+
+If the vulnerability under investigation is a false positive, one can convey this information using OpenVEX statements.
+To do this, one adds a reason for why the vulnerability does not apply. These justifications can be found in the [OpenVEX spec](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-justifications).
+
+**Example**
+
+OTP was investigating the CVE `FIKA-2026-BROD` and found themselves not affected.
+We continue from the example in the previous section, that contained `zlib` with status `under_investigation`:
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          }
+    ]
+}
+```
+
+One can update the `make/openvex.table` with the reason of "code not present", meaning, the component is included
+in OTP but the vulnerable code is not present. It is important to note that any statement written in the table
+should not be updated, the table is append only.
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": {"not_affected": "vulnerable_code_not_present"}
+          }
+    ]
+}
+```
+
+To update the OpenVEX statements, run:
+
+```bash
+.github/scripts/otp-compliance.es vex run --input-file make/openvex.table -b otp-29 | bash
+```
+
+It produces a new entry in the openvex statements for OTP-29 stating that OTP-29 is not vulnerable to the CVE `FIKA-2026-BROD`.
+
+
+#### Add `affected`
+
+When OTP is affected by a CVE, one can communicate this using the `affected` status.
+
+**Example**
+
+OTP was investigating the CVE `FIKA-2026-BROD` and found themselves affected.
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          }
+    ]
+}
+```
+
+One can write then in `make/openvex.table`:
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "affected"
+          }
+    ]
+}
+```
+
+where the version affected is written as part of the package url `pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc`.
+
+Execute the command below to update the OpenVEX statements.
+
+```bash
+.github/scripts/otp-compliance.es vex run --input-file make/openvex.table -b otp-29 | bash
+```
+
+It produces a new entry in the openvex statements for OTP-29.
+One can run multiple times the same statement without introducing each time the same statement.
+(the script makes the operation idempotent).
+
+In some cases, it may be useful to provide additional information to mitigate the vulnerability.
+To specify this, write the `status` value as an object with free text.
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": {"affected" : "do not use this component until there is a fix"}
+          }
+    ]
+}
+```
+ and run the `otp-compliance` script as stated above.
+
+#### Add `fixed`
+
+One can specify that the CVE is fixed in a specific version using the `fixed` keyword in the `make/openvex.table` statements.
+
+**Example**
+
+OTP was affected the CVE `FIKA-2026-BROD`, reported in `make/openvex.table`.
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "affected"
+          }
+    ]
+}
+```
+
+OTP creates an emergency patch to fix this vendor dependency, and states that the package url (product and version)
+`pkg:github/madler/zlib@04f42cecafika2026brod` fixes the vulnerability.
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "affected"
+          },
+          {
+              "pkg:github/madler/zlib@04f42cecafika2026brod": "FIKA-2026-BROD",
+              "status": "fix"
+          },
+    ]
+}
+```
+
+
+Execute the command below to update the OpenVEX statements.
+
+```bash
+.github/scripts/otp-compliance.es vex run --input-file make/openvex.table -b otp-29 | bash
+```
+
+Alternatively, one can write the affected and fixed versions in a single object for OTP applications.
+
+```json
+    "otp-29": [
+          {
+              "pkg:otp/erts@10.3.4": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:otp/erts@10.3.4": "FIKA-2026-BROD",
+              "status": { "affected": "Mitigation message, update to the next release",
+                          "fixed": ["pkg:otp/erts@10.3.20"]}
+          }
+    ]
+```
+
+#### Vendor Statements
+
+Some vendor applications may be tied to the runtime system, such as `openssl` is tied to `erts` and `erl_interface`.
+When there is a CVE towards a third party tied to an Erlang/OTP package (almost always!),
+where Erlang/OTP is not affected (almost all cases of `openssl`), one can write the following,
+where `apps` is a list of applications not affected, started from their package url version
+(`14.2.5.10`) until implicitly their last version in the tree.
+
+```json
+    {
+      "pkg:github/openssl/openssl@0foobar": "CVE-2024-9143",
+      "status": { "not_affected": "vulnerable_code_not_present",
+                  "apps": ["pkg:otp/erts@14.2.5.10"]}
+    },
+```
+
+
+In case of wanting to explicitly state that Erlang/OTP is vulnerable or not to a vendor CVE,
+one can write the following, but the script will not generate range queries due to these
+been at the sha-1 commit hash level.
+
+```json
+{
+    "otp-29": [
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": "under_investigation"
+          },
+          {
+              "pkg:github/madler/zlib@04f42ceca40f73e2978b50e93806c2a18c1281fc": "FIKA-2026-BROD",
+              "status": { "affected": "Mitigation message, update to the next release",
+                          "fixed": ["pkg:github/madler/zlib@04f42thiscommitfixesthecve"]}
+          }
+    ]
+}
+```
diff --git a/scripts/license-header.es b/scripts/license-header.es
index 9b22240fc3..96e94e004a 100755
--- a/scripts/license-header.es
+++ b/scripts/license-header.es
@@ -241,6 +241,7 @@ ci(Opts) ->
                       "**/*.cover",
                       "lib/jinterface/.**",
                       "erts/etc/win32/",
+                      "lib/wx/wxwin-nothrow.m4",
                       "erts/lib_src/yielding_c_fun/lib/simple_c_gc/**",
                       "lib/inets/test/httpd_test_data/**"] ++
                       NoWarnNewFiles,
diff --git a/system/doc/docs.exs b/system/doc/docs.exs
index aac54c3809..a25e7040bb 100644
--- a/system/doc/docs.exs
+++ b/system/doc/docs.exs
@@ -31,6 +31,7 @@
     "system_principles/upgrade.md": [],
     "system_principles/versions.md": [],
     "system_principles/misc.md": [],
+    "sbom/sbom.md": [],
     "embedded/embedded.md": [],
     "getting_started/getting_started.md": [],
     "getting_started/seq_prog.md": [],
diff --git a/system/doc/guides b/system/doc/guides
index 784ff2ba91..4b3e7309b2 100644
--- a/system/doc/guides
+++ b/system/doc/guides
@@ -7,3 +7,4 @@ reference_manual:Erlang Reference Manual
 efficiency_guide:Efficiency Guide
 tutorial:Interoperability Tutorial
 embedded:Embedded Systems User's Guide
+sbom:Software Bill Of Materials
diff --git a/system/doc/guides.license b/system/doc/guides.license
new file mode 100644
index 0000000000..f80cadf75c
--- /dev/null
+++ b/system/doc/guides.license
@@ -0,0 +1,19 @@
+%CopyrightBegin%
+
+SPDX-License-Identifier: Apache-2.0
+
+Copyright Ericsson AB 2024. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+%CopyrightEnd%
diff --git a/system/doc/sbom/sbom.md b/system/doc/sbom/sbom.md
new file mode 100644
index 0000000000..e8b4347c5d
--- /dev/null
+++ b/system/doc/sbom/sbom.md
@@ -0,0 +1,227 @@
+<!--
+%CopyrightBegin%
+
+SPDX-License-Identifier: Apache-2.0
+
+Copyright Ericsson AB 2025. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+%CopyrightEnd%
+-->
+
+# Software Bill Of Materials
+
+[](){: #sbom }
+
+# Software Bill-of-Materials (SBOM)
+
+A Software Bill-of-Materials (SBOM) is a document to share information about the
+software used, dependencies and, essentially, what the software is made of. SBOMs have many
+different use cases, and some examples include verification of licenses, or
+vulnerability scanning using databases such as [CVE](https://www.cve.org/) and [OSV](https://osv.dev/), among others.
+
+Erlang/OTP has multiple third-party dependencies. Some are vendored into the source code of Erlang/OTP:
+- pcre2 (`erts/emulator/pcre`)
+- zlib (`erts/emulator/zlib`)
+- asmjit (`erts/emulator/asmjit`)
+- openssl (`erts/emulator/openssl`)
+- zstd (`erts/emulator/zstd`)
+- others
+
+The Erlang/OTP project provides source SBOMs starting with OTP 28. Below we detail structure of the source SBOM.
+
+## Source SBOM Structure And General Understanding
+
+Erlang/OTP publishes a source SBOM for Erlang/OTP using [SPDX v2.2](https://spdx.github.io/spdx-spec/v2.3/relationships-between-SPDX-elements/) format.
+The source SBOM can be seen as a tree data structure.
+
+- `root`: the root of the tree is found under the key `documentDescribes`.
+  The value is a single item that points to the SPDX `package` that represents the root node from where all packages converge.
+  This root node contains mostly configuration files that do not belong to Erlang/OTP applications nor runtime applications.
+  All SPDX packages (Erlang/OTP apps and runtime, explained later) are under the `packages` key in the SPDX document.
+
+  ```json
+  {
+    "SPDXID": "SPDXRef-DOCUMENT",
+    "creationInfo": { ... },
+    "dataLicense": "CC0-1.0",
+    "documentDescribes": [ "SPDXRef-Project-OTP" ],     <----- ROOT NODE
+    ...,
+
+    "name": "Erlang/OTP",
+      "packages": [
+        {
+          "SPDXID": "SPDXRef-Project-OTP",              <----- DESCRIPTION OF ROOT NODE
+          "downloadLocation": "https://github.com/erlang/otp/releases",
+          "externalRefs": [ { "comment": "",
+                              "referenceCategory": "PACKAGE-MANAGER",
+                              "referenceLocator": "pkg:github/erlang/otp@28.0.1",
+                              "referenceType": "purl"
+                            } ],
+        "filesAnalyzed": true,
+        "hasFiles": [ "SPDXRef-File-1", "SPDXRef-File-2", ...]  <----- FILES IN ROOT NODE
+        },
+        ...      <----- OTHER PACKAGES LIKE ERTS
+      ]
+  }
+  ```
+
+- First level branches from `root` represent Erlang/OTP applications, the runtime system (`erts`), and
+  some vendor build scripts (`SPDXRef-otp-make-install-sh`). As an example, we show below the `erts` package.
+
+  ```json
+  {
+    "SPDXID": "SPDXRef-otp-erts",
+    "downloadLocation": "https://github.com/erlang/otp/releases",
+    "externalRefs": [ { "comment": "Erlang Runtime System",
+                        "referenceCategory": "PACKAGE-MANAGER",
+                        "referenceLocator": "pkg:otp/erts@16.0.1?vcs_url=git+https://github.com/erlang/otp.git",
+                        "referenceType": "purl"}],
+    ...
+    "filesAnalyzed": true,
+    "hasFiles": [ "SPDXRef-File-380", ...],
+    "name": "erts",
+    "packageVerificationCode": { "packageVerificationCodeValue": "2568c51ee8756f36b6173037035ca4f77ed0d00b" },
+    "supplier": "Organization: Ericsson AB",
+    "versionInfo": "16.0.1"
+  },
+  ```
+
+- All Erlang/OTP application SPDX packages are named with the prefix
+  `SPDXRef-otp-<appname>`. `<appname>` represents the name of an Erlang
+  application, where the value is the name of the Erlang application with the
+  underscores `_` dropped, e.g., `common_test` becomes `commontest`.
+  
+- Application packages have at least two sub packages. One for tests and one for docs.
+
+  The documentation and the tests packages add a suffix to the `SPDXRef-otp-<appname>`, namely `documentation` and `test`.
+  We use `stdlib` as a running example to explain the package structure in the SPDX SBOM, where Erlang/OTP applications:
+  - `SPDXRef-otp-stdlib-documentation` contains all documentation about `stdlib`, and
+  - `SPDXRef-otp-stdlib-test` contains all tests about `stdlib`, and `SPDXRef-otp-stdlib` contains the source code of the `stdlib` application.
+
+  ```json
+  {
+      "SPDXID": "SPDXRef-otp-stdlib",                                   <------- stdlib PACKAGE
+      "downloadLocation": "https://github.com/erlang/otp/releases",
+      "externalRefs": [...],
+      "filesAnalyzed": true,
+      "hasFiles": [
+        "SPDXRef-File-9022",
+        "SPDXRef-File-9023",
+        ...
+      ],
+      "name": "stdlib",
+      "packageVerificationCode": { "packageVerificationCodeValue": "29200c1cd7da4a5c015cdafd6f71db538ae0a1c9" },
+      "supplier": "Organization: Ericsson AB",
+      "versionInfo": "7.0.2"
+  },
+  {
+      "SPDXID": "SPDXRef-otp-stdlib-documentation",                         <------- stdlib DOCUMENTATION PACKAGE
+      ...
+      "name": "stdlib-documentation",
+      "packageVerificationCode": { "packageVerificationCodeValue": "ad443de0ca77bf6cbadc35813e0807494949f25c" },
+      "supplier": "Organization: Ericsson AB",
+      "versionInfo": "7.0.2"
+  },
+  {
+      "SPDXID": "SPDXRef-otp-stdlib-test",                                   <------- stdlib TEST PACKAGE
+      ...
+  }
+  ```
+
+- Application packages have the following fields:
+  - `name` which represents the Erlang/OTP application name, e.g., `stdlib`, `erts`, etc,
+    and/or the application name with the suffix `documentation` or `test`, e.g., `stdlib-test` and `stdlib-documentation`.
+  - `copyrightText` includes the copyright of all the files under the given package.
+  - `downloadLocation` specifies where the package can be downloaded from.
+  - `versionInfo` specifies the version of the application, which in case of documentation or test
+     packages, it refers to the top-level application. For example, the `stdlib` package has `versionInfo` equals to `7.0.2` and its corresponding `stdlib-documentation` and `stdlib-test` packages will have the same `versionInfo`, as this is the version of the package.
+  - `licenseInfoFromFiles` contains the list of licenses found in the files that belong to the given package.
+  - for other clarications, please check the SPDX 2.2 standard.
+
+- The application package, application test package, and the application documentation package may all in turn contain one or more vendor packages. An example of this is the package `SPDXRef-otp-erts` who contains other packages, such as `SPDXRef-otp-erts-asmjit`.
+
+- To remove non-needed applications from your SBOM, remove the first level packages (Erlang/OTP applications) that are not needed, including all of their transitive dependencies (other packages reachable from them), as well as all files reachable from these packages. For example, to remove the application `ftp`, one must remove the package `SPDXRef-otp-ftp`, `SPDXRef-otp-ftp-documentation`, and `SPDXRef-otp-ftp-test`, and all the files that they reference (including also [relationship items](https://spdx.github.io/spdx-spec/v2.3/relationships-between-SPDX-elements/)). In most ocassions, you may want to remove the first level Erlang/OTP applications and the keep first level vendor dependencies (identified by comment "vendor package" in the SPDX package). The reason for keeping the first level vendor dependencies is that those include Erlang/OTP building scripts.
+
+Below we show how the `stdlib` packages are linked between them and against the root package, `"SPDXRef-Project-OTP"`.
+In this particular case, `stdlib` does not have any more relationships. But Erlang/OTP applications have dependencies
+in their `app.src` file and these are also captured in the source SBOM in the relationships field. If you remove packages,
+you need to remove relationships that do not exist anymore.
+
+  ```json
+  {
+    "SPDXID": "SPDXRef-DOCUMENT",
+    "creationInfo": { ... },
+    "dataLicense": "CC0-1.0",
+    "documentDescribes": [ "SPDXRef-Project-OTP" ],     <----- ROOT NODE
+    ...,
+
+    "name": "Erlang/OTP",
+    "packages": [ ... ],
+    "relationships": [                                  <----- RELATIONSHIPS, OR, HOW EVERYTHING FITS TOGETHER
+       {
+         "relatedSpdxElement": "SPDXRef-otp-stdlib",
+         "relationshipType": "TEST_OF",                 <----- THESE ARE TESTS
+         "spdxElementId": "SPDXRef-otp-stdlib-test"
+       },
+       {
+         "relatedSpdxElement": "SPDXRef-otp-stdlib",
+         "relationshipType": "DOCUMENTATION_OF",        <----- THESE ARE DOCUMENTS, EXAMPLES, ETC
+         "spdxElementId": "SPDXRef-otp-stdlib-documentation"
+       },
+       {
+         "relatedSpdxElement": "SPDXRef-Project-OTP",
+         "relationshipType": "PACKAGE_OF",              <------ THIS SPECIFIES THAT stdlib IS PART OF PROJECT-OTP
+         "spdxElementId": "SPDXRef-otp-stdlib"
+       },
+       ...
+    ]
+  }
+  ```
+
+
+## Verification Of Source SBOM
+
+In each release, Erlang/OTP releases a source SBOM together with a signed SBOM attestation artifact.
+This gives users the ability to verify the signed artefact.
+
+Below we show how to do this for Erlang/OTP version `28.0.2` using Sigstore `cosign` ([installation](https://github.com/sigstore/cosign)) and/or Github `gh` tools ([installation](https://github.com/cli/cli)).
+
+### Sigstore `cosign`
+
+1. Download the SBOM for `28.0.2`, named `bom.spdx.json` ([here](https://github.com/erlang/otp/releases/download/OTP-28.0.2/bom.spdx.json))
+2. Download the sigstore file, `bom.spdx.json.sigstore` ([here](https://github.com/erlang/otp/releases/download/OTP-28.0.2/bom.spdx.json.sigstore))
+3. Run `cosign` with the following parameters
+
+   ```bash
+   cosign verify-blob-attestation \
+      --bundle "bom.spdx.json.sigstore" \
+      --new-bundle-format \
+      --type "https://spdx.dev/Document/v2.2" \
+      --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
+      --certificate-identity "https://github.com/erlang/otp/.github/workflows/main.yaml@refs/tags/OTP-28.0.2" \
+      "bom.spdx.json"
+   ```
+### Github CLI `gh`
+
+1. Download the SBOM for `28.0.2`, named `bom.spdx.json` ([here](https://github.com/erlang/otp/releases/download/OTP-28.0.2/bom.spdx.json))
+2. Run `gh` with the following parameters
+
+   ```bash
+   gh attestation verify \
+      --predicate-type "https://spdx.dev/Document/v2.2" \
+      --repo "erlang/otp" \
+      --source-ref "refs/tags/OTP-28.0.2" \
+      "bom.spdx.json"
+   ```
-- 
2.51.0

openSUSE Build Service is sponsored by