2026-04-21·14 min read·

NIS2 Art.9–12: CSIRT Network, EU-CyCLONe, ENISA's Role, and Coordinated Vulnerability Disclosure — Developer Guide (2026)

Articles 5–8 define the national governance layer: who your NCA is, what your national CSIRT does, how the Cooperation Group produces policy. Articles 9–12 define the EU-level coordination layer that sits above the national infrastructure. This layer activates when incidents cross borders, when vulnerabilities need coordinated disclosure, and when crises require multi-state response.

Understanding Articles 9–12 matters for three practical reasons. First, EU-CyCLONe (Art.10) is the body that coordinates large-scale cyber crises — if your platform is involved in a significant incident, EU-CyCLONe may be coordinating the response even if you never interact with it directly. Second, ENISA's Art.11 mandate drives the technical guidance your national CSIRT implements — understanding ENISA's role helps you anticipate guidance before it becomes your NCA's formal requirement. Third, Art.12's CVD framework is directly actionable: it defines how you should handle vulnerability disclosures in your own software and how national CVD policies apply to your security research.

Art.9: The CSIRT Network — EU-Level Technical Coordination

Article 9 establishes the CSIRT Network — a permanent coordination body composed of national CSIRTs and ENISA's CERT-EU. The CSIRT Network is not a supranational authority; it cannot issue binding instructions to member state CSIRTs. It is a coordination and information-sharing forum.

What the CSIRT Network Does

Art.9(3) lists the CSIRT Network's tasks:

Exchange of information on CSIRTs' capabilities — member state CSIRTs maintain a shared registry of tools, techniques, and response capabilities. When a cross-border incident occurs, the responding CSIRTs can identify which network members have relevant expertise.

Facilitate operational cooperation — during active incidents, the CSIRT Network enables rapid technical information exchange. This is distinct from the Cooperation Group's strategic coordination (Art.8): CSIRT Network exchanges are operational and often classified.

Exchange information on incidents, near misses, and threats — Art.9(3)(c) creates a structured information sharing mechanism. Your incident report to your national CSIRT may, after anonymisation, contribute to CSIRT Network threat intelligence shared across all EU CSIRTs.

Share best practices — including on operational procedures, use of frameworks like MITRE ATT&CK, and sector-specific incident response playbooks.

Assist in capacity building — supporting CSIRTs in member states with lower cybersecurity maturity. This is why compliance obligations can vary in practical stringency across member states: your Belgian CSIRT operates at a different maturity level than the German BSI's CERT-Bund.

What This Means for Your Incident Data

When you file an Art.23 notification to your national CSIRT, that notification may flow into CSIRT Network intelligence. Art.9(3)(c) explicitly enables this. The anonymisation requirement means your identity is protected — but the technical indicators of compromise (IoCs) and incident patterns contribute to EU-wide threat intelligence.

The CSIRT Network also maintains the TLP (Traffic Light Protocol) framework for classified information sharing. CSIRTs communicating under TLP:RED share information that cannot be forwarded; TLP:AMBER is restricted to specific organisations. Understanding TLP helps if your incident triggers information requests from your national CSIRT: the classification level they assign affects how widely your incident data is shared across the CSIRT Network.

from dataclasses import dataclass
from enum import Enum
from typing import Optional


class TLPClassification(Enum):
    """Traffic Light Protocol levels for CSIRT Network information sharing."""
    CLEAR = "clear"        # No restriction, publicly releasable
    GREEN = "green"        # Community-wide, no public release
    AMBER = "amber"        # Limited sharing within defined organisations
    AMBER_STRICT = "amber_strict"  # Strict need-to-know within organisation
    RED = "red"            # Named recipients only, no forwarding


@dataclass
class IncidentNotificationRecord:
    """Tracks how your Art.23 notification flows through the CSIRT Network."""
    incident_id: str
    national_csirt: str
    filed_at_iso: str
    tlp_assigned: TLPClassification
    shared_to_csirt_network: bool
    anonymised_before_sharing: bool
    csirt_network_ref: Optional[str] = None  # Reference ID if network-shared

    def can_be_used_in_eu_threat_intel(self) -> bool:
        """Art.9(3)(c): anonymised incidents may flow to CSIRT Network intelligence."""
        return self.anonymised_before_sharing and self.tlp_assigned in (
            TLPClassification.CLEAR,
            TLPClassification.GREEN,
            TLPClassification.AMBER,
        )


