Skip to main content
  1. Posts/

Creating an SBOM to meet CRA requirements - A Developer's Perspective

·18 mins·

The Cyber Resilience Act (CRA) is an EU regulation that will be fully applicable as of 2027-12-11 with reporting getting into effect in September 2026 already. It forces everyone placing ‘products with digital elements’ onto the EU market to comply with essential cybersecurity requirements during the whole product lifecycle including design, development, production, operation and decommissioning.

One part in complying with the CRA will be to create a Software Bill of Materials (SBOM) as stated in CRA Annex I Part 2 (1). It is also stated that the SBOM needs to be in a machine-readable format.

Some more explanation as to why an SBOM is a good tool for this is explained in Recital (77). I’m unsure if I am allowed to quote, but paraphrased that recital says that an SBOM helps facilitate the vulnerability analysis by informing about the supply chain. And that it is very important to not ship products that contain vulnerable components from third parties.

So in this post I set out to apply CRA to generate an SBOM for a Gradle/Kotlin project, scan it for vulnerabilities and automate the process in a CI/CD pipeline.

I also used and cited the draft standard prEN 40000-1-3 (‘Vulnerability Handling’) in this post. The European Committee for Standardization (CEN) has published these and other draft standards under the EN 40000 series to support compliance with the CRA. However as this document is still a draft (thus the ‘pr’ before the ‘EN’) it might change.

Please note that I am not a lawyer. This is my interpretation and experience of how to practically follow the CRA requirements on creating an SBOM.

Generating the SBOM
#

Luckily modern build systems with centralized dependency management (like Gradle, NuGet, cargo) make the process of generating an SBOM pretty straight forward. However, this process may be significantly more complex for legacy projects or those using decentralized package management (I really feel for you!).

Section [PRE-7-RQ-04] in the prEN 40000-1-3 says that the SBOM needs to be assembled in a machine readable format and Note 7 suggests two possible formats, Software Package Data eXchange (SPDX) or CycloneDX. CycloneDX is a flagship format of the OWASP Foundation and seems to be widely used.

The OWASP Foundation also provides an open source gradle plugin that creates SBOMs in the CycloneDX format. That makes generating CycloneDX SBOMs pretty straightforward for Gradle projects. The OWASP Foundation provides similar plugins for other build systems as well 🎉.

Here is how I use the plugin in my Gradle/Kotlin project.

Setup
#

Add the plugin to your Gradle configuration like this (note that I’m using a version catalog):

1
2
3
4
5
6
//libs.versions.toml
[versions]
cyclonedx = "3.2.4"

[plugins]
cyclonedx-bom = { id = "org.cyclonedx.bom", version.ref = "cyclonedx" }
1
2
3
4
5
6
//build.gradle.kts

plugins {
    ...
    alias(libs.plugins.cyclonedx.bom)
}

The plugin creates a task (cyclonedxBom) that is project wide and generates an aggregated SBOM over all modules. This task is only registered in the project it is applied to (so you need to call apply false if you want to register but not apply it) and it will then aggregate the SBOM for that project and all modules within that project. In an Android project where you typically have one build.gradle.kts at the root level (project level) and at least one at the module level (app level), you might want to apply the plugin at the root level build.gradle.kts and configure it there. Each module then has a cyclonedxDirectBom task registered, that can be configured in the modules build.gradle.kts file. The plugin creates both an aggregated SBOM and one SBOM for each module in the default location: build/reports/cyclonedxand $modulename/build/reports/cyclonedx-direct respectively.

All you need to do now is to run

1
./gradlew cyclonedxBom

Note that this will also create an SBOM in build/reports/cyclonedx-direct that contains the top-level dependencies (i.e. your modules) only. The module level SBOMs and the aggregated SBOM however contain the transitive dependencies as well. Requirements prEN 40000-1-3 [PRE-7-RQ-03] as well as CRA ANNEX I Part 2 (1) state that at least the top-level dependencies need to be present in the SBOM. It is highly recommended to include transitive dependencies when possible. In practice, scanning your SBOM for vulnerabilities will only be truly effective if transitive dependencies are included.

If you are only interested in your modules’ SBOMs you run

1
./gradlew cyclonedxDirectBom

