DEV Community

Oleksandr Kuryzhev
Oleksandr Kuryzhev

Posted on Originally published at kuryzhev.cloud

CRA vulnerability reporting: a 24h and 72h runbook for small teams

Originally published on kuryzhev.cloud


The scenario: a Friday-evening exploit report

It is 18:40 on a Friday. A customer emails your two-person team a proof-of-concept for your firmware update daemon, with a link to a forum thread where someone is already using it. Before September 2026 that was a bad evening. Now it also starts a legal clock, because CRA vulnerability reporting has been mandatory since 11 Sep 2026. For actively exploited vulnerabilities, manufacturers of products with digital elements must send an early warning within 24 hours and a fuller notification within 72 hours. The Commission's overview is at digital-strategy.ec.europa.eu/en/policies/cra-reporting.

Large companies have legal and compliance staff for this. Small teams have whoever is on call. The deadlines count in hours, weekends included, so the runbook has to exist before the report arrives.

Short answer:

  • Write down who decides that a vulnerability is "actively exploited."
  • Record the moment you became aware.
  • Open a tracked issue that computes the 24h and 72h deadlines.
  • Prepare the early-warning and notification content as templates, so submitting is a copy-and-paste job.

This applies if you place products with digital elements on the EU market. Verify scope with counsel, because this article is engineering guidance, not legal advice.

Prerequisites

You need a few things in place before the runbook is worth anything:

  • A scope decision. List which of your products are "products with digital elements" under the CRA. Pure hosted SaaS is generally handled differently, and open-source stewards have lighter obligations. Check the regulation text (Regulation (EU) 2024/2847) and the Commission page above.
  • Access to the EU single reporting platform. The CRA routes reports through a single reporting platform established by ENISA. Submissions reach the CSIRT designated as coordinator and ENISA. Check the Commission page for current onboarding steps, and get your account created before an incident, not during one.
  • Two named people, a decider and a submitter, each with a deputy. One person cannot cover a 72-hour window.
  • An SBOM per release, and the gh CLI authenticated with issue-write rights (cli.github.com/manual).
  • Tooling: GNU date (Linux, or gdate on macOS), plus jq, syft and grype for the evidence step.

Cost to be aware of: this adds on-call duty and process weight. If you ship nothing to the EU market, skip it.

Step 1: Make intake findable and define "aware"

The clock runs from when you become aware of active exploitation, so the first job is a reachable intake channel:

A minimal security.txt served at https://example.com/.well-known/security.txt:

Contact: mailto:security@example.com
Expires: 2027-06-30T00:00:00.000Z
Policy: https://example.com/security-policy
Preferred-Languages: en
Canonical: https://example.com/.well-known/security.txt

Then put a definition in docs/cra-runbook.md. For example: "Awareness time is the timestamp when the decider records that credible evidence of exploitation exists (a vendor report, telemetry, a public exploit used in the wild), not when the first email arrived."

Whether that definition holds up legally is a question for your counsel. What matters operationally is that it is written down and timestamped, so nobody argues about it at 3 a.m.

Watch out for: an unmonitored security@ mailbox. If a report sits unread over a weekend, ask counsel whether that delay could count against you. Do not assume the clock waits for you to read it. Route the mailbox to the same pager as production alerts.

Step 2: Start the clock with one command

The cheapest control is a script that stamps the awareness time and creates a tracked issue with both deadlines. The labels must exist first:

gh label create cra-clock
gh label create sev-exploited

The script uses epoch arithmetic rather than date-string parsing, so the offsets are always exactly 24 and 72 hours.

#!/usr/bin/env bash
# cra-clock.sh <product> <cve-or-internal-id>
set -euo pipefail
PRODUCT="${1:?product}"; VULN="${2:?cve or internal id}"

FMT='+%Y-%m-%dT%H:%M:%SZ'
AWARE_EPOCH=$(date -u +%s)
AWARE=$(date -u -d "@${AWARE_EPOCH}" "$FMT")
T24=$(date -u -d "@$((AWARE_EPOCH + 86400))" "$FMT")    # early warning due
T72=$(date -u -d "@$((AWARE_EPOCH + 259200))" "$FMT")   # notification due

gh issue create \
  --title "CRA clock: ${VULN} (${PRODUCT})" \
  --label cra-clock --label sev-exploited \
  --assignee "@me" \
  --body "$(cat <<EOF
**Aware at:** ${AWARE}
**Early warning due (24h):** ${T24}
**Notification due (72h):** ${T72}