def estimate_csirt_network_exposure(notification: IncidentNotificationRecord) -> str:
    """
    Estimates how broadly your incident data may be shared after Art.23 filing.
    Returns human-readable description.
    """
    if notification.tlp_assigned == TLPClassification.RED:
        return "Named recipients only. Will not reach CSIRT Network beyond bilateral agreements."
    if notification.tlp_assigned == TLPClassification.AMBER_STRICT:
        return "Strict need-to-know. Likely stays within national CSIRT."
    if notification.tlp_assigned == TLPClassification.AMBER:
        return "May be shared with specific CSIRT Network members under NDA."
    if notification.anonymised_before_sharing:
        return "Anonymised IoCs and patterns may enter EU-wide CSIRT Network threat intelligence."
    return "Consult your national CSIRT for specific classification guidance."

CSIRT Network vs. Cooperation Group: The Distinction

The CSIRT Network (Art.9) handles operational technical coordination — active incidents, real-time threat intelligence, response tooling. The Cooperation Group (Art.8) handles strategic policy coordination — guidelines, common frameworks, sector-specific requirements. The two bodies are complementary. A major incident might trigger CSIRT Network operational response (Art.9) while the Cooperation Group (Art.8) subsequently produces updated guidelines based on what the incident revealed.

Art.10: EU-CyCLONe — Large-Scale Cyber Crisis Coordination

Article 10 establishes the European Cyber Crisis Liaison Organisation Network (EU-CyCLONe). This is the body that activates when incidents reach the scale of large-scale cyber crises affecting multiple member states simultaneously.

What EU-CyCLONe Is

EU-CyCLONe differs from the CSIRT Network in a critical way: it bridges technical CSIRT response and political crisis management. The CSIRT Network is composed of technical experts. EU-CyCLONe includes senior officials from member state cyber crisis management authorities — in Germany, that includes the Federal Office of Civil Protection (BBK) alongside BSI; in France, it includes the Secretary-General for National Defence and Security (SGDSN) alongside ANSSI.

Art.10(1) defines the membership: national authorities responsible for cyber crisis management, ENISA (as secretariat), and the European Commission (as observer).

EU-CyCLONe's Mandate Under Art.10(2)

EU-CyCLONe's tasks are specifically scoped to large-scale incidents:

Increase preparedness — Art.10(2)(a) requires EU-CyCLONe to develop and maintain EU-level cyber crisis management capabilities, including joint exercises under Art.10(2)(c).

Ensure situational awareness — Art.10(2)(b). During a large-scale incident, EU-CyCLONe maintains a common operational picture across member states. This is the body that would coordinate EU-wide situational awareness during an event like the NotPetya campaign (2017) or the SolarWinds compromise.

Coordinate crisis management — Art.10(2)(d). When a cyber crisis requires political-level coordination between member states, EU-CyCLONe is the activation point. This can include coordinating public communications to avoid market panic, coordinating with NATO and international partners, and escalating to the EU Integrated Political Crisis Response (IPCR) if needed.

Discuss national crisis response plans — Art.10(2)(e). EU-CyCLONe reviews member states' national cyber crisis management plans to identify gaps and ensure cross-border response is coordinated.

The "Large-Scale Incident" Threshold

Art.10 activates for "large-scale cybersecurity incidents and crises." ENISA Recital 59 indicates that large-scale incidents are those with a "significant impact" on at least two member states or that exceed the capacity of one member state to respond. EU Cybersecurity Blueprint (adopted by the Council in 2017, updated in 2023) provides further operational criteria:

Your Art.23 notification triggers national CSIRT response (Art.9). If the incident is large enough, your CSIRT escalates to EU-CyCLONe level. You do not interact with EU-CyCLONe directly in most scenarios.

from dataclasses import dataclass
from typing import List


