2026-04-09·13 min read·sota.io team

EU Cyber Resilience Act: SBOM Requirements and Vulnerability Handling Developer Guide

The EU Cyber Resilience Act (Regulation (EU) 2024/2847, "CRA") entered into force on 10 December 2024. Enforcement begins on 11 December 2027. Any software or hardware product sold in the EU market that has digital elements — this includes SaaS platforms, on-premises software, embedded systems, mobile apps, and cloud-hosted services accessed via network — must comply.

The CRA introduces four categories of mandatory developer obligations that have no counterpart in prior EU regulation:

  1. Software Bill of Materials (Art. 13): machine-readable inventory of all components
  2. Coordinated Vulnerability Disclosure (Art. 14): 24-hour ENISA notification for actively exploited vulnerabilities
  3. Security Update Delivery (Art. 15): mandatory updates for at least 5 years
  4. Security-by-Design (Art. 11): secure defaults, minimal attack surface, least privilege

This guide covers each article in implementation detail, explains the critical intersections with the EU AI Act and NIS2 Directive, and identifies where infrastructure jurisdiction determines your compliance posture.

Scope: What Is a "Product with Digital Elements"?

The CRA applies to any product that has data processing functionality and is placed on the EU market — whether sold, licensed, or provided via subscription. Recital 12 and Art. 3(1) define the scope broadly:

Class A (Standard PDEs): Default CRA obligations. Self-assessment of conformity possible.

Class B (Important PDEs — Annex III): Higher-risk categories including browsers, password managers, VPN clients, network management software, SIEM systems, and industrial automation software. Third-party conformity assessment required for critical categories.

The classification determines not which obligations apply — all PDEs face Art. 11/13/14/15 — but how conformity is assessed.

Art. 11: Security-by-Design Obligations

Article 11 requires that products with digital elements are designed and developed to deliver security as a default property, not as a post-deployment addition. The operative requirements:

11(1) — No Known Exploitable Vulnerabilities at Placement

Products must be placed on the market without known exploitable vulnerabilities in components. This does not require zero CVEs — it requires that all known CVEs are either patched, mitigated, or explicitly documented with compensating controls.

Practical implication: you cannot ship a container image with log4j 2.14 and CVE-2021-44228 unpatched. Market surveillance authorities (MSAs) can pull products from sale if placed with known critical vulnerabilities unaddressed.

11(2) — Secure Default Configuration

Default configurations must implement least-privilege. Specific prohibitions:

11(3) — Data Protection at Design Level

Products must implement:

11(4) — Breach Notification Capability

Products must include mechanisms to detect and report security incidents. For software: structured logging of authentication failures, configuration changes, and anomalous access patterns. For embedded devices: tamper detection.

# Art.11(4)-compliant security event logging
import hashlib
import json
import time
from datetime import datetime, timezone

class SecurityEventLog:
    """Tamper-evident security event log for CRA Art.11(4) compliance."""

    def __init__(self, log_path: str):
        self.log_path = log_path
        self._prev_hash = "genesis"

    def record(self, event_type: str, details: dict, severity: str = "INFO") -> str:
        """Record security event with chain hash for tamper detection."""
        entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "event_type": event_type,
            "severity": severity,
            "details": details,
            "prev_hash": self._prev_hash,
        }
        entry_json = json.dumps(entry, sort_keys=True)
        entry_hash = hashlib.sha256(entry_json.encode()).hexdigest()
        entry["hash"] = entry_hash
        self._prev_hash = entry_hash

        with open(self.log_path, "a") as f:
            f.write(json.dumps(entry) + "\n")

        return entry_hash

    def record_auth_failure(self, user_id: str, source_ip: str, method: str):
        return self.record(
            "AUTH_FAILURE",
            {"user_id": user_id, "source_ip": source_ip, "method": method},
            severity="WARNING"
        )

    def record_config_change(self, changed_by: str, parameter: str, old_val, new_val):
        return self.record(
            "CONFIG_CHANGE",
            {"changed_by": changed_by, "parameter": parameter,
             "old_value": str(old_val), "new_value": str(new_val)},
            severity="AUDIT"
        )

# Usage
log = SecurityEventLog("/var/log/security-events.jsonl")
log.record_auth_failure("user@example.com", "203.0.113.42", "password")

Art. 13: Software Bill of Materials (SBOM) Requirements

