# Vulnerability response is becoming a 24-hour operational duty

For most software teams, a vulnerability has been a backlog item with a severity label.

A scanner opens a ticket. Someone triages it during the week. A fix ships in the next release, or the one after that. The process was slow, and nobody outside the company was timing it.

That changed on 11 September 2026. From that day, Article 14 of the EU Cyber Resilience Act (CRA) requires manufacturers of products with digital elements to report actively exploited vulnerabilities and severe incidents on a fixed clock. The first report is due within 24 hours of becoming aware. Nothing in the text pauses that clock for the weekend.

The form is the easy part. The hard part is answering, in hours, questions that usually wait for a sprint. What did we ship? Where is it running? Is the vulnerable code reachable? When exactly did we know?

Those are engineering questions. Answering them fast takes SBOMs, reachability triage, machine-readable advisories and an on-call path, and all four are turning into platform capabilities. The next wall is 11 December 2027, when the rest of the CRA applies.

This is an engineering reading of the regulation and the Commission's guidance. It is not legal advice.

## The CRA's first deadline for manufacturers is a response deadline

Most of the CRA, which [entered into force on 10 December 2024](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act), applies from 11 December 2027. Article 71(2) brings one article forward:

> "However, Article 14 shall apply from 11 September 2026 ..."

That ordering is unusual. The Annex I requirements, including vulnerability handling and the SBOM, arrive in 2027. Apart from the rules for conformity assessment bodies, which applied from June 2026, reporting is the first part of the CRA to bind manufacturers. What it asks of them first is a working response process for products that already exist.

### Who owes what, and from when

Which date applies depends on where you sit in the supply chain.

| Role | What applies | Applies from |
| --- | --- | --- |
| Manufacturer, current products | Reporting of exploited vulnerabilities and severe incidents (Art. 14) | 11 September 2026 |
| Manufacturer, legacy or unsupported products | Same reporting; Annex I vulnerability handling does not apply | 11 September 2026 |
| Manufacturer, Annex I duties (including SBOM) | Vulnerability handling for new and substantially modified products | 11 December 2027 |
| Open-source software steward | Reporting to the extent set by Art. 24(3) | 11 December 2027 |
| Maintainer who is neither manufacturer nor steward | No Art. 14 duty | Not applicable |

### Reporting covers products you shipped years ago

Products already in the field are covered. Article 69(3) applies Article 14 to products "placed on the market before 11 December 2027", meaning products first made available on the EU market before that date. Reporting also outlives support. The Commission's [guidance on applying the CRA](https://ec.europa.eu/newsroom/dae/redirection/document/131456) (C(2026) 5252, 27 July 2026, non-binding) says in paragraph 210:

> "unlike the vulnerability handling obligations, which continue only for the length of a product's support period, the reporting obligations continue to apply after a product with digital elements is no longer supported."

For legacy products and products past their support period, you may not owe a patch, but you still owe the report. The Commission's [CRA FAQ](https://ec.europa.eu/newsroom/dae/redirection/document/122331) (FAQ 5.3) admits what that means. For old products, "tooling to scan or run old software versions may no longer exist, build environments for old code may be impossible to recreate ... staff with knowledge of old codebases may have left." Manufacturers are still "required to notify".

Reporting is not retroactive. Guidance paragraph 217 exempts exploitation the manufacturer already knew about before 11 September 2026. A vulnerability known before that date whose exploitation was not known is in scope, so an old CVE in a discontinued product can start a clock tomorrow, given fresh evidence of exploitation.

## Exploited vulnerabilities and severe incidents start the same 24-hour clock

The first trigger is exploitation, and the bar is evidence. Article 14(1) covers "any actively exploited vulnerability contained in the product with digital elements that it becomes aware of", which Article 3(42) defines as "a vulnerability for which there is reliable evidence that a malicious actor has exploited it in a system without permission of the system owner".

The second trigger is a severe incident "having an impact on the security of the product" (Article 14(3)). An incident is severe if it affects the product's ability to protect "the availability, authenticity, integrity or confidentiality of sensitive or important data or functions", or if it "has led or is capable of leading to the introduction or execution of malicious code in a product with digital elements or in the network and information systems of a user" (Article 14(5)). That second clause covers a compromised build pipeline, which comes back in the section on dependencies.

| Stage | Actively exploited vulnerability (Art. 14(2)) | Severe incident (Art. 14(4)) |
| --- | --- | --- |
| Early warning | 24 hours from awareness. Member States where the product is available, where applicable | 24 hours from awareness. Whether unlawful or malicious acts are suspected, plus Member States where available |
| Notification | 72 hours from awareness. Product, general nature of exploit and vulnerability, measures taken and measures users can take, sensitivity of the information | 72 hours from awareness. Nature of the incident, initial assessment, measures taken and measures users can take |
| Final report | No later than 14 days after a corrective or mitigating measure is available. Severity and impact, malicious actor where available, details of the fix | One month after the 72-hour notification is submitted. Severity and impact, likely threat type or root cause, applied and ongoing mitigations |
| On request | Intermediate status reports (Art. 14(6)) | Intermediate status reports (Art. 14(6)) |

The vulnerability final report clock starts when "a corrective or mitigating measure is available", so a published workaround can start the 14-day window before the fixed release ships. The incident final report counts from submission of the 72-hour notification, which means your tooling has to record when you submitted as well as when you knew.

Both later stages open with "unless the relevant information has already been provided". Reports are cumulative, and guidance paragraph 215 expects them to be updated as the investigation advances.

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/e72f46fb-1828-40c6-aea6-12630275bf23.png align="center")

### Twenty-four hours means twenty-four hours