LARGE_SCALE_THRESHOLD_CRITERIA = {
    "cross_border_member_states_min": 2,
    "critical_infrastructure_sectors": [
        "energy", "transport", "banking", "financial_market",
        "health", "drinking_water", "wastewater", "digital_infrastructure",
        "ict_service_management", "public_administration", "space"
    ],
    "estimated_economic_damage_eur_min": 1_000_000,  # Indicative threshold
    "requires_political_coordination": True,
}


@dataclass
class IncidentScaleAssessment:
    affected_member_states: List[str]
    affected_sectors: List[str]
    estimated_eu_economic_impact_eur: float
    requires_public_comms_coordination: bool
    exceeds_single_ms_response_capacity: bool

    def eu_cyclone_activation_likely(self) -> bool:
        """
        Assesses whether your incident is likely to trigger EU-CyCLONe activation.
        Based on Art.10 scope and EU Cybersecurity Blueprint criteria.
        """
        cross_border = len(self.affected_member_states) >= 2
        critical_sector = any(
            s in LARGE_SCALE_THRESHOLD_CRITERIA["critical_infrastructure_sectors"]
            for s in self.affected_sectors
        )
        economic = (
            self.estimated_eu_economic_impact_eur
            >= LARGE_SCALE_THRESHOLD_CRITERIA["estimated_economic_damage_eur_min"]
        )
        return (cross_border and critical_sector) or self.exceeds_single_ms_response_capacity

    def recommended_action(self) -> str:
        if self.eu_cyclone_activation_likely():
            return (
                "Notify national CSIRT immediately under Art.23(1)(a). "
                "Expect CSIRT Network (Art.9) escalation. "
                "EU-CyCLONe may activate for cross-border coordination. "
                "Prepare for possible public communications guidance from authorities."
            )
        return (
            "File Art.23 notification with national CSIRT. "
            "Standard national response process applies. "
            "Monitor for cross-border propagation."
        )

Relationship Between CSIRT Network, EU-CyCLONe, and Cooperation Group

The three bodies form a response pyramid:

BodyArt.ScopeActivation
CSIRT Network9Operational technical responseAny cross-border incident
EU-CyCLONe10Large-scale crisis managementLarge-scale incidents across ≥2 MS
Cooperation Group8Strategic policyOngoing, plus post-incident lessons

The ENISA Blueprint establishes that CSIRT Network → EU-CyCLONe escalation follows specific criteria. Your national CSIRT makes the escalation decision; you do not need to notify EU-CyCLONe separately.

Art.11: ENISA's Role Under NIS2

Article 11 defines ENISA's specific mandate within NIS2. ENISA (European Union Agency for Cybersecurity) predates NIS2 — it was established in 2004 and its mandate was significantly expanded by the EU Cybersecurity Act (CSA, Regulation 2019/881). Art.11 of NIS2 defines the specific tasks ENISA performs in support of NIS2 implementation.

Art.11(3): ENISA's NIS2-Specific Tasks

Art.11(3) lists ENISA's tasks under NIS2:

Maintain the European vulnerability registry — Art.11(3)(a) requires ENISA to maintain an EU-level registry of disclosed vulnerabilities. This operates alongside CVE (Common Vulnerabilities and Exposures) and CERT-EU's vulnerability tracking. The European Vulnerability Database (EUVD) launched by ENISA in 2024 provides EU-specific vulnerability intelligence, including exploitation status and affected EU-regulated sectors.

Provide technical assistance to CSIRT Network — Art.11(3)(b). ENISA's CERT-EU provides direct technical support to national CSIRTs for incident response, particularly for incidents involving EU institutions or cross-border critical infrastructure.

Coordinate CVD at EU level — Art.11(3)(c). ENISA coordinates Coordinated Vulnerability Disclosure across member states, harmonising how national CVD policies interact. This is the EU-level counterpart to Art.12's national CVD obligation.

Produce guidance and reports — Art.11(3)(d)–(f). ENISA publishes technical guidelines, sector-specific risk assessments, and annual threat landscape reports (ENISA Threat Landscape, published each October). These publications inform how NCAs interpret Art.21 compliance requirements.

Support exercises — Art.11(3)(g). ENISA organises Cyber Europe exercises — large-scale EU incident response simulations. Cyber Europe 2024 tested EU-CyCLONe activation scenarios and NIS2 incident notification flows.