Article 13(1)(b) and Annex I Part II require that manufacturers of products with digital elements maintain a machine-readable Software Bill of Materials covering all third-party components and dependencies.

Mandatory SBOM Content

The CRA does not prescribe every field, but Annex I Part II and ENISA guidance specify minimum content per component:

FieldDescriptionExample
Component nameLibrary or package namefastapi
VersionExact version string0.115.6
SupplierAuthor/maintainerSebastián Ramírez
Unique identifierCPE 2.3 or Package URL (PURL)pkg:pypi/fastapi@0.115.6
HashSHA-256 of distributed artifactsha256:abc123...
LicenseSPDX license expressionMIT
RelationshipRelationship to parent componentDEPENDENCY_OF

Transitive dependencies (dependencies of dependencies) must be included. For a typical Python service with 30 direct dependencies, the full transitive closure typically covers 150-300 packages.

SBOM Formats: CycloneDX vs SPDX

The CRA does not mandate a specific format, but requires machine-readability. Two formats dominate:

CycloneDX (recommended for security workflows):

SPDX (recommended for license compliance):

# Generate CycloneDX SBOM from Python project
# pip install cyclonedx-bom

import subprocess
import json
from pathlib import Path

def generate_sbom(project_root: Path, output_path: Path, format: str = "json") -> dict:
    """Generate CRA Art.13-compliant SBOM using cyclonedx-bom."""
    result = subprocess.run(
        ["cyclonedx-py", "environment", "--output-format", format,
         "--output-file", str(output_path)],
        cwd=project_root,
        capture_output=True,
        text=True
    )
    if result.returncode != 0:
        raise RuntimeError(f"SBOM generation failed: {result.stderr}")

    with open(output_path) as f:
        sbom = json.load(f)

    # Validate required fields per CRA Annex I Part II
    components = sbom.get("components", [])
    missing_fields = []
    for comp in components:
        if not comp.get("name"):
            missing_fields.append(f"{comp}: missing name")
        if not comp.get("version"):
            missing_fields.append(f"{comp.get('name')}: missing version")
        if not comp.get("purl") and not any(
            ref.get("type") == "cpe23Type" for ref in comp.get("externalReferences", [])
        ):
            missing_fields.append(f"{comp.get('name')}: missing PURL/CPE identifier")

    return {"sbom": sbom, "component_count": len(components), "warnings": missing_fields}

SBOM Storage and Disclosure Requirements

The CRA does not require public SBOM publication. Manufacturers must:

  1. Maintain the SBOM for the product's supported lifetime
  2. Provide it to market surveillance authorities on request
  3. Make it available to professional users on request (B2B supply chains)

Critical infrastructure consideration: If your SBOM is stored on US cloud infrastructure (AWS S3, Azure Blob, GCP Storage), it is subject to the US CLOUD Act (18 U.S.C. § 2713). US Department of Justice can compel disclosure of SBOM contents to US law enforcement without EU court involvement — including component lists, version details, and vulnerability information about your product.

For products in CRA Class B (Annex III), market surveillance authorities may share SBOM contents across EU member states via the ENISA vulnerability database. Storing SBOMs on EU-native infrastructure keeps this data under EU administrative law exclusively.

Art. 14: Coordinated Vulnerability Disclosure

Article 14 introduces the most operationally demanding CRA obligation for most development teams: mandatory vulnerability disclosure with hard time limits.

The Three-Stage Disclosure Timeline

When a manufacturer becomes aware of an actively exploited vulnerability in their product:

Stage 1 — Early Warning (24 hours): Notify ENISA via the single reporting platform with: product name, vulnerability type, CVE/identifier if known, affected versions, potential impact, whether a fix is available.

Stage 2 — Vulnerability Notification (72 hours): Submit detailed vulnerability report including: root cause analysis, affected component details, CVSS score, remediation steps, workaround instructions if patch not yet available.

Stage 3 — Final Report (14 days after remediation): Submit final report documenting: patch details, coordinated disclosure timeline, affected user population estimate, mitigation adoption status.

The 24-hour trigger is "actively exploited" — meaning confirmed reports of exploitation in the wild, not mere discovery of a vulnerability. Vulnerabilities discovered internally that are not yet exploited trigger coordinated disclosure obligations (Art. 14(3)) but not the 24-hour emergency timeline.