The deadlines are plain hours, "within 24 hours" and "within 72 hours", with no business-day carve-out. The general EU rule on time periods, [Regulation 1182/71](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:31971R1182), counts weekends unless a period is in working days. Whether it governs CRA deadlines is unsettled, and no published guidance we found from the Commission or ENISA (the EU cybersecurity agency) addresses the question. Plan as if nothing pauses the clock.

### Fine exposure stays unsettled until December 2027

Breaching Articles 13 or 14 can cost up to EUR 15 million or 2.5% of worldwide annual turnover, whichever is higher (Article 64(2)), under penalty rules each Member State sets.

Two caveats matter more than the ceiling. Micro and small enterprises cannot be fined for missing the 24-hour early warning, though the 72-hour notification and final report have no such exemption (Article 64(10)(a), as fixed by a [corrigendum of 2 July 2025](https://eur-lex.europa.eu/eli/reg/2024/2847/corrigendum/2025-07-02/oj)). And Commission FAQ 7.1 says the enforcement provisions apply from 11 December 2027. Whether a missed Article 14 deadline before then can be fined has no official answer yet in published guidance.

## The clock starts at awareness, and awareness needs evidence

The CRA never defines "becoming aware", and FAQ 5.1 confirms it does not specify how a manufacturer becomes aware. The Commission guidance fills the gap. A suspicious event should be assessed "immediately", and under paragraph 213 the manufacturer

> "is therefore to be regarded as having become aware when, after such an initial assessment, it has a reasonable degree of certainty that: (i) a vulnerability contained in its product with digital elements is being actively exploited; or (ii) a severe incident has occurred and has led to the security of its product with digital elements being compromised."

Parking a signal in a triage queue is not a safe way to delay the clock. Paragraph 214 says "the emphasis should be on prompt action to carry out the initial assessment", so an awareness time that follows a slow queue is hard to defend.

Signals can come from customers and partners, threat intelligence, researchers, ethical hackers, telemetry, honeypots and "Governmental cybersecurity agencies" (FAQ 5.1). The FAQ adds that this does not require you to carry out those activities or monitor those channels. Once a signal reaches you, though, the assessment is supposed to start.

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/942ec6b0-fa3c-48c1-aaec-abaf31c8bf17.png align="center")

### Record awareness as a timestamped event

With the test defined, the gap is evidence. ENISA's [Single Reporting Platform (SRP) FAQ](https://www.enisa.europa.eu/topics/product-security/single-reporting-platform-srp/frequently-asked-questions) (FAQ 26) describes a field called "Date/Time when you became aware of the incident/actively exploited vulnerability", a timestamp many teams would have to reconstruct from chat logs. Treat awareness like an incident timeline entry:

- first signal time and channel
- start of the initial assessment
- evidence that reached "reasonable degree of certainty"
- decision time and decision owner
- affected products and versions
- Member States where the product is available

If those fields live in a system with immutable timestamps, the SRP field is copy and paste. If they live in a chat thread, it is an argument.

### Compute every deadline from recorded timestamps

Once `aware_at` exists, the deadlines are arithmetic. This function derives the 24-hour and 72-hour deadlines from that one value, takes the fix or submission time for the final report, and works only in UTC.

```typescript
// CRA Article 14 reporting deadlines in UTC. Elapsed hours, weekends included.
export type ReportKind = "actively_exploited_vulnerability" | "severe_incident";

export interface CraDeadlines {
  earlyWarningDue: Date;      // Art. 14(2)(a) / 14(4)(a): 24 hours after awareness
  notificationDue: Date;      // Art. 14(2)(b) / 14(4)(b): 72 hours after awareness
  finalReportDue: Date | null; // Art. 14(2)(c) / 14(4)(c)
}

const HOUR_MS = 60 * 60 * 1000;

function parseInstant(name: string, iso: string): Date {
  // Require an explicit offset or Z so "2026-09-11T18:30" is never read as server local time.
  if (!/(Z|[+-]\d{2}:\d{2})$/.test(iso)) throw new Error(`${name} must include a UTC offset`);
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) throw new Error(`${name} is not a valid timestamp`);
  return d;
}

function addOneMonthUtc(d: Date): Date {
  const y = d.getUTCFullYear();
  const m = d.getUTCMonth() + 1;
  const lastDay = new Date(Date.UTC(y, m + 1, 0)).getUTCDate();
  return new Date(Date.UTC(y, m, Math.min(d.getUTCDate(), lastDay),
    d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()));
}

export function craDeadlines(
  kind: ReportKind,
  awareAt: string,
  opts: { fixAvailableAt?: string; notificationSubmittedAt?: string } = {},
): CraDeadlines {
  const aware = parseInstant("awareAt", awareAt).getTime();
  let finalReportDue: Date | null = null;
  if (kind === "actively_exploited_vulnerability" && opts.fixAvailableAt) {
    finalReportDue = new Date(parseInstant("fixAvailableAt", opts.fixAvailableAt).getTime() + 14 * 24 * HOUR_MS);
  } else if (kind === "severe_incident" && opts.notificationSubmittedAt) {
    finalReportDue = addOneMonthUtc(parseInstant("notificationSubmittedAt", opts.notificationSubmittedAt));
  }
  return {
    earlyWarningDue: new Date(aware + 24 * HOUR_MS),
    notificationDue: new Date(aware + 72 * HOUR_MS),
    finalReportDue,
  };
}
```

Awareness at `2026-09-11T18:30:00Z`, a Friday, gives an early warning due Saturday at 18:30 UTC and a notification due Monday at 18:30 UTC.

Store instants in UTC and render local time only for humans. Awareness at 23:30 Berlin summer time on 24 October 2026 is 21:30 UTC, so the early warning is due at 21:30 UTC on 25 October. The clocks went back overnight, so that is 22:30 in Berlin, an hour before a wall-clock calculation would put it.

The platform's own counter can disagree with yours. ENISA FAQ 26 says the current SRP shows the 72-hour due date as 48 hours after the early warning was submitted, so a notification "may be displayed as overdue before 72 hours have elapsed". The platform has no final report counter for exploited vulnerabilities, and its counters "do not replace the responsibility" of the manufacturer, so the deadline of record is the one computed from your own `aware_at`.

## One submission reaches the coordinator and ENISA together

A manufacturer submits each notification once. It goes to "the electronic notification end-point of the CSIRT designated as coordinator of the Member State where the manufacturers have their main establishment in the Union" and is "simultaneously accessible to ENISA" (Article 14(7)). A CSIRT is a computer security incident response team; each Member State designates one as its coordinator.

Main establishment means where product cybersecurity decisions "are predominantly taken". Manufacturers without an EU main establishment, including those based outside the Union, follow an ordered list: authorized representative, importer, distributor, then "the Member State in which the highest number of users ... are located".

From there, the receiving CSIRT forwards the notification "without delay" to the coordinator CSIRTs of the Member States where you said the product is available (Article 16(2)). That step can be delayed on "justified cybersecurity-related grounds", including during coordinated disclosure. So the Member State list in your early warning decides where the report goes next, and that list comes from distribution data, one more thing engineering has to produce fast.

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/48aeec17-9bf5-47b6-ade8-5c8bbe0f2559.png align="center")