How ENISA Guidance Flows Into Your Compliance

ENISA's technical guidelines are not directly binding — they do not have the force of law. But they influence compliance in three ways:

  1. NCA interpretation — NCAs use ENISA guidelines when assessing Art.21 compliance. If ENISA has published a security baseline for your sector, your NCA may treat deviation from it as evidence of Art.21 non-compliance.

  2. CSIRT capacity building — ENISA trains national CSIRTs, which means ENISA's incident classification frameworks (including the NIS2 Incident Taxonomy) are the frameworks your national CSIRT uses when processing your Art.23 notification.

  3. ENISA Vulnerability Database (EUVD) — ENISA's vulnerability registry identifies which vulnerabilities are being actively exploited against EU-regulated sectors. Monitoring EUVD is a practical Art.21(2)(b) input (vulnerability handling and disclosure obligations).

import httpx
from typing import Optional


ENISA_EUVD_BASE = "https://euvd.enisa.europa.eu/api/v1"


async def check_euvd_for_product(
    product_name: str,
    vendor: str,
    version: Optional[str] = None,
) -> dict:
    """
    Queries ENISA European Vulnerability Database for product vulnerabilities.
    Monitoring EUVD is a practical Art.21(2)(b) vulnerability management input.

    Returns dict with vulnerability list and exploitation status.
    """
    params = {"product": product_name, "vendor": vendor}
    if version:
        params["version"] = version

    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.get(f"{ENISA_EUVD_BASE}/search", params=params)
        resp.raise_for_status()
        data = resp.json()

    return {
        "product": product_name,
        "vendor": vendor,
        "total_vulns": data.get("total", 0),
        "actively_exploited": [
            v for v in data.get("vulnerabilities", [])
            if v.get("exploitation_status") == "exploited"
        ],
        "eu_sector_affected": [
            v for v in data.get("vulnerabilities", [])
            if v.get("eu_regulated_sector_impact")
        ],
    }


def euvd_monitoring_checklist() -> list[str]:
    """
    Art.21(2)(b) practical checklist for EUVD-based vulnerability management.
    """
    return [
        "Subscribe to ENISA Threat Landscape annual report (October publication)",
        "Monitor EUVD for vulnerabilities in your tech stack",
        "Check EUVD 'EU Sector Impact' flag for your Annex I/II sector",
        "Integrate EUVD CVE IDs into your vulnerability management tooling",
        "Review ENISA sector-specific reports for your industry",
        "Check CERT-EU security advisories for shared EU infrastructure components",
    ]

ENISA's European Vulnerability Database vs. CVE/NVD

ENISA EUVD is not a replacement for NIST NVD or MITRE CVE — it is a complementary source with EU-specific context. Key differences:

FeatureENISA EUVDNIST NVDMITRE CVE
EU sector impact flagging✅ Yes❌ No❌ No
Exploitation status in EU✅ YesPartial❌ No
NIS2 sector relevance✅ Yes❌ No❌ No
CVSS score✅ Via CVE✅ Yes❌ No
API available✅ Yes✅ Yes✅ Yes

For NIS2 Art.21(2)(b) compliance, monitoring ENISA EUVD alongside your existing CVE/NVD feeds gives you EU-sector-specific exploitation context that NVD alone does not provide.

Art.12: Coordinated Vulnerability Disclosure

Article 12 is the most directly actionable provision in this group. It establishes a mandatory framework for Coordinated Vulnerability Disclosure (CVD) across all EU member states and creates specific obligations for entities handling vulnerability reports.

What Art.12 Requires

Art.12(1) requires each member state to adopt a national CVD policy. The policy must at minimum:

Art.12(2) requires member states to "facilitate CVD" by taking measures to ensure that legal persons can report vulnerabilities without fear of prosecution. This is a significant step: it creates a legal safe harbour for CVD in member states that implement Art.12(2) properly.

Art.12(3): ENISA's EU-Level CVD Coordination

Art.12(3) gives ENISA a coordination role at EU level:

EU CVD registry — ENISA maintains the European vulnerability registry referenced in Art.11(3)(a), which integrates with national CVD processes.