# CRA Art.14-compliant vulnerability disclosure workflow
import httpx
from dataclasses import dataclass
from datetime import datetime, timezone, timedelta
from enum import Enum

class DisclosureStage(Enum):
    EARLY_WARNING = "early_warning"      # 24h deadline
    VULNERABILITY_REPORT = "vuln_report"  # 72h deadline
    FINAL_REPORT = "final_report"         # 14 days deadline

@dataclass
class VulnerabilityDisclosure:
    cve_id: str
    product_name: str
    affected_versions: list[str]
    severity_cvss: float
    exploited_in_wild: bool
    discovered_at: datetime
    patch_available: bool = False
    patch_version: str | None = None

    def get_deadline(self, stage: DisclosureStage) -> datetime:
        deadlines = {
            DisclosureStage.EARLY_WARNING: timedelta(hours=24),
            DisclosureStage.VULNERABILITY_REPORT: timedelta(hours=72),
            DisclosureStage.FINAL_REPORT: timedelta(days=14),
        }
        return self.discovered_at + deadlines[stage]

    def is_overdue(self, stage: DisclosureStage) -> bool:
        return datetime.now(timezone.utc) > self.get_deadline(stage)

    def hours_remaining(self, stage: DisclosureStage) -> float:
        delta = self.get_deadline(stage) - datetime.now(timezone.utc)
        return delta.total_seconds() / 3600