Configuration
#

The standard configuration of the plugin is missing some information that is required according to prEN 40000-1-3, it looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
  "bomFormat" : "CycloneDX",
  "specVersion" : "1.6",
  "serialNumber" : "urn:uuid:6a729d4e-1475-4816-a3a6-afa9872198f5",
  "version" : 1,
  "metadata" : {
    "timestamp" : "2026-05-28T05:50:00Z",
    "tools" : {
      "components" : [
        {
          "type" : "application",
          "author" : "CycloneDX",
          "name" : "cyclonedx-gradle-plugin",
          "version" : "3.2.4"
        }
      ]
    },
    "component" : {
      "type" : "library",
      "bom-ref" : "pkg:maven/unspecified/my-app@unspecified?project_path=%3A",
      "group" : "",
      "name" : "my-app",
      "version" : "unspecified",
      "purl" : "pkg:maven/unspecified/my-app@unspecified?project_path=%3A",
      "modified" : false,
      "externalReferences" : [
        {
          "type" : "vcs",
          "url" : "ssh://git@gitrepoadress"
        }
      ]
    }
  },
  "components" : [
....

According to [PRE-7-RQ-06] the metadata for the SBOM must include the SBOM author (defined as the name of the entity creating the SBOM for a component), the version and a timestamp.

Note 12 mentions that license information can also be included.

We are missing the version and the author as well as license information. We got the timestamp though (l.7). I am assuming that ‘author’ or ’entity’ refers to the legal entity responsible for the product under the CRA.

We can configure those in the build.gradle.kts file that applies the plugin (because the aggregate task can only be configured there) like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import org.cyclonedx.model.ExternalReference
import org.cyclonedx.model.License
import org.cyclonedx.model.LicenseChoice
import org.cyclonedx.model.OrganizationalContact
import org.cyclonedx.model.OrganizationalEntity

group = "my.group" // will be part of the purl and bom-ref
version = "0.0.1"
tasks.cyclonedxBom {
    projectType.set(org.cyclonedx.model.Component.Type.APPLICATION) // pick what applies to your product here
    schemaVersion.set(org.cyclonedx.Version.VERSION_16)

    organizationalEntity =
        OrganizationalEntity().apply {
            name = "My Entity"
            urls = listOf("https://www.example.com")
            addContact(
                OrganizationalContact().apply {
                    name = "Entity Name"
                    email = "email@example.com"
                    phone = "0171-3920099"
                },
            )
        }

    externalReferences.set( // not mandatory
        listOf(
            ExternalReference().apply {
                type = ExternalReference.Type.VCS
                url = "vcsUrl"
            },
            ExternalReference().apply {
                type = ExternalReference.Type.ISSUE_TRACKER
                url = "issueTrackerUrl"
            },
        ),
    )

    licenseChoice =
        LicenseChoice().apply {
            addLicense(
                License().apply {
                    name = "GPL-3.0-or-later"
                    url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
                },
            )
        }
}

After running the task again the SBOM metadata contains all the required fields 🎉:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{
  "bomFormat" : "CycloneDX",
  "specVersion" : "1.6",
  "serialNumber" : "urn:uuid:2763b128-548d-4481-9ce9-91b8c6e1f90e",
  "version" : 1,
  "metadata" : {
    "timestamp" : "2026-05-28T07:48:47Z",
    "tools" : {
      "components" : [
        {
          "type" : "application",
          "author" : "CycloneDX",
          "name" : "cyclonedx-gradle-plugin",
          "version" : "3.2.4"
        }
      ]
    },
    "component" : {
      "type" : "application",
      "bom-ref" : "pkg:maven/my.group/my-app@0.0.1?project_path=%3A",
      "group" : "my.group",
      "name" : "my-app",
      "version" : "0.0.1",
      "purl" : "pkg:maven/my.group/my-app@0.0.1?project_path=%3A",
      "modified" : false,
      "externalReferences" : [
        {
          "type" : "vcs",
          "url" : "vcsUrl"
        },
        {
          "type" : "issue-tracker",
          "url" : "issueTrackerUrl"
        }
      ]
    },
    "manufacturer" : {
      "name" : "My Entity",
      "url" : [
        "https://www.example.com"
      ],
      "contact" : [
        {
          "name" : "Entity Name",
          "email" : "email@example.com",
          "phone" : "0171-3920099"
        }
      ]
    },
    "licenses" : [
      {
        "license" : {
          "name" : "GPL-3.0-or-later",
          "url" : "https://www.gnu.org/licenses/gpl-3.0.en.html"
        }
      }
    ]
  },
  "components" : [
    ...

In Clause 5.3.8.3 of prEN 40000-1-3, it is also specified which information should be included for each dependency. Those include the Software producer, the component name and version, the dependency relationship, unique common identifiers (such as purls), license information and if provided the hash and algorithm for the component.

We cannot configure any of this in the plugin, but let’s look at one of the components in the created SBOM to see if we can find the information:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
      "type" : "library",
      "bom-ref" : "pkg:maven/org.jetbrains.kotlin/kotlin-scripting-jvm@2.3.21?type=jar",
      "group" : "org.jetbrains.kotlin",
      "name" : "kotlin-scripting-jvm",
      "version" : "2.3.21",
      "description" : "Kotlin Scripting Jvm",
      "hashes" : [
        {
          "alg" : "MD5",
          "content" : "28cafeb752d59eb6cb5d14ec9d3615e4"
        },
        ...
      ],
      "licenses" : [
        {
          "license" : {
            "id" : "Apache-2.0",
            "url" : "https://www.apache.org/licenses/LICENSE-2.0"
          }
        }
      ],
      "purl" : "pkg:maven/org.jetbrains.kotlin/kotlin-scripting-jvm@2.3.21?type=jar",
      "modified" : false,
      "externalReferences" : [
        {
          "type" : "vcs",
          "url" : "https://github.com/JetBrains/kotlin"
        }
      ],
      "properties" : [
        {
          "name" : "cdx:maven:package:test",
          "value" : "false"
        }
      ]
    },

We have the component name (l.5) and the component version (l.6). The Software producer is missing, though the component name gives it away in this case. However I didn’t find a single dependency that had the manufacturer field supplied. Either I am missing something or this will soon become a problem.

License information (l.15-22), hashes & algorithms (l.8-14) as well as Unique component identifiers (here in the form of a purl l.23) are there as well. The dependency relationships are listed a little further down in the SBOM in a dependencies array, they look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
,
  "dependencies" : [
    {
      "ref" : "pkg:maven/ch.qos.logback/logback-classic@1.3.14?type=jar",
      "dependsOn" : [
        "pkg:maven/ch.qos.logback/logback-core@1.3.14?type=jar",
        "pkg:maven/org.slf4j/slf4j-api@2.0.7?type=jar"
      ]
    },
    ....

Analyzing Vulnerabilities
#

So that was fun! Now you have a file to show to anyone who is interested. I for my part felt very pleased with myself but did not look into the file in detail for a long time. An SBOM of course is only an artifact, you need to do something with it, for it to become useful (and with ‘you’ I mean ‘I’). And that is of course also what the CRA wants. According to prEN 40000-1-3 the SBOM is used as an input for the vulnerability handling process (5.3).

So as I understand it the SBOM should be used to be aware of all the components your software is made up of in order to act on vulnerabilities in these components when they get known, and not ship anything with vulnerable third party components.

So as a first step I wanted to include a vulnerability check in my CI Pipeline right after the binary had been built and the SBOM was created. I tried some options locally first.

OWASP dependency check Gradle plugin
#

There is an OWASP Gradle plugin that scans your direct and transitive dependencies declared in the build.gradle files for Common Vulnerabilities and Exposures (CVE) against the National Vulnerability Database (NVD). That sounds great in theory, you don’t even need the SBOM, you just need to install a Gradle plugin and it can check all your dependencies directly from the Gradle build file. However, the authors do recommend getting an NVD API key. Otherwise the traffic to the vulnerability database will be limited. I didn’t want to get an API key, ran the task for 20 minutes without result and removed the plugin. It is not possible to choose another vulnerability database for the plugin, otherwise I would have tried to run it against the EUVD, the European Vulnerability Database.

I started reading about the NVD and fell into a rabbit hole. If you want to do the same, I would recommend the Open Source Security Podcast episode The Global Vulnerability Intelligence Platform where Josh Bressers talks to Olle Johansson about the need for a global Vulnerability Database, the fragility of the NVD and other (very interesting) stuff. I can only agree that there is a need for such a Platform and it is sad to hear that the EUVD seems to go their own way and build another non-global Database.

Trivy
#

So I went another way and now use Trivy in the pipeline. Trivy is an open source security scanner developed by Aqua security. The business model seems to be Open-core, meaning some of the (more basic) features are licensed as open source whereas other features are not and can be bought. All-in-all using Trivy was a smooth experience. There are a lot of different things you can do with Trivy, even generating your SBOM by scanning your binary. I used it to scan the SBOM I had just created, I am using Gradle to manage my dependencies, so Gradle seems to be as close to the truth as possible. Also my application would stop working without Gradle, so relying on Gradle a little more seems more reasonable than relying on another third party. I ran their docker container locally to check the SBOM for vulnerabilities like this:

1
docker run -v $(pwd):/root/src aquasec/trivy sbom /root/src/build/reports/cyclonedx/bom.json

Vulnerabilities!
#

And that gave me a list of vulnerable packages in the SBOM. Some of those were top-level dependencies and I was able to update the version. But some of the transitive ones didn’t go away 😱. Here is what the trivy output showed me:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Report Summary

┌────────┬──────┬─────────────────┐
│ Target │ Type │ Vulnerabilities │
├────────┼──────┼─────────────────┤
│ Java   │ jar  │        8└────────┴──────┴─────────────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)


Java (jar)
==========
Total: 8 (UNKNOWN: 0, LOW: 2, MEDIUM: 3, HIGH: 3, CRITICAL: 0)

┌────────────────────────────────┬────────────────┬──────────┬──────────┬───────────────────┬─────────────────────┬──────────────────────────────────────────────────────────────┐
│            Library             │ Vulnerability  │ Severity │  Status  │ Installed Version │    Fixed Version    │                            Title                             │
├────────────────────────────────┼────────────────┼──────────┼──────────┼───────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────┤
│ ch.qos.logback:logback-core    │ CVE-2024-12798 │ MEDIUM   │ fixed    │ 1.3.14            │ 1.5.13, 1.3.15      │ logback-core: arbitrary code execution via                   │
│                                │                │          │          │                   │                     │ JaninoEventEvaluator                                         │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2024-12798                   │
│                                ├────────────────┤          │          │                   ├─────────────────────┼──────────────────────────────────────────────────────────────┤
│                                │ CVE-2025-11226 │          │          │                   │ 1.5.19, 1.3.16      │ ch.qos.logback/logback-core: Conditional abitrary code       │
│                                │                │          │          │                   │                     │ execution in logback-core                                    │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2025-11226                   │
│                                ├────────────────┼──────────┤          │                   ├─────────────────────┼──────────────────────────────────────────────────────────────┤
│                                │ CVE-2024-12801 │ LOW      │          │                   │ 1.5.13, 1.3.15      │ logback-core: SaxEventRecorder vulnerable to Server-Side     │
│                                │                │          │          │                   │                     │ Request Forgery (SSRF) attacks                               │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2024-12801                   │
│                                ├────────────────┤          │          │                   ├─────────────────────┼──────────────────────────────────────────────────────────────┤
│                                │ CVE-2026-1225  │          │          │                   │ 1.5.25              │ ch.qos.logback/logback-core: Malicious logback.xml           │
│                                │                │          │          │                   │                     │ configuration file allows instantiation of arbitrary classes │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2026-1225                    │
├────────────────────────────────┼────────────────┼──────────┤          ├───────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────┤
│ org.apache.kafka:kafka-clients │ CVE-2026-35554 │ HIGH     │          │ 3.9.1             │ 3.9.2, 4.0.2, 4.1.2 │ Apache Kafka Clients: Apache Kafka Clients: Information      │
│                                │                │          │          │                   │                     │ disclosure and data corruption due...                        │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2026-35554                   │
│                                ├────────────────┼──────────┤          │                   ├─────────────────────┼──────────────────────────────────────────────────────────────┤
│                                │ CVE-2026-33558 │ MEDIUM   │          │                   │ 3.9.2, 4.0.1        │ Apache Kafka exposes sensitive information in its DEBUG logs │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2026-33558                   │
├────────────────────────────────┼────────────────┼──────────┤          ├───────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────┤
│ org.lz4:lz4-java               │ CVE-2025-12183 │ HIGH     │          │ 1.8.0             │ 1.8.1               │ lz4-java: lz4-java: Out-of-bounds memory operations lead to  │
│                                │                │          │          │                   │                     │ denial of service and information...                         │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2025-12183                   │
│                                ├────────────────┤          ├──────────┤                   ├─────────────────────┼──────────────────────────────────────────────────────────────┤
│                                │ CVE-2025-66566 │          │ affected │                   │                     │ lz4-java: lz4-java: Information Disclosure via Insufficient  │
│                                │                │          │          │                   │                     │ Output Buffer Clearing                                       │
│                                │                │          │          │                   │                     │ https://avd.aquasec.com/nvd/cve-2025-66566                   │
└────────────────────────────────┴────────────────┴──────────┴──────────┴───────────────────┴─────────────────────┴──────────────────────────────────────────────────────────────┘

The kafka-clients is no top-level dependency, one of the packages I use must be using it.

What to do when you have vulnerable dependencies
#

So I set out to find out who was to blame! I used the Gradle dependencies task (./gradlew dependencies) and it gave me:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
io.kotest:kotest-extensions-testcontainers:6.1.11
|    \--- io.kotest:kotest-extensions-testcontainers-jvm:6.1.11
|         +--- org.jetbrains.kotlin:kotlin-stdlib:2.2.21 -> 2.3.21 (*)
|         +--- io.kotest:kotest-framework-engine:6.1.11 (*)
|         +--- org.testcontainers:testcontainers:2.0.3 -> 2.0.5
|         |    +--- org.slf4j:slf4j-api:1.7.36 -> 2.0.18
|         |    +--- org.apache.commons:commons-compress:1.28.0
|         |    |    +--- commons-codec:commons-codec:1.19.0
|         |    |    +--- commons-io:commons-io:2.20.0
|         |    |    \--- org.apache.commons:commons-lang3:3.18.0
|         |    +--- org.rnorth.duct-tape:duct-tape:1.0.8
|         |    |    \--- org.jetbrains:annotations:17.0.0 -> 23.0.0
|         |    +--- com.github.docker-java:docker-java-api:3.7.1
|         |    |    +--- com.fasterxml.jackson.core:jackson-annotations:2.20
|         |    |    \--- org.slf4j:slf4j-api:1.7.30 -> 2.0.18
|         |    \--- com.github.docker-java:docker-java-transport-zerodep:3.7.1
|         |         +--- com.github.docker-java:docker-java-transport:3.7.1
|         |         +--- org.slf4j:slf4j-api:1.7.36 -> 2.0.18
|         |         \--- net.java.dev.jna:jna:5.18.1
|         +--- org.testcontainers:testcontainers-jdbc:2.0.3 -> 2.0.5
|         |    \--- org.testcontainers:testcontainers-database-commons:2.0.5
|         |         \--- org.testcontainers:testcontainers:2.0.5 (*)
|         +--- org.testcontainers:testcontainers-kafka:2.0.3
|         |    \--- org.testcontainers:testcontainers:2.0.3 -> 2.0.5 (*)
|         +--- com.zaxxer:HikariCP:6.3.3
|         |    \--- org.slf4j:slf4j-api:2.0.17 -> 2.0.18
|         \--- org.apache.kafka:kafka-clients:3.9.1

The Kotest testcontainers extension! I was already on the newest version. The lz4-java dependency is part of kafka-clients and the old logback-core version is used by the ktlint plugin as can be read from the dependencies array in the SBOM (just to give you an alternative way):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
      "ref" : "pkg:maven/ch.qos.logback/logback-classic@1.3.14?type=jar",
      "dependsOn" : [
        "pkg:maven/ch.qos.logback/logback-core@1.3.14?type=jar",
        "pkg:maven/org.slf4j/slf4j-api@2.0.7?type=jar"
      ]
    },
{
      "ref" : "pkg:maven/com.pinterest.ktlint/ktlint-cli@1.5.0?type=jar",
      "dependsOn" : [
        "pkg:maven/ch.qos.logback/logback-classic@1.3.14?type=jar",
        ...
}

So none of the vulnerable packages will be part of the build and released binary. And I started wondering if I should exclude everything from the SBOM that is not part of the binary. According to prEN 40000-1-3 the SBOM only needs to include packages included in the binary. So I changed the cyclonedxDirectBom task configuration to not list dependencies that are not part of the compile and runtime classpath like this:

1
2
3
tasks.cyclonedxDirectBom {
    includeConfigs.set(listOf("runtimeClasspath", "compileClasspath"))
}

BUT I have been reading up on this (or rather I listened to some Podcasts) and the problem with ignoring dependencies that are pulled in for testing/linting etc. is that those can have vulnerabilities that can be exploited, like all those supply chain attacks that extract secrets (like the recent Shai Hulud Attack). If such a vulnerability is disclosed it is valuable to know if you are affected. So having a separate build time SBOM that you can at least check is a solution to this. However you don’t need to fail your pipeline for vulnerabilities found in build time dependencies as they won’t affect the artifact.

Each vulnerability affecting the shipped binary should be reviewed, and there must not be any actively exploited ones. You check what is causing it, update if you can. If the exploit is not applicable to your application (because you are not using the affected functionality for example), you can create a VEX statement based on your generated SBOM and feed that to Trivy.

You can also force Gradle to use a certain/newer version of a transitive dependency. I haven’t tried it and this can easily lead to strange problems and needs to be tested of course.

If the project welcomes PRs, that might be another way to get those nasty dependencies to go away and the last resort might be to either fork the project (if possible) or remove it altogether.

Automation and CI/CD integration
#

prEN 40000-1-3 states that a new SBOM has to be created every time a component is updated or when an old SBOM needs to be corrected. So I created a gitlab CI task to create an SBOM for every build and publish it and then run the trivy check on it. If vulnerabilities with a score of at least HIGH are detected the pipeline fails. It seems to be the unofficial standard to ignore vulnerabilities that are not at least HIGH. I am unsure if that is the right way. This is of course something to discuss within your organization.

Here is my current gitlab ci pipeline:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sbom_job:
  stage: build
  script:
    - ./gradlew cyclonedxBom
    - mv build/reports/cyclonedx/bom.json "sbom-${CI_COMMIT_SHA}.json"
  artifacts:
    name: "sbom-$CI_COMMIT_SHA"
    paths:
      - "sbom-*.json"

trivy_job:
  stage: vulnerabilityCheck
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - SBOM_FILE=$(ls sbom-*.json)
    - trivy sbom --format json --output trivy-report.json $SBOM_FILE
    - trivy sbom --exit-code 1 --severity HIGH,CRITICAL $SBOM_FILE
  artifacts:
    paths:
      - trivy-report.json
    when: always

Next steps
#

In the past I have run into the problem that new CVEs are published for components that are used within an application that was built some days ago, before the CVEs were published. You then need a way to ensure those applications are updated. But how do you know which applications contain the vulnerable components? You use their SBOMs of course! My plan now is to set up the OWASP Dependency Track application, automatically feed SBOMs to it and set up alarms for when that scenario happens. Another problem that needs solving is that of storage and versioning. Old SBOMs must be stored and accessible somewhere.

Legal & Compliance Disclaimer #

This blog post is for informational and educational purposes only and does not constitute legal advice. The Cyber Resilience Act (CRA) is a complex legal framework.

  • Standards vs. Law: The Cyber Resilience Act does not mandate specific SBOM formats or standards (like CycloneDX) or specific documents (like prEN 40000-1-3). These are used here as industry best practices to help demonstrate due diligence. Final standards are still being finalized by CEN.
  • Liability: The author and the blog publisher accept no liability for any compliance failures, fines, or legal issues arising from the use of the methods described in this post.
  • Consult Professionals: For actual CRA compliance, you must consult with qualified legal counsel and security experts to assess your specific product risk profile.
Katharina Damschen
Author
Katharina Damschen