Cross-border coordination — when a vulnerability affects vendors or infrastructure in multiple member states, ENISA coordinates between national CSIRTs to ensure aligned disclosure timelines and prevent conflicting embargoes.

Reporting — ENISA reports annually to the Cooperation Group on CVD practices across member states, enabling policy harmonisation.

How Art.12 Applies to Your Software

If you develop software (SaaS platforms, APIs, SDKs), Art.12 affects you in two ways:

As a vulnerability recipient — if someone reports a vulnerability in your software, national CVD policy applies. You should have a published security.txt (RFC 9116) and a clear disclosure policy that aligns with your national CSIRT's framework.

As a vulnerability researcher — if you discover vulnerabilities in third-party software (including dependencies), national CVD policy determines how you can disclose safely.

from dataclasses import dataclass, field
from datetime import date, timedelta
from enum import Enum
from typing import Optional


class CVDStatus(Enum):
    RECEIVED = "received"
    TRIAGED = "triaged"
    VENDOR_NOTIFIED = "vendor_notified"
    FIX_IN_PROGRESS = "fix_in_progress"
    FIX_CONFIRMED = "fix_confirmed"
    DISCLOSURE_PENDING = "disclosure_pending"
    DISCLOSED = "disclosed"
    CSIRT_ESCALATED = "csirt_escalated"  # Art.12(1)(a): CSIRT coordination activated


@dataclass
class VulnerabilityDisclosure:
    """
    Tracks CVD lifecycle under Art.12 national policy framework.
    Default timeline: 90 days (de facto EU standard).
    """
    vuln_id: str
    reporter_contact: str
    affected_product: str
    received_date: date
    disclosure_deadline_days: int = 90
    status: CVDStatus = CVDStatus.RECEIVED
    vendor_notified_date: Optional[date] = None
    fix_confirmed_date: Optional[date] = None
    csirt_escalation_reason: Optional[str] = None
    notes: list[str] = field(default_factory=list)

    @property
    def disclosure_deadline(self) -> date:
        return self.received_date + timedelta(days=self.disclosure_deadline_days)

    @property
    def days_remaining(self) -> int:
        return (self.disclosure_deadline - date.today()).days

    def should_escalate_to_csirt(self) -> bool:
        """
        Art.12(1)(a): escalate to national CSIRT when vendor is unresponsive
        or when multiple organisations are affected.
        """
        if self.vendor_notified_date is None and self.days_remaining < 30:
            return True
        if self.status == CVDStatus.VENDOR_NOTIFIED and self.days_remaining < 14:
            return True
        return False

    def escalate_to_csirt(self, reason: str) -> None:
        self.status = CVDStatus.CSIRT_ESCALATED
        self.csirt_escalation_reason = reason
        self.notes.append(
            f"Escalated to national CSIRT on {date.today().isoformat()}: {reason}"
        )

    def generate_security_advisory(self) -> dict:
        """Produces CVD advisory data for public disclosure."""
        return {
            "vuln_id": self.vuln_id,
            "affected_product": self.affected_product,
            "disclosure_date": self.disclosure_deadline.isoformat(),
            "timeline": {
                "received": self.received_date.isoformat(),
                "vendor_notified": (
                    self.vendor_notified_date.isoformat()
                    if self.vendor_notified_date else None
                ),
                "fix_confirmed": (
                    self.fix_confirmed_date.isoformat()
                    if self.fix_confirmed_date else None
                ),
            },
            "cvd_policy": "Art.12 NIS2 national CVD policy",
        }


# security.txt template (RFC 9116) aligned with Art.12 CVD policy
SECURITY_TXT_TEMPLATE = """
Contact: mailto:security@{domain}
Contact: https://{domain}/security
Expires: {expires}
Encryption: https://{domain}/.well-known/pgp-key.txt
Preferred-Languages: en, de, fr
Policy: https://{domain}/security-policy
Acknowledgments: https://{domain}/security/hall-of-fame
# Art.12 NIS2 CVD Policy: 90-day disclosure timeline
# Escalation to national CSIRT: {csirt_contact}
""".strip()