def submit_enisa_early_warning(disclosure: VulnerabilityDisclosure, enisa_api_token: str):
    """Submit CRA Art.14 Stage 1 early warning to ENISA platform."""
    payload = {
        "product_name": disclosure.product_name,
        "vulnerability_id": disclosure.cve_id,
        "affected_versions": disclosure.affected_versions,
        "actively_exploited": disclosure.exploited_in_wild,
        "fix_available": disclosure.patch_available,
        "severity": "CRITICAL" if disclosure.severity_cvss >= 9.0 else
                   "HIGH" if disclosure.severity_cvss >= 7.0 else "MEDIUM",
        "reported_at": disclosure.discovered_at.isoformat(),
    }
    # ENISA single reporting platform (operational from 2027)
    response = httpx.post(
        "https://euvd.enisa.europa.eu/api/v1/disclosures/early-warning",
        json=payload,
        headers={"Authorization": f"Bearer {enisa_api_token}"},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()

Actively Exploited vs Discovered: Threshold Clarity

The 24-hour deadline applies only to actively exploited vulnerabilities. For vulnerabilities discovered internally:

Discovery ScenarioCRA ObligationTimeline
Internal code review finds a bugCoordinated disclosure + patchNo hard timer until exploitation confirmed
Bug bounty report (unexploited)Acknowledge reporter, begin CVD processReasonable disclosure timeline (typically 90 days per industry standard)
External report of active exploitationArt. 14 mandatory ENISA notification24h early warning
ENISA-requested disclosureMandatory cooperationPer ENISA request terms

NIS2 intersection: NIS2 Art. 23 requires essential entities to notify their national CSIRT within 24 hours of becoming aware of a "significant incident." A CRA-reportable actively exploited vulnerability in a product used by essential entities may simultaneously trigger CRA Art. 14 ENISA notification AND NIS2 Art. 23 CSIRT notification. These are separate reporting obligations to different authorities.

Art. 15: Security Update Delivery

Article 15 requires manufacturers to:

  1. Deliver security updates for the product's support period
  2. Minimum support period: the shorter of (a) the expected lifetime of the product in its context of use, or (b) 5 years from market placement
  3. Separate security updates from functional updates: security patches must be deliverable independently, without requiring users to also accept new features
  4. Automatic update option: products must offer automatic security update delivery. Users may opt out, but the option must exist and be enabled by default (Art. 11(2)(f))
  5. User notification: users must be notified of available updates, their urgency level, and actions required
# CRA Art.15-compliant update management
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum

class UpdateType(Enum):
    SECURITY = "security"      # Must be separable from functional updates
    FUNCTIONAL = "functional"
    COMBINED = "combined"      # Allowed only if functional changes required for security fix

class UpdateUrgency(Enum):
    CRITICAL = "critical"      # Exploit available, deploy immediately
    HIGH = "high"              # No exploit yet, 7-day deployment window
    MEDIUM = "medium"          # 30-day deployment window
    LOW = "low"                # 90-day deployment window

@dataclass
class SecurityUpdate:
    version: str
    update_type: UpdateType
    urgency: UpdateUrgency
    cve_ids: list[str]
    cvss_scores: list[float]
    release_date: datetime
    end_of_support_date: datetime
    auto_apply_eligible: bool = True

    def days_until_eol(self) -> int:
        delta = self.end_of_support_date - datetime.now()
        return delta.days

    def recommended_deployment_deadline(self) -> datetime:
        windows = {
            UpdateUrgency.CRITICAL: timedelta(days=1),
            UpdateUrgency.HIGH: timedelta(days=7),
            UpdateUrgency.MEDIUM: timedelta(days=30),
            UpdateUrgency.LOW: timedelta(days=90),
        }
        return self.release_date + windows[self.urgency]

class UpdatePolicyManager:
    """CRA Art.15-compliant update policy for products with digital elements."""

    def __init__(self, product_name: str, market_placement_date: datetime,
                 expected_lifetime_years: int = 5):
        self.product_name = product_name
        # Art.15: support period = min(expected lifetime, 5 years)
        self.support_end = market_placement_date + timedelta(
            days=365 * min(expected_lifetime_years, 5)
        )

    def is_in_support(self) -> bool:
        return datetime.now() < self.support_end

    def generate_update_manifest(self, update: SecurityUpdate) -> dict:
        """Generate machine-readable update manifest for automatic update systems."""
        return {
            "product": self.product_name,
            "update_version": update.version,
            "update_type": update.update_type.value,
            "urgency": update.urgency.value,
            "cve_ids": update.cve_ids,
            "max_cvss": max(update.cvss_scores, default=0),
            "release_date": update.release_date.isoformat(),
            "deployment_deadline": update.recommended_deployment_deadline().isoformat(),
            "auto_apply_eligible": update.auto_apply_eligible,
            "in_support_period": self.is_in_support(),
            "support_ends": self.support_end.isoformat(),
        }

CRA × EU AI Act Intersection

High-risk AI systems under EU AI Act Annex III are almost universally also Products with Digital Elements under the CRA. This creates dual compliance obligations that partially overlap but are independently enforced.

Where Obligations Overlap

Obligation AreaAI Act RequirementCRA Requirement
DocumentationArt. 11 Technical DocumentationArt. 13 SBOM
Risk ManagementArt. 9 Risk Management SystemArt. 11 Secure-by-Design
Post-Market MonitoringArt. 72 Post-Market MonitoringArt. 14 Vulnerability Disclosure
Incident ReportingArt. 73 Serious Incident ReportingArt. 14 ENISA Notification
UpdatesArt. 72(4) Corrective ActionsArt. 15 Security Updates

Where Obligations Diverge

AI Act Art. 72 (Post-Market Monitoring) covers AI-specific incidents — unintended outputs, performance degradation, discriminatory outcomes, safety failures. CRA Art. 14 covers security vulnerabilities — exploitable weaknesses that attackers can leverage.

These are different incident categories that may require simultaneous reporting to different authorities:

A compromised high-risk AI system (e.g., an attacker manipulates model inputs to cause discriminatory outputs) may simultaneously trigger both reporting obligations.

AI Act Art. 9 FMEA requirement: Risk management must include foreseeable misuse scenarios. A CRA Art. 14 vulnerability in the AI system's inference API that enables adversarial examples is a foreseeable misuse scenario that should appear in the Art. 9 risk register — bridging the two frameworks.

# Unified CRA × AI Act incident classifier
from dataclasses import dataclass
from enum import Enum

class ReportingAuthority(Enum):
    ENISA = "ENISA"                    # CRA Art.14
    NATIONAL_MSA = "National_MSA"      # AI Act Art.73
    BOTH = "Both_ENISA_and_MSA"

@dataclass
class SecurityIncident:
    affects_ai_system: bool
    ai_system_is_high_risk: bool  # AI Act Annex III
    is_actively_exploited: bool
    causes_ai_output_harm: bool
    personal_data_breach: bool    # GDPR Art.33 also triggered

    def required_reporting(self) -> list[ReportingAuthority]:
        authorities = []
        if self.is_actively_exploited:
            authorities.append(ReportingAuthority.ENISA)  # CRA Art.14, 24h
        if self.affects_ai_system and self.ai_system_is_high_risk and self.causes_ai_output_harm:
            authorities.append(ReportingAuthority.NATIONAL_MSA)  # AI Act Art.73
        return authorities if authorities else [ReportingAuthority.NATIONAL_MSA]

    def enisa_deadline_hours(self) -> int | None:
        return 24 if self.is_actively_exploited else None

    def gdpr_deadline_hours(self) -> int | None:
        return 72 if self.personal_data_breach else None

CRA × NIS2 Intersection

The CRA and NIS2 Directive address cybersecurity from different angles:

The same organisation can be subject to both simultaneously: if you manufacture cybersecurity software (CRA applies to the product) and you operate a digital infrastructure service (NIS2 applies to your operations).

Where the Rules Diverge

DimensionCRANIS2
SubjectProduct manufacturerEssential/Important entity
Scope triggerPlacing a PDE on the EU marketOperating in a covered sector
Vulnerability reportingENISA (24h for active exploitation)National CSIRT (24h for significant incidents)
EnforcementMarket surveillance authorityNational competent authority
FinesUp to €15M or 2.5% global turnoverUp to €10M (important) / €20M (essential) or 1.7%/2%

The dual-reporting trap: A CRA-reportable actively exploited vulnerability in your product is not automatically a NIS2-reportable significant incident for your operations — the thresholds differ. A significant incident under NIS2 Art. 23 requires measurable operational impact (service disruption, data breach, loss of integrity). A vulnerability without confirmed operational impact may not reach that threshold.

However, if the actively exploited vulnerability does cause operational disruption — particularly for NIS2-essential services — you face simultaneous:

  1. CRA Art. 14 → ENISA notification (24h)
  2. NIS2 Art. 23 → National CSIRT notification (24h)
  3. GDPR Art. 33 → National DPA notification (72h, if personal data involved)

Infrastructure Jurisdiction and CRA Compliance

The CRA creates three specific scenarios where infrastructure jurisdiction affects compliance posture:

1. SBOM Data Sovereignty

Art. 13 SBOMs stored on US cloud infrastructure (AWS S3, Azure Blob, GCP Storage) are subject to the US CLOUD Act. The DoJ can compel AWS/Microsoft/Google to disclose SBOM contents without EU court involvement. For competitive or national security-sensitive products, this creates a structural exposure:

EU-native SBOM storage resolves this: data remains subject exclusively to EU administrative law. Market surveillance authorities can request it through established EU legal channels; no parallel CLOUD Act route exists.

2. Vulnerability Disclosure Infrastructure

Art. 14 early warning submissions to ENISA should originate from infrastructure your team controls entirely. If your incident response tooling — SIEMs, vulnerability scanners, disclosure workflow systems — runs on US cloud, the following scenarios arise:

3. Security Update Delivery

Art. 15 requires delivering security updates for 5+ years. If your update delivery infrastructure — package repositories, CDNs, OTA update endpoints — runs on US cloud:

EU-native update delivery infrastructure removes these concerns structurally.

CRA Compliance Checklist

# CRA compliance readiness assessment
from dataclasses import dataclass, field

@dataclass
class CRAComplianceStatus:
    product_name: str

    # Art. 11 — Security-by-Design
    no_default_shared_passwords: bool = False
    automatic_updates_default_on: bool = False
    minimal_attack_surface_documented: bool = False
    security_event_logging_implemented: bool = False

    # Art. 13 — SBOM
    sbom_generated: bool = False
    sbom_format_cyclonedx_or_spdx: bool = False
    sbom_includes_transitive_deps: bool = False
    sbom_stored_eu_jurisdiction: bool = False
    sbom_updated_on_every_release: bool = False

    # Art. 14 — Vulnerability Disclosure
    cvd_policy_published: bool = False
    enisa_reporting_workflow_documented: bool = False
    incident_response_24h_capable: bool = False
    vuln_disclosure_contact_public: bool = False

    # Art. 15 — Security Updates
    support_period_minimum_5_years: bool = False
    security_updates_separable_from_functional: bool = False
    auto_update_mechanism_implemented: bool = False
    eol_date_published: bool = False

    def score(self) -> tuple[int, int]:
        fields = [
            self.no_default_shared_passwords,
            self.automatic_updates_default_on,
            self.minimal_attack_surface_documented,
            self.security_event_logging_implemented,
            self.sbom_generated,
            self.sbom_format_cyclonedx_or_spdx,
            self.sbom_includes_transitive_deps,
            self.sbom_stored_eu_jurisdiction,
            self.sbom_updated_on_every_release,
            self.cvd_policy_published,
            self.enisa_reporting_workflow_documented,
            self.incident_response_24h_capable,
            self.vuln_disclosure_contact_public,
            self.support_period_minimum_5_years,
            self.security_updates_separable_from_functional,
            self.auto_update_mechanism_implemented,
            self.eol_date_published,
        ]
        passed = sum(1 for f in fields if f)
        return passed, len(fields)

    def gaps(self) -> list[str]:
        checks = {
            "Art.11 — No default shared passwords": self.no_default_shared_passwords,
            "Art.11 — Automatic updates enabled by default": self.automatic_updates_default_on,
            "Art.11 — Attack surface documented": self.minimal_attack_surface_documented,
            "Art.11 — Security event logging": self.security_event_logging_implemented,
            "Art.13 — SBOM generated": self.sbom_generated,
            "Art.13 — CycloneDX or SPDX format": self.sbom_format_cyclonedx_or_spdx,
            "Art.13 — Transitive dependencies included": self.sbom_includes_transitive_deps,
            "Art.13 — SBOM stored in EU jurisdiction": self.sbom_stored_eu_jurisdiction,
            "Art.13 — SBOM updated per release": self.sbom_updated_on_every_release,
            "Art.14 — CVD policy published": self.cvd_policy_published,
            "Art.14 — ENISA reporting workflow documented": self.enisa_reporting_workflow_documented,
            "Art.14 — 24h incident response capable": self.incident_response_24h_capable,
            "Art.14 — Vulnerability disclosure contact public": self.vuln_disclosure_contact_public,
            "Art.15 — 5-year minimum support period": self.support_period_minimum_5_years,
            "Art.15 — Security updates separable": self.security_updates_separable_from_functional,
            "Art.15 — Auto-update mechanism": self.auto_update_mechanism_implemented,
            "Art.15 — EOL date published": self.eol_date_published,
        }
        return [label for label, passed in checks.items() if not passed]

Open Source and the CRA

The CRA's treatment of open source software has been controversial. The final text (Art. 16) exempts software that is:

  1. Developed entirely outside of a commercial activity
  2. Not intended for commercial use by the developer

Open source components integrated into commercial products are not exempt — the commercial product manufacturer bears CRA obligations for those components. A startup building on an open source framework must maintain an SBOM that includes the framework, notify ENISA if a framework vulnerability is actively exploited in their product, and deliver security updates that address framework vulnerabilities.

The practical burden: open source ecosystem vulnerabilities (like Log4Shell, XZ Utils, or OpenSSL heartbleed) are now the manufacturer's responsibility to track, SBOM-document, and report if actively exploited in their product. This makes continuous dependency scanning (Dependabot, Renovate, OWASP Dependency-Check) a regulatory requirement, not just a good practice.

Implementation Timeline

DateCRA Milestone
10 December 2024CRA enters into force
11 September 2026Art. 14 vulnerability disclosure obligations apply
11 December 2027Full CRA enforcement — all obligations active
Ongoing from 20275-year minimum security update support periods begin

The September 2026 vulnerability disclosure deadline arrives before full enforcement — development teams should implement Art. 14 workflows and ENISA reporting capabilities before their 2027 product compliance deadline.

Conclusion

The EU Cyber Resilience Act creates four independent but interconnected developer obligations: security-by-design (Art. 11), machine-readable component transparency (Art. 13), coordinated vulnerability disclosure with hard timelines (Art. 14), and multi-year security update commitments (Art. 15). For teams building high-risk AI systems, these obligations layer on top of EU AI Act Art. 9/11/72/73 requirements — creating parallel documentation, monitoring, and incident reporting workflows that must be designed to coexist.

Infrastructure jurisdiction is not a peripheral concern under the CRA. SBOM storage, vulnerability disclosure tooling, and update delivery infrastructure all have data governance implications that differ materially between EU-native and US-cloud deployments. Teams that resolve this at the infrastructure layer — choosing EU-native hosting for compliance-critical data and update delivery — eliminate an entire category of regulatory exposure before the first line of CRA-compliance code is written.

See Also