TL;DR

  • On 11 September 2026 the first key CRA obligation came into force: reporting an actively exploited vulnerability within 24 hours of becoming aware of it. Most of the remaining obligations apply from 11 December 2027.
  • Annex I leaves four technical obligations that can be automated: an SBOM, a dependency scan on every pull request, a recurring scan of what actually runs at customers, and a separate alert for vulnerabilities with an exploit.
  • On Jenkins this is handled by the vuln-today-jenkins-plugin and one line in the Jenkinsfile: vulnScan file: 'package-lock.json'.
  • The lockfile and sbom.cdx.json in the repository document the components of every version. Git keeps the history, so "what was in the March release" is a single command.
  • The pipeline will not replace a coordinated disclosure policy, a security.txt file or the person who decides after an alert whether this is a CSIRT report.

Before the lawyers wake up

On 11 September 2026 the first key obligation of the CRA came into force: the duty to report actively exploited vulnerabilities within 24 hours. Sooner or later someone from the legal department will come to the team with three questions: which components are in the product version customers run today (and which they ran a year ago), does any of them have a known, exploited vulnerability, and who knows about it.

The good news: the pipeline can answer all three. Below we show how we did it on Jenkins with vuln.today, a Polish vulnerability analysis tool built by our security engineer.

What the CRA requires from software makers

The regulation entered into force on 10 December 2024. Most obligations apply from 11 December 2027, but vulnerability and incident reporting (art. 14) already started on 11 September 2026. A manufacturer who learns that a vulnerability in its product is actively exploited reports it to ENISA and the national CSIRT through the new reporting platform.

The report has three stages:

  • an early warning within 24 hours of becoming aware (what and in which product),
  • a fuller notification within 72 hours (the nature of the vulnerability and the exploit, corrective measures taken or available),
  • a final report 14 days after the fix is made available.

Regardless of the report, the manufacturer informs the customers affected by that particular vulnerability about the problem and the available corrective measures. To be clear: this is not a public announcement about a CVE in your dependencies. The duty covers only a vulnerability that is actively exploited in your product, and the Commission guidelines allow you to limit the details to affected customers when public disclosure would increase the risk. After all, nobody wants to hand attackers information about flaws in their products. Importantly, the duty also covers products placed on the market before 11 December 2027.

24 hours for the early warning from becoming aware of an actively exploited vulnerability. Then 72 hours for the notification and 14 days after the fix for the final report.

Annex I part II describes how a manufacturer has to handle vulnerabilities. After filtering out the purely procedural duties (a CVD policy, a reporting contact, publishing advisories), four remain that can and should be automated.

Four requirements worth automating

CRA requirementWhat it means technicallyHow we do it
A machine-readable SBOM, at least top-level dependencies, kept with the technical documentationA component list for every version customers have had, available on request for yearsLockfile and SBOM in the repository, history in git
Identify and document vulnerabilities in componentsA dependency tree scanLockfile scan on every pull request
Regularly test and review the security of the productA scan of what is deployed, without waiting for a code changeDaily scan of the production branch or tag
Report actively exploited vulnerabilities within 24 hours (art. 14)A KEV or exploit signal must reach a specific person immediatelyA separate Slack channel for vulnerabilities with an exploit

Before anyone closes this text thinking "but we build SaaS, this does not apply to us": the CRA covers products made available on the market, that is software delivered to a customer. Pure SaaS is, as a rule, subject to NIS2 rather than the CRA. But a mobile app, a library, a plugin, an agent or an on-prem installation of such a product - those are covered. Many companies have at least one such thing in their portfolio, and the requirements for dependencies under NIS2 are identical.

Implementation on Jenkins

Everything is built on our Jenkins plugin, vuln-today-jenkins-plugin (source on GitHub), and the vuln.today API. The plugin adds a vulnScan step that sends the given lockfile to the API, receives the list of vulnerabilities and sends notifications. The scan itself runs on the agent that runs the build. The plugin was written with minimalism in mind: agents need no scanners, curl or jq, and the Jenkins controller does not process files from repositories. The controller keeps only what belongs there: credentials and the notification state, that is which CVEs have already been reported for which application and version, so that no duplicates are generated.

One-time configuration:

  1. Install the plugin (.hpi) in Manage Jenkins, Plugins.
  2. Two Secret text credentials: vuln-today-api-key and vuln-today-slack-webhook. Optionally a third one, vuln-today-slack-webhook-critical, for the channel that receives vulnerabilities with an exploit.

Then one line in every project:

pipeline {
  agent any
  stages {
    stage('Dependency Scan') {
      when { changeRequest() }
      steps {
        vulnScan file: 'package-lock.json'
      }
    }
  }
}

We pass the path to the lockfile and nothing else. vuln.today recognises the format on its own (npm, Composer, pip, Go, Maven, Gradle, Cargo, Bundler, as well as SBOMs in CycloneDX and SPDX). The result comes back in a few seconds, the plugin aggregates it per package and sends a notification. This covers the second requirement: every dependency change is checked before it reaches the main branch.

SBOM: a single source of truth, the repository

The first requirement is mostly met before we do anything. A package-lock.json is a machine-readable list of all components, including transitive dependencies, versions and checksums. It lives in the repository and git remembers every version of it. The regulator's question "what was in the version deployed in March 2027" is a plain git show <commit>:package-lock.json. No tags, no separate artefact and no external tool are needed. Git is enough.

The SBOM is listed explicitly in Annex VII as part of the technical documentation, which the manufacturer should keep for 10 years from placing the product on the market or for the support period if that is longer. In practice this comes down to not deleting the repository.