### The reporting platform is a web form with no API

ENISA [launched the Single Reporting Platform](https://www.enisa.europa.eu/index.php/news/the-cra-single-reporting-platform-is-launched) on 11 September 2026 as an "initial operating capability". [On 4 November 2026 it moved to version 1.3](https://www.enisa.europa.eu/topics/product-security/single-reporting-platform-srp). ENISA's FAQ says it is available in English only.

Submissions go through the web interface only. ENISA says notifications "must be submitted through the platform interface" and that "API functionality may be considered in a future phase" (FAQ 15). The same answer notes that organizations "may automate their internal reporting workflows". Generate report content in your own systems, from your own data, and paste it into the form. If an API arrives, the integration becomes a transport change instead of a rewrite.

An outage does not cancel the duty. If the platform is down, ENISA says to wait and submit when it returns (FAQ 25). You may contact your CSIRT directly in the meantime, but the SRP submission is still required.

### Access is personal, and so are drafts

Only named people can submit. Each Assigned Representative needs a personal EU Login account with multi-factor authentication (ENISA FAQ 9), and each manufacturer can have one Primary AR and up to 20 Secondary ARs. One rule in the same FAQ breaks shift handovers:

> "Drafts are stored locally in the individual AR's account and are therefore not visible to other ARs associated with the same manufacturer."

A half-written early warning does not hand over with the shift.

ENISA suggests registering the manufacturer association only when a notification is needed, but EU Login accounts "can be created in advance". Create them now, with MFA, for everyone on the rotation, and keep the working draft in your own system.

## Per-release SBOMs turn the first question into a query

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/6e6a54cc-fbb2-416f-9744-9d3cb1a0a14a.jpg align="center")

*Photo by* [*Taylor Vick*](https://unsplash.com/@tvick) *on* [*Unsplash*](https://unsplash.com/photos/aWslrFhs1w4)

Each question needs the answer to the one before it. Triage needs a match against known vulnerabilities, and a match needs a record of what shipped.

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/21d95f65-8151-42be-b743-0b5edb977c77.png align="center")

The legal duty is narrow. Annex I Part II(1) requires "a software bill of materials in a commonly used and machine-readable format covering at the very least the top-level dependencies of the products". It applies from 11 December 2027, and not to products placed on the market before then unless they are substantially modified. The CRA names no format. The SBOM is not public by default either: it goes to market surveillance authorities, the national regulators that police products on the EU market, "further to a reasoned request" (Annex VII).

It is still the fastest way to answer the first Article 14 question. When an exploited CVE lands, "which releases contain this component" is either a query or an archaeology project.

### Generate the SBOM from the release, at release time

This workflow runs on every published GitHub release. It generates a CycloneDX SBOM, attaches it to the release and scans it.

```yaml
name: release-sbom

on:
  release:
    types: [published]

permissions: {}

jobs:
  sbom:
    runs-on: ubuntu-24.04
    permissions:
      contents: write # attach the SBOM to the release
      actions: read   # sbom-action reads its own workflow artifact when attaching release assets
    env:
      SBOM_FILE: ${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.cdx.json
      OSV_SCANNER_VERSION: "2.7.0"
      OSV_SCANNER_SHA256: "160c6253e2922e9e0a6a9771c5c4c40a4b3c6090b34a3cdd5e80c96cc2e4218b" # osv-scanner_linux_amd64 v2.7.0
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - name: Generate CycloneDX SBOM and attach it to the release
        uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
        with:
          path: .
          format: cyclonedx-json
          output-file: ${{ env.SBOM_FILE }}
          artifact-name: ${{ env.SBOM_FILE }}
          syft-version: v1.57.0

      - name: Scan the release SBOM with OSV-Scanner
        run: |
          curl -fsSLo osv-scanner \
            "https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
          echo "${OSV_SCANNER_SHA256}  osv-scanner" | sha256sum -c -
          chmod +x osv-scanner
          set +e
          ./osv-scanner scan source -L "${SBOM_FILE}" --format json --output-file osv-results.json
          rc=$?
          set -e
          # 0 = no known vulns, 1 = vulns found (triage, do not block the release), anything else = scan failed
          if [ "$rc" -ne 0 ] && [ "$rc" -ne 1 ]; then
            echo "::error::osv-scanner failed with exit code $rc"
            exit "$rc"
          fi

      - name: Keep the scan result next to the SBOM
        uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        with:
          name: osv-results-${{ github.event.release.tag_name }}
          path: osv-results.json
```

Actions are pinned to commit SHAs, the scanner binary is checked against the published v2.7.0 checksum, and permissions start from deny.

[osv-scanner returns](https://google.github.io/osv-scanner/output/#return-codes) 0 for no known vulnerabilities, 1 when it finds some, 127 for a general error and 128 when it finds no packages. A release should not block on known vulnerabilities, since that is triage's job, but it must fail on anything else. Google's own [reusable osv-scanner workflow](https://github.com/google/osv-scanner-action) carries a comment warning that, without a guard, a failed scan would be reported as "no vulnerabilities found". A scanner that silently passes is worse than none.

Tag names containing `/` break the filename, so sanitize them. The workflow passes actionlint and its scan logic was exercised locally (with the older `--output` flag), but it has not been run on a GitHub-hosted runner. Test it in your own repository first.

### Rescan archived SBOMs, because the advisory data changes

A release-time scan says what was known when you shipped. Article 14 cares about what is exploited now, in everything still deployed. The SBOM does not change, but the vulnerability data does. Locally, generating an SBOM and rescanning an archived one looks like this:

```bash
# generate with syft
syft scan dir:. -o cyclonedx-json=demo-app-1.4.2.cdx.json
# or with cdxgen, which writes the same file, so run one or the other
# cdxgen -t js -o demo-app-1.4.2.cdx.json .

# scan an archived SBOM
# v2 syntax
osv-scanner scan source -L ./releases/demo-app-1.4.2.cdx.json

# machine-readable output for a triage pipeline
osv-scanner scan source -L ./releases/demo-app-1.4.2.cdx.json --format json --output-file osv-results.json
```

osv-scanner v2 picks its parser from the filename: `bom.json`, `*.cdx.json`, `*.spdx.json` and similar. The same SBOM saved as `sbom.json` fails with "could not determine extractor suitable to this file" and exit code 127. The old `--sbom` flag is deprecated.

Against a small npm demo app with lodash 4.17.20 and minimist 1.2.5, SBOMs from syft 1.57.0 and cdxgen 12.9.1 produced the same five findings. Both emit CycloneDX 1.7 by default.

### A default SBOM falls short of BSI's profile

[TR-03183-2](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2_v2_1_0.pdf?__blob=publicationFile&v=6) (v2.1.0) is a detailed SBOM profile from Germany's federal cybersecurity agency, BSI. It asks for CycloneDX 1.6+ or SPDX 3.0.1+, recursive dependency resolution, creator contact details, a timestamp (UTC recommended) and SHA-512 hashes of deployable components. A default lockfile scan does not fill all of that in.

TR-03183-2 also says vulnerability information "should not be detailed by the SBOM" and points to CSAF (the Common Security Advisory Framework) or VEX (Vulnerability Exploitability eXchange). That split is right. The SBOM says what is in the box. Something else has to say whether it matters.

## Exploitation, not presence, decides what you report

A scanner reports presence. Article 14 is triggered by exploitation.

A critical CVE in your dependency tree is not reportable on its own, and neither is a reachable one: Article 3(42) requires "reliable evidence that a malicious actor has exploited it". Good-faith research does not count either. Recital 68, one of the explanatory statements that precede the articles, excludes vulnerabilities found through "good faith testing, investigation, correction or disclosure". FAQ 5.2 applies that to bug bounties: a zero-day reported through a bounty "is not an actively exploited vulnerability subject to mandatory reporting" without evidence of prior malicious exploitation.

Component vulnerabilities follow the same rule. If one "cannot be exploited in its product with digital elements (e.g. because the vulnerable code is not reachable)" or has not been exploited in it, it "is not subject to mandatory reporting for that manufacturer" (guidance paragraph 218). The reverse holds too. When an exploited component vulnerability is in your product, you notify, and the component's manufacturer also notifies if it placed the component on the market (FAQ 5.4).

### A KEV listing starts an assessment

CISA's [Known Exploited Vulnerabilities catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) is a widely used public list of exploited CVEs. It is a US catalog, and no EU document we found equates a KEV listing with a CRA trigger.

It is still a strong signal, and FAQ 5.1 names government agencies as a channel. When a component you ship appears in KEV, start the initial assessment. Is the vulnerable code in your product and reachable? Is there evidence of exploitation against your product?

### VEX records the answer to the first question

VEX writes a triage decision down per vulnerability, product and version. CISA's 2023 [minimum requirements for VEX](https://www.cisa.gov/resources-tools/resources/minimum-requirements-vulnerability-exploitability-exchange-vex) set the shared statuses: Not Affected, Affected, Fixed, Under Investigation. [OpenVEX](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md) is a compact JSON encoding. This statement says a dependency CVE does not affect one product version, with a justification a schema can check:

```json
{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://example.com/vex/demo-app-1.4.2-2026-11-07-001",
  "author": "Example Corp Product Security <psirt@example.com>",
  "role": "Manufacturer",
  "timestamp": "2026-11-07T09:30:00Z",
  "version": 1,
  "statements": [
    {
      "vulnerability": {
        "name": "CVE-2021-44906",
        "aliases": ["GHSA-xvch-5gv4-984h"]
      },
      "products": [
        {
          "@id": "pkg:npm/demo-app@1.4.2",
          "subcomponents": [
            { "@id": "pkg:npm/minimist@1.2.5" }
          ]
        }
      ],
      "status": "not_affected",
      "justification": "vulnerable_code_not_in_execute_path",
      "impact_statement": "minimist is only reachable from the build-time CLI script and is never invoked with untrusted input in the shipped runtime."
    }
  ]
}
```

CVE-2021-44906 is the real minimist prototype pollution issue osv-scanner flagged in the demo app. The product and the reachability claim are illustrative.

The vocabulary is strict. OpenVEX v0.2.0 accepts five justifications, such as `vulnerable_code_not_in_execute_path` and `inline_mitigations_already_exist`. Write `not_reachable` and schema validation fails. CycloneDX carries VEX inside the SBOM's `vulnerabilities[].analysis` with different words, such as `code_not_reachable`. Pick one encoding per pipeline and map at the edges.

The CRA does not mandate VEX. VEX is useful because paragraph 218 makes an unreachable component vulnerability non-reportable for that manufacturer. That is a decision, and it should be written down before someone asks for it.

A `not_affected` statement is an engineering judgment about one build. It can be wrong, and it can become wrong when a later release wires the component into a request path. Tie each statement to a version, keep the analysis behind it, and revisit it when the code changes.

## Advisories are becoming machine-readable supply chain data

Users have to be told, too. After becoming aware of an actively exploited vulnerability or severe incident, the manufacturer "shall inform the impacted users", "where appropriate in a structured, machine-readable format that is easily automatically processable" (Article 14(8)).

The article sets no numeric deadline for this, but it does set a consequence: if the manufacturer does not inform users "in a timely manner", the notified CSIRTs "may provide such information to the users". Guidance paragraph 220 adds that this "does not imply that such information must be made public or disclosed indiscriminately."

From December 2027, Annex I Part II adds public disclosure of fixed vulnerabilities, a coordinated vulnerability disclosure policy, "a contact address for the reporting of the vulnerabilities", and security updates "accompanied by advisory messages". From the same date, Article 13(6) also requires manufacturers to report component vulnerabilities upstream and share fixes "where appropriate in a machine-readable format."

### CSAF is the format customers' tooling can consume

The law names no format, but CSAF is the standard built for this job. [CSAF 2.0](https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html) is an OASIS Standard, approved in November 2022 and published as ISO/IEC 20153:2025. Version 2.1 is still a committee draft, so build on 2.0. Its `csaf_vex` profile uses the same five justification labels as OpenVEX, so a triage decision can move from internal VEX into a customer advisory without translation.

Here is a compact CSAF 2.0 security advisory for an exploited vulnerability. CVE-2026-99999 is a placeholder.

```json
{
  "document": {
    "category": "csaf_security_advisory",
    "csaf_version": "2.0",
    "publisher": {
      "category": "vendor",
      "name": "Example Corp",
      "namespace": "https://example.com"
    },
    "title": "Example Gateway: authentication bypass exploited in the wild",
    "tracking": {
      "id": "EXAMPLE-SA-2026-0042",
      "status": "final",
      "version": "1",
      "initial_release_date": "2026-11-07T12:00:00.000Z",
      "current_release_date": "2026-11-07T12:00:00.000Z",
      "revision_history": [
        { "date": "2026-11-07T12:00:00.000Z", "number": "1", "summary": "Initial version." }
      ]
    },
    "notes": [
      {
        "category": "summary",
        "title": "Summary",
        "text": "An authentication bypass in the Example Gateway admin API is being actively exploited. Version 3.4.2 fixes the issue."
      }
    ]
  },
  "product_tree": {
    "branches": [
      {
        "category": "vendor",
        "name": "Example Corp",
        "branches": [
          {
            "category": "product_name",
            "name": "Example Gateway",
            "branches": [
              {
                "category": "product_version",
                "name": "3.4.1",
                "product": {
                  "product_id": "EGW-3.4.1",
                  "name": "Example Gateway 3.4.1",
                  "product_identification_helper": { "purl": "pkg:generic/example/gateway@3.4.1" }
                }
              },
              {
                "category": "product_version",
                "name": "3.4.2",
                "product": {
                  "product_id": "EGW-3.4.2",
                  "name": "Example Gateway 3.4.2",
                  "product_identification_helper": { "purl": "pkg:generic/example/gateway@3.4.2" }
                }
              }
            ]
          }
        ]
      }
    ]
  },
  "vulnerabilities": [
    {
      "cve": "CVE-2026-99999",
      "notes": [
        {
          "category": "description",
          "text": "Missing authorization check on the admin API allows an unauthenticated attacker to create administrator sessions."
        }
      ],
      "product_status": {
        "known_affected": ["EGW-3.4.1"],
        "fixed": ["EGW-3.4.2"]
      },
      "remediations": [
        {
          "category": "vendor_fix",
          "details": "Upgrade to Example Gateway 3.4.2.",
          "product_ids": ["EGW-3.4.1"],
          "url": "https://example.com/downloads/gateway/3.4.2"
        }
      ],
      "threats": [
        {
          "category": "exploit_status",
          "details": "Exploitation in the wild has been observed.",
          "date": "2026-11-06T22:15:00.000Z"
        }
      ]
    }
  ]
}
```

This document passes the OASIS schema and the mandatory tests in BSI's [csaf-validator-lib](https://github.com/secvisogram/csaf-validator-lib). Run your own documents through the same validator, because mandatory tests catch things the schema misses, such as a status entry pointing at an undefined product ID.

Each block maps to an earlier stage. `product_tree` is your SBOM archive's version list. `product_status` is your VEX decision. `threats` is the evidence that started the clock. A team with those records in place mostly assembles the advisory from data it already holds.

### security.txt is how reporters and tools find you

[RFC 9116](https://www.rfc-editor.org/rfc/rfc9116.html) defines `/.well-known/security.txt`, a simple way to publish a vulnerability contact. Its IANA-registered `CSAF` field points machines at your advisory index. A file like this one, served over HTTPS as `text/plain; charset=utf-8`, covers both:

```text
Contact: mailto:security@example.com
Contact: https://example.com/security/report
Expires: 2027-09-30T23:59:59Z
Encryption: https://example.com/.well-known/pgp-key.txt
Preferred-Languages: en, de
Policy: https://example.com/security/disclosure-policy
Acknowledgments: https://example.com/security/hall-of-fame
Canonical: https://example.com/.well-known/security.txt
CSAF: https://example.com/.well-known/csaf/provider-metadata.json
```

Only `Contact` and `Expires` are required. `Expires` appears exactly once, and the RFC recommends less than a year ahead. An expired file is a stale promise, so put its renewal in the calendar that tracks certificate expiry.

## Your dependencies can start your clock

Many exploited vulnerabilities in your product will sit in code you did not write. Some severe incidents will not be vulnerabilities at all. A compromised build pipeline or a poisoned release under your name fits Article 14(5)(b) directly: an incident is severe when it "has led or is capable of leading to the introduction or execution of malicious code".

### Worms publish under your name

The 2026 npm worm waves show the mechanics. On 4 August 2026, [Elastic Security Labs](https://www.elastic.co/security-labs/shai-hulud-chaindrop-npm-supply-chain) identified a new Shai-Hulud campaign built around a self-propagating worm it called CHAINDROP. A `keyv` maintainer was compromised and "every subpackage in the `keyv` repo" was backdoored, with execution "triggered via a `preinstall` hook". Besides harvesting credentials, the worm planted a Claude Code `SessionStart` hook and a VS Code `folderOpen` task. Elastic's vendor-reported count was over 400 packages at the time; other vendors published different, higher counts.

A worm that writes a Claude Code hook treats the coding agent as part of the supply chain, the shift covered in [how AI changed the software supply chain](https://blog.prashantkoirala.info.np/ai-changed-the-software-supply-chain).

On 8 October, [Endor Labs](https://www.endorlabs.com/learn/tensorlake-npm-package-compromised-by-shai-hulud-in-latest-software-supply-chain-attack) reported a compromised Tensorlake npm release. Its timeline puts the first malicious commit at 01:20 UTC on 7 October and the publish of version 0.5.144 at 01:12 UTC on 8 October. The release went through the project's normal GitHub Actions pipeline and "even carried valid build provenance." Endor's summary: "Provenance confirms where a build came from, not that the source was clean."

That is the same limit discussed in [moving software publishing beyond long-lived tokens](https://blog.prashantkoirala.info.np/software-publishing-is-moving-beyond-long-lived-tokens). Trusted publishing removes a class of stolen-token attacks, but it cannot vouch for what was in the repository when the pipeline ran.

For a manufacturer, the question is whether your pipeline published malicious code, or whether your product pulled it in and ran it on a user's systems. If either happened, the 24-hour clock starts at reasonable certainty, and the evidence trail starts at your build logs.

Exploitation of open source is not limited to install-time worms. KEV additions between August and early November 2026 included Starlette, LiteLLM, Gitea, Ray, MLflow, PHP, Brotli and several Linux kernel issues. Some of those are libraries embedded in other products.

Upstream reporting becomes a duty in December 2027. From then, Article 13(6) requires reporting a component vulnerability "to the person or entity manufacturing or maintaining the component", even when it is not reportable to a CSIRT. Give the triage record an "upstream reported" field next to the VEX status now.

### Open-source stewards get a lighter regime, and more time

An open-source software steward is a legal person, other than a manufacturer, that systematically supports free and open-source software "intended for commercial activities" (Article 3(14)). Individual maintainers who are neither manufacturers nor stewards have no Article 14 duty. Steward reporting is narrower than the manufacturer duty:

- from 11 December 2027 (Commission [FAQ 5.5](https://ec.europa.eu/newsroom/dae/redirection/document/122331) and the [reporting obligations page](https://digital-strategy.ec.europa.eu/en/policies/cra-reporting))
- for exploited vulnerabilities, only "to the extent that they are involved in the development" of the products; for severe incidents, only where they affect systems the steward provides for that development (Article 24(3))
- for vulnerabilities, triggered by exploitation, "not merely where a vulnerability exists in the codebase" (guidance paragraph 216)
- no administrative fines (Article 64(10)(b))

Paragraph 216 expects stewards to typically learn of exploitation from downstream manufacturers or researchers, so your upstream report may be how the project finds out.

## Vulnerability response needs an on-call rotation, not a mailbox

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/be68cae5-b3ef-4099-bf71-5ff300bf5196.jpg align="center")

*Photo by* [*cottonbro studio*](https://www.pexels.com/@cottonbro/) *on* [*Pexels*](https://www.pexels.com/photo/alarm-clock-and-phone-on-a-side-table-6940357/)

A shared `security@` inbox read on weekdays was a reasonable design when the deadline was "next release". A 24-hour clock needs a pager.

### Exploitation keeps its own calendar

Additions to [CISA's KEV JSON feed](https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json) (catalog version 2026.11.06) show how often new exploitation evidence lands, and how the deadlines attached to it have moved:

- 64 CVEs added between 11 September and 6 November 2026, 52 with a 3-day federal remediation due date and 12 with a 14-day due date
- 21-day due dates on 175 of 186 additions in 2024 and 226 of 245 in 2025; 3-day due dates on 151 of 285 additions in 2026 to 6 November

The due dates are set by CISA policy for US federal agencies, and they do not bind CRA manufacturers. The shift shows how CISA now prices exploited vulnerabilities. It is not proof that attackers got faster.

Some of that evidence arrives on a Sunday. On 27 September 2026, CISA added two Citrix NetScaler zero-days, CVE-2026-88771 and CVE-2026-88772. Its [alert](https://www.cisa.gov/news-events/alerts/2026/09/27/critical-zero-day-vulnerabilities-exploited-citrix-netscaler-adc-gateway) said both "can independently enable remote code execution" and that "threat actors are actively exploiting these vulnerabilities globally." A third, CVE-2026-88779, followed on Sunday 4 October. Other additions landed on Fridays with Monday due dates, such as Collabora Online CVE-2026-110447 on 23 October.

What any vendor filed under the CRA is not public, but the shape of the problem is. A government agency publishes evidence of exploitation on a Sunday, and every affected manufacturer should have an initial assessment under way before Monday's stand-up.

Vendor research does suggest exploitation is arriving earlier. Mandiant's M-Trends 2026, as [reported by Help Net Security](https://www.helpnetsecurity.com/2026/03/24/mandiant-m-trends-2026-report/), estimates mean time-to-exploit for 2025 at minus seven days, meaning exploitation on average began before a patch existed. In 2018 it was 63 days. These are vendor-reported figures measured by Google Threat Intelligence Group.

### The readiness test is how fast you can answer

A written policy proves little. Time each answer.

| Question | What answers it | Feeds |
| --- | --- | --- |
| Which releases contain the component? | Archived per-release SBOMs, indexed | Early warning |
| In which Member States is the product available? | Distribution and licensing data | Early warning, routing |
| Is the vulnerable code reachable? | VEX statements with recorded analysis | Decision to report |
| Is there reliable evidence of exploitation against our product? | Threat intel, customer reports, telemetry | Decision to report |
| When did we become aware, and who decided? | Timestamped incident record | SRP awareness field, all deadlines |
| What can users do right now? | Mitigation playbook, advisory draft | Notification, user information |
| Who can submit to the SRP this minute? | EU Login accounts with MFA on the rotation | Every submission |

Rate each row today: under an hour, under a day, or not at all. Every "not at all" is backlog.

### Write the runbook before the page fires

1. Route every external signal (support, sales, `security@`, intel feeds) to a paging queue.
2. The on-call engineer starts the initial assessment and opens a timestamped incident record.
3. Query the SBOM archive for affected releases.
4. Decide reachability and record it as VEX, with the analysis attached.
5. Decide whether the evidence reaches "reasonable degree of certainty". If yes, record `aware_at` and the decision owner.
6. Compute deadlines from `aware_at` and post them in the incident channel.
7. Draft the early warning in your own system, then submit through the SRP.
8. Prepare user information and the CSAF advisory alongside the 72-hour notification.
9. Record the submission time of each report. The incident final report counts from the 72-hour notification.
10. Record when a fix or mitigation ships. The vulnerability final report clock starts there.

The rotation needs people who hold SRP access as well as people who can read logs.

## What to build before 11 December 2027

September 2026 made reporting operational. December 2027 adds the rest: Annex I requirements, the SBOM duty, public disclosure of fixed vulnerabilities, upstream reporting under Article 13(6), steward reporting and the enforcement provisions. Support periods also become explicit. Article 13(8) sets at least five years unless the product's expected use is shorter.

![](https://cdn.hashnode.com/uploads/covers/6818fe7787817224e50ba1ae/62e26eb8-21c8-4262-87a3-4179417316e8.png align="center")

Harmonised standards, the European standards whose use lets a product be presumed to conform with the law, are still being drafted, and no citation in the Official Journal could be confirmed at the time of writing. That is a reason to wait on a certification plan. It is not a reason to wait on the pipeline.

### Phase one: make the evidence exist

- per-release SBOMs generated in CI and archived with the release
- a scheduled rescan of every archived SBOM still deployed
- an incident record with immutable timestamps and an `aware_at` field
- the deadline calculator wired into that record

### Phase two: make decisions portable

- VEX statements per product version, with recorded analysis
- an upstream reporting field for Article 13(6)
- CSAF 2.0 advisories, validated in CI before publication
- `security.txt` with a `CSAF` pointer and a renewal reminder

### Phase three: staff the response

- a paging route from every external channel
- EU Login accounts with MFA for everyone on the rotation
- a named Primary Assigned Representative and secondaries
- report drafts kept in your own system, ready to paste

Deployments and outages became platform capabilities. Vulnerability response is next. They fit the broader move toward [security-first architecture](https://blog.prashantkoirala.info.np/the-rise-of-security-first-architecture), where controls live in the paved road instead of arriving during an audit. Apart from the reporting platform, the CRA mandates nothing in the table below, but each entry shortens the time between a signal and a defensible answer.

| Need | Format or tool | Status (7 November 2026) | Watch out for |
| --- | --- | --- | --- |
| SBOM | CycloneDX 1.7 | Released October 2025 | VEX vocabulary differs from OpenVEX |
| SBOM | SPDX 3.0.1 | Latest final; 3.1 is a release candidate | Do not build on 3.1 yet |
| SBOM generation | syft 1.57.0, cdxgen 12.9.1 (tested; 13.x is the current cdxgen major) | Both emit CycloneDX 1.7 by default | Not automatically TR-03183-2 compliant |
| Matching | osv-scanner 2.7.0 | `scan source -L` | Filename picks the parser; exit 127 on a bad name |
| Triage record | OpenVEX 0.2.0 | Spec with JSON schema | Strict justification list |
| Advisory | CSAF 2.0 | OASIS Standard; ISO/IEC 20153:2025 | 2.1 is still a draft |
| Contact and discovery | security.txt (RFC 9116) | Published April 2022 | `Expires` must be renewed |
| Reporting | ENISA SRP 1.3 | Live; English only | No API; drafts are per account |

---

Vulnerabilities used to end as a line in a changelog.

Since 11 September 2026, an exploited vulnerability in a product sold in the EU is also a timestamp, a 24-hour deadline and a report to a CSIRT, whether it arrives on a Tuesday afternoon or a Sunday night.

The form takes minutes. Knowing what you shipped, where it runs, whether it is reachable and when you knew takes a platform. Teams that build it now will find the clock routine. Teams that wait for December 2027 will spend the first live report reconstructing their own releases.

## References

**Primary sources: law, regulators, standards bodies**

1. Regulation (EU) 2024/2847 (Cyber Resilience Act), OJ L, 20.11.2024: <https://eur-lex.europa.eu/eli/reg/2024/2847/oj>
2. Corrigendum to Regulation (EU) 2024/2847, OJ L 2025/90555, 2.7.2025: <https://eur-lex.europa.eu/eli/reg/2024/2847/corrigendum/2025-07-02/oj>
3. Regulation (EEC, Euratom) No 1182/71 (periods, dates and time limits): <https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:31971R1182>
4. European Commission, Commission guidance on the application of the CRA, C(2026) 5252, 27 July 2026: <https://digital-strategy.ec.europa.eu/en/library/commission-publishes-new-guidance-support-timely-cyber-resilience-act-implementation> (PDF: <https://ec.europa.eu/newsroom/dae/redirection/document/131456>)
5. European Commission, FAQs on the Cyber Resilience Act, v1.4, 4 September 2026: <https://ec.europa.eu/newsroom/dae/redirection/document/122331>
6. European Commission, CRA reporting obligations: <https://digital-strategy.ec.europa.eu/en/policies/cra-reporting>
7. European Commission, Cyber Resilience Act: <https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act>
8. ENISA, Single Reporting Platform: <https://www.enisa.europa.eu/topics/product-security/single-reporting-platform-srp>
9. ENISA, SRP frequently asked questions (updated 15 October 2026): <https://www.enisa.europa.eu/topics/product-security/single-reporting-platform-srp/frequently-asked-questions>
10. ENISA, "The CRA Single Reporting Platform is launched", 11 September 2026: <https://www.enisa.europa.eu/index.php/news/the-cra-single-reporting-platform-is-launched>
11. CISA, Known Exploited Vulnerabilities catalog: <https://www.cisa.gov/known-exploited-vulnerabilities-catalog> (JSON feed: <https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json>)
12. CISA, alert on Citrix NetScaler ADC and Gateway zero-days, 27 September 2026 (revised 9 October 2026): <https://www.cisa.gov/news-events/alerts/2026/09/27/critical-zero-day-vulnerabilities-exploited-citrix-netscaler-adc-gateway>
13. CISA, Minimum Requirements for Vulnerability Exploitability eXchange (VEX), April 2023: <https://www.cisa.gov/resources-tools/resources/minimum-requirements-vulnerability-exploitability-exchange-vex>
14. BSI, TR-03183-2 v2.1.0, Software Bill of Materials: <https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2_v2_1_0.pdf?__blob=publicationFile&v=6>
15. OASIS, Common Security Advisory Framework Version 2.0: <https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html>
16. OASIS CSAF TC releases (2.1 drafts): <https://github.com/oasis-tcs/csaf/releases>
17. RFC 9116, A File Format to Aid in Security Vulnerability Disclosure: <https://www.rfc-editor.org/rfc/rfc9116.html> (IANA field registry: <https://www.iana.org/assignments/security-txt-fields/>)
18. CycloneDX 1.7 release: <https://cyclonedx.org/news/cyclonedx-v1.7-released>
19. SPDX specification releases: <https://github.com/spdx/spdx-spec/releases>
20. OpenVEX specification: <https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md>

**Tool documentation**

21. OSV-Scanner documentation, return codes: <https://google.github.io/osv-scanner/output/#return-codes> (v2 migration guide: <https://github.com/google/osv-scanner/blob/main/docs/migration-guide.md>)
22. OSV-Scanner GitHub Action: <https://github.com/google/osv-scanner-action>
23. Syft: <https://github.com/anchore/syft> and anchore/sbom-action: <https://github.com/anchore/sbom-action>
24. cdxgen: <https://github.com/CycloneDX/cdxgen>
25. BSI csaf-validator-lib: <https://github.com/secvisogram/csaf-validator-lib>

**Secondary sources (vendor-reported where noted)**

26. Elastic Security Labs, Shai-Hulud campaign and the CHAINDROP npm worm, 6 August 2026 (vendor-reported): <https://www.elastic.co/security-labs/shai-hulud-chaindrop-npm-supply-chain>
27. Endor Labs, Tensorlake npm package compromise, 8 October 2026 (vendor-reported): <https://www.endorlabs.com/learn/tensorlake-npm-package-compromised-by-shai-hulud-in-latest-software-supply-chain-attack>
28. Help Net Security, coverage of Mandiant M-Trends 2026, 24 March 2026 (vendor-reported data, measured by Google Threat Intelligence Group): <https://www.helpnetsecurity.com/2026/03/24/mandiant-m-trends-2026-report/>