- [ ] Decider confirmed evidence of exploitation
- [ ] Member states where product is available listed
- [ ] Early warning submitted (record reference ID)
- [ ] Notification drafted: product, nature of vuln/exploit, mitigations
- [ ] Notification submitted (record reference ID)
- [ ] Corrective measure released; final report due within 14 days of that (confirm in Art. 14)
EOF
)"

An alias worth keeping in your shell profile: alias craclock='~/ops/cra-clock.sh'. Starting the clock takes seconds. GitHub records the issue's creation time, and any later edits to the body appear in the issue's edit history.

Watch out for: macOS ships BSD date, which does not support -d. Use gdate from coreutils, or the script will fail at the worst moment.

Step 3: Gather evidence and pre-write the reports

The early warning is meant to be fast and thin. For an actively exploited vulnerability, the regulation asks it to indicate, where applicable, the member states in which you know the product has been made available. (The "suspected malicious act" indication applies to severe-incident early warnings.)

The 72h notification adds:

  • general product information
  • the general nature of the vulnerability and exploit
  • the corrective or mitigating measures taken
  • the measures users can take

Keep one Markdown template for each report in the repo, and fill in the blanks rather than composing prose under pressure.

For evidence, generate an SBOM of the artifact you actually ship and cross-check it against CISA's Known Exploited Vulnerabilities catalog. Grype often reports GitHub advisory (GHSA) IDs, so the filter also reads the related CVE IDs:

syft dir:./release -o cyclonedx-json=sbom.cdx.json            # SBOM of the shipped artifact
grype sbom:./sbom.cdx.json -o json > findings.json            # match components to vulns

curl -fsSL https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \
  | jq -r '.vulnerabilities[].cveID' | LC_ALL=C sort -u > kev.txt

jq -r '.matches[] | .vulnerability.id, (.relatedVulnerabilities[]?.id)' findings.json \
  | grep '^CVE-' | LC_ALL=C sort -u \
  | LC_ALL=C comm -12 - kev.txt                                 # CVEs in your SBOM AND in KEV

Each output line is a CVE in your shipped components that is known to be exploited somewhere. Treat that as a strong trigger for the decider, not as the legal definition. KEV is one signal; your own telemetry and customer reports count too, and absence from KEV proves nothing.

Verify the flags against the current Syft and Grype documentation, since both tools change output formats between releases.

Step 4: Assign roles and hand-offs across the 72 hours

Write a simple timeline into the runbook so tired people are not improvising:

  • Hour 0: run craclock as soon as a credible report arrives. The decider then confirms or rejects the exploitation evidence and notes the reason in the issue. Starting at triage is deliberately conservative: it can only make your deadlines earlier than the legal ones. If the evidence is rejected, close the issue with the reason.
  • Hours 0-8: the submitter lists affected member states and files the early warning. Aim to finish well inside 24 hours, since the platform may be slow or your login may have expired.
  • Hours 8-60: engineering builds a mitigation (config change, feature flag, patch). Draft the notification in parallel, not after.
  • Hours 60-72: the submitter files the notification and links the reference IDs back in the issue. Do not wait for the full fix; the notification describes measures taken so far.

After a corrective or mitigating measure is available, a final report is due. Article 14 sets this at no later than 14 days for actively exploited vulnerabilities; confirm against the regulation text and put a dated reminder in the issue.

Keep users informed too. The CRA expects you to tell impacted users about the vulnerability and what they can do about it.

Verify and test

Run a drill before you need one:

  1. Clock script. From a clone of a test repo, run ./cra-clock.sh demo-product TEST-0001. Confirm the issue appears with the two timestamps exactly 24 and 72 hours after "Aware at." If the script errors, the cause is almost always a non-GNU date.
  2. Evidence pipeline. Build a throwaway fixture that contains a component at a version affected by a KEV-listed CVE, for example log4j-core 2.14.1 (CVE-2021-44228). Confirm the comm -12 line prints that CVE. An empty result against a deliberately vulnerable fixture means the pipeline is broken, not that you are safe.
  3. Tabletop exercise. Time the elapsed time from craclock to a filled-in early-warning draft; the target is hours, not a day. Log in to the reporting platform during the drill, but do not submit real reports for fake incidents.

Repeat the drill after staff changes, and calendar the security.txt expiry date.

Closing

CRA vulnerability reporting rewards boring preparation: a monitored inbox, a written definition of awareness, a one-command clock script, and templates that turn a Friday-night scramble into a checklist.

None of this replaces legal review of your scope and obligations. A small team should confirm both with the Commission's guidance and the regulation text. Set it up this week, rehearse it once, and put the platform login in your password manager. For more practical security and CI/CD runbooks, browse kuryzhev.cloud.

Related

Top comments (0)