def generate_security_txt(
    domain: str,
    csirt_contact: str,
    expires_days: int = 365,
) -> str:
    from datetime import datetime, timezone
    expires = (
        datetime.now(timezone.utc) + timedelta(days=expires_days)
    ).strftime("%Y-%m-%dT%H:%M:%SZ")
    return SECURITY_TXT_TEMPLATE.format(
        domain=domain,
        expires=expires,
        csirt_contact=csirt_contact,
    )

The 90-Day Disclosure Standard

NIS2 Art.12 does not mandate a specific disclosure timeline — it delegates this to national CVD policies. The practical EU standard has converged on 90 days:

The 90-day clock starts when the reporter makes first contact with the vendor (not when they discover the vulnerability). If the vendor is non-responsive within 7 days of initial contact, the national CSIRT can be engaged under Art.12(1)(a).

Art.12 Interaction with Art.21 Vulnerability Handling

Art.21(2)(b) requires entities to have "policies and procedures to assess the effectiveness of cybersecurity risk-management measures, including vulnerability handling and disclosure." Art.12 provides the framework within which Art.21(2)(b) must operate.

Practically: your Art.21(2)(b) vulnerability handling policy must align with your national CVD policy (Art.12(1)), use your national CSIRT as coordinator when needed (Art.12(1)(a)), and integrate with ENISA EUVD for cross-border context (Art.11(3)(a) + Art.12(3)).

Implementation Checklist: Art.9–12 Compliance

RequirementArticleAction
Know your national CSIRT's TLP classification practiceArt.9Check national CSIRT website for TLP policy
Understand incident escalation to EU-CyCLONeArt.10Read EU Cybersecurity Blueprint
Monitor ENISA EUVD for your tech stackArt.11(3)(a)Subscribe to EUVD API or RSS
Track ENISA Threat Landscape annual reportArt.11(3)(d)Set calendar: October each year
Publish security.txt aligned with Art.12Art.12RFC 9116 + national CVD policy reference
Implement 90-day CVD timeline as defaultArt.12(1)(b)Document in your security policy
Know your national CSIRT for CVD escalationArt.12(1)(a)Same body as Art.6/Art.23 notifications
Document CVD process in Art.21(2)(b) policyArt.21(2)(b)Cross-reference Art.12 national CVD policy

CVD Implementation: Minimum Viable Setup

For most NIS2-covered entities, a minimum viable CVD setup under Art.12 requires:

  1. Published security.txt at /.well-known/security.txt and /security.txt — RFC 9116 format with contact, policy, and expiry.

  2. Documented response timeline — 90 days default with possible extension clause for complex vulnerabilities.

  3. CSIRT escalation path — identify your national CSIRT (Art.6/Art.9) and document when you escalate unresponsive-vendor situations to them.

  4. EUVD monitoring — integrate ENISA EUVD alongside NVD/CVE for vulnerability management. The EU-sector-impact flag gives compliance-relevant context that NVD lacks.

  5. Art.21(2)(b) documentation — your vulnerability handling policy references Art.12 national CVD policy and the 90-day standard.

This is not a high-effort requirement. A security.txt, a written CVD policy (2–3 pages), and EUVD API integration into your existing vulnerability scanner covers the practical Art.12 floor for most entities.

Articles 9–12: The Coordination Architecture

Articles 9–12 complete the governance architecture that articles 5–8 began. The full structure:

LayerBodyFunction
EntityYouArt.21 controls + Art.23 reporting
NationalNCA + CSIRTSupervision + technical response
EU operationalCSIRT Network (Art.9)Cross-border technical coordination
EU crisisEU-CyCLONe (Art.10)Large-scale crisis management
EU technical/policyENISA (Art.11)Guidance, EUVD, capacity building
EU policyCooperation Group (Art.8)Strategic coordination

Your primary interaction is with the national layer. The EU-level bodies (Art.9–12) operate above it, and they affect you through the guidance ENISA produces, the threat intelligence your national CSIRT receives from the CSIRT Network, and the CVD framework Art.12 establishes.

The next articles in the NIS2 governance chapter (Art.13–20) cover specific sector coordination, ENISA's additional mandates, and the information-sharing frameworks that connect these bodies into an operational whole.