One thing is uncertain for now: the regulation speaks of a "commonly used format", and the Commission may specify it in an implementing act. There is no such act today. The most frequently cited reference is the German BSI guideline TR-03183-2, which points to CycloneDX or SPDX, and it is unclear whether an npm lockfile will be considered sufficient. That is why, next to the lockfile, we keep sbom.cdx.json in the repository, generated in the pipeline on the main branch and committed when it changes:

stage('SBOM') {
  when { branch 'master' }
  steps {
    sh '''
      npm sbom --sbom-format cyclonedx --omit dev > sbom.cdx.json
      git diff --quiet sbom.cdx.json || {
        git commit -m "chore: update SBOM [skip ci]" sbom.cdx.json
        git push origin HEAD:master
      }
    '''
  }
}

The npm sbom command has been built into npm since version 9.5, and for Composer (PHP) there is cyclonedx-php-composer. The SBOM lives next to the lockfile, with the same history and the same commit as the code it describes. Developers do not have to remember it or generate it.

A daily scan of what is deployed

This is the third requirement. New CVEs do not wait for a pull request: a product nobody has touched for six months can get a CVE with an exploit today. That is why the daily scan has to cover what actually runs at customers, not what we are about to release or have just released. In other words, scanning software once at release time and forgetting about it is not enough. It has to be scanned regularly.

In most projects production is the latest build of the main branch. In that case a cron job on that branch is enough:

pipeline {
  agent any
  triggers { cron('H 6 * * *') }
  stages {
    stage('Scan deployed release') {
      steps {
        vulnScan file: 'package-lock.json'
      }
    }
  }
}

If on-prem customers sit on an older version or you maintain several support lines, the same job checks out the right tag and calls vulnScan file: 'package-lock.json', version: TAG. The version parameter gives every version its own state: the same CVE fixed in 3.0.0 still comes back as open for 2.3.1 instead of being swallowed by deduplication.

An alert that tells "update" from "report"

The fourth requirement, the most important one since 11 September. The reporting duty concerns an actively exploited vulnerability, not every one with a high CVSS. A CVE 9.8 with no known exploit is an urgent update. A CVE 6.5 from the CISA KEV catalogue, in a version a customer runs in production, potentially starts the 24-hour clock. If both looked the same on Slack, the second one would drown in the middle of the list because of its lower score, while in practice it matters more legally.

For every vulnerability vuln.today returns not only the CVSS but also epss_score, a kev flag (CISA Known Exploited Vulnerabilities) and a poc flag (a publicly available exploit). Based on these, the plugin splits notifications into two classes:

  • New CVEs in dependencies: the team channel, packages sorted by CVSS, the fixed version, a ready npm install command, links. The same CVE may come back after 7 days as a reminder.
  • An exploited vulnerability or one with an exploit (kev == true or poc == true): a separate channel, no deduplication, with the version name and a mention of the person responsible for the assessment.

The second alert is not a report yet. The fact that a library has a public exploit does not automatically mean our product is vulnerable: it may not use that function, it may have a mitigation. But this is the moment when someone has to make that assessment and document it. The Commission guidelines say it plainly: the manufacturer "becomes aware" only when, after an initial assessment, it has reasonable certainty that a vulnerability in its product is actively exploited. That initial assessment, however, has to be made without delay.

What the pipeline will not solve

The pipeline answers "what do we have" and "what is vulnerable". Three things from Annex I are beyond the scanner's reach:

  • a public contact for reporting vulnerabilities (/.well-known/security.txt) and a coordinated disclosure policy,
  • an advisory template and the place where you publish information about fixed vulnerabilities (Annex I part II point 4 requires this only after the fix is available, so these are ordinary security release notes, not a warning ahead of the patch),
  • a designated person who, after an alert from the critical channel, decides whether this is a CSIRT report, and a place to record the reasoning.

The designated person is the link between the pipeline and the lawyers. We name them so that on the day the clock really starts, nobody asks "who is handling this?".

Summary

The four technical obligations of the CRA come down to one plugin and a few lines in a Jenkinsfile:

  • vulnScan on pull requests: no vulnerable dependency enters the main branch unnoticed.
  • vulnScan on a schedule on the production branch or tag: deployed versions are checked against CVEs published yesterday.
  • The lockfile and sbom.cdx.json in the repository: component documentation for every version, with git history, right at the source.
  • Separate Slack channels: updates in one, potential reports in another, with a responsible person.

Whether you scan with vuln.today, Trivy, Grype or OSV-Scanner, and whether you report to Slack or Mattermost, it is worth checking the same four things: does the scan see the lockfile rather than the manifest; does it run without a code change; does it cover the versions customers have; and does the "exploit available" signal look different from an ordinary CVE. If so, the lawyers can wake up whenever they like. The answers will be waiting for them in the repository and in Jenkins.

Sources

  • Regulation (EU) 2024/2847 (Cyber Resilience Act), text on EUR-Lex: eur-lex.europa.eu
  • CRA, art. 13: obligations of manufacturers, including the SBOM and technical documentation
  • CRA, art. 14: reporting obligations of manufacturers
  • CRA, art. 71: entry into force and application
  • CRA, Annex VII: contents of the technical documentation
  • European Commission: guidelines on the application of the CRA, C(2026) 5252, 27 July 2026
  • ENISA: Single Reporting Platform: enisa.europa.eu
  • BSI TR-03183-2: requirements for SBOMs
  • npm sbom documentation: docs.npmjs.com
  • cyclonedx-php-composer: github.com
  • The vuln-today-jenkins-plugin, source code: github.com/radeksh/vuln-today-jenkins-plugin
  • vuln.today: vuln.today