2026-04-21·14 min read·

NIS2 Art.37–40: Criminal Sanctions, NCA Confidentiality, Peer Reviews, and EU-CyCLONe — Developer Guide (2026)

Articles 33–36 defined the administrative enforcement framework — fines, supervisory orders, reactive and proactive NCA powers. Articles 37–40 close out the directive's enforcement and cooperation chapters with four functionally distinct mechanisms:

For developers and compliance engineers, Art.37 is the most operationally significant: it means the same NIS2 violation can be purely administrative in one Member State and criminally prosecutable in another.

Art.37: Criminal Sanctions — Member State Discretion and Personal Liability

The Architecture of Art.37

Art.37 does not mandate criminal sanctions — it explicitly grants Member States discretion to impose them. The article reads: "Member States may lay down rules on criminal sanctions applicable to infringements of national provisions adopted pursuant to this Directive."

This discretionary structure creates a highly fragmented criminal liability landscape across the EU:

Member StateCriminal Sanctions ApproachKey Legislation
GermanyBSIG-neu §38 — criminal liability for management failures in critical infrastructureIT-Sicherheitsgesetz 3.0 (in progress)
FranceParquet national cyber (PNC) — dedicated cybercrime prosecutors, active NIS2 referral pipelineCode pénal, ANSSI referral protocol
NetherlandsNCSC referral to Openbaar Ministerie for severe NIS2 violationsWet beveiliging netwerk- en informatiesystemen 2
AustriaCybersecurity-Gesetz with criminal overlay for intentional violationsKSG + StGB overlap
SwedenNCSC referral mechanism for criminal prosecution of intentional or reckless violationsNIS2-implementeringslagen

What Constitutes a Criminalised NIS2 Violation?

Member States that have enacted criminal sanctions typically focus on:

  1. Intentional obstruction of NCA supervision — refusing access to systems or documents during an authorised audit (Art.32/33 supervisory actions)
  2. Deliberate failure to report significant incidents — knowingly suppressing Art.23 notifications
  3. Falsification of compliance documentation — submitting inaccurate information to NCAs
  4. Gross negligence in essential infrastructure — reckless failure to implement Art.21 minimum measures that causes actual harm
from dataclasses import dataclass
from enum import Enum
from typing import Optional


class SanctionType(Enum):
    ADMINISTRATIVE_ONLY = "administrative_only"
    CRIMINAL_AVAILABLE = "criminal_available"
    CRIMINAL_MANDATORY = "criminal_mandatory"


class MemberState(Enum):
    DE = "Germany"
    FR = "France"
    NL = "Netherlands"
    AT = "Austria"
    SE = "Sweden"
    IT = "Italy"
    ES = "Spain"
    PL = "Poland"


@dataclass
class CriminalLiabilityProfile:
    member_state: MemberState
    sanction_type: SanctionType
    personal_liability_scope: str
    referral_mechanism: str
    max_imprisonment_years: Optional[int]

    def is_criminal_exposure_possible(self) -> bool:
        return self.sanction_type != SanctionType.ADMINISTRATIVE_ONLY

    def requires_intent(self) -> bool:
        """Criminal liability typically requires intent or gross negligence."""
        return True


MEMBER_STATE_PROFILES = {
    MemberState.DE: CriminalLiabilityProfile(
        member_state=MemberState.DE,
        sanction_type=SanctionType.CRIMINAL_AVAILABLE,
        personal_liability_scope="Management board members, CISO for knowingly failing Art.21 measures in KRITIS",
        referral_mechanism="BSIG §38: BSI referral to Staatsanwaltschaft",
        max_imprisonment_years=3,
    ),
    MemberState.FR: CriminalLiabilityProfile(
        member_state=MemberState.FR,
        sanction_type=SanctionType.CRIMINAL_AVAILABLE,
        personal_liability_scope="Dirigeants (CEOs, CTOs) for intentional obstruction or negligent exposure of OES/DSP",
        referral_mechanism="ANSSI referral to Parquet National Cyber",
        max_imprisonment_years=5,
    ),
    MemberState.NL: CriminalLiabilityProfile(
        member_state=MemberState.NL,
        sanction_type=SanctionType.CRIMINAL_AVAILABLE,
        personal_liability_scope="Directors for willful obstruction of NCSC supervision",
        referral_mechanism="NCSC → Openbaar Ministerie referral",
        max_imprisonment_years=2,
    ),
}


def assess_criminal_exposure(
    member_state: MemberState,
    violation_type: str,
    intent_present: bool,
    is_management: bool,
) -> dict:
    profile = MEMBER_STATE_PROFILES.get(member_state)
    if not profile:
        return {
            "criminal_risk": "unknown",
            "recommendation": f"Verify NIS2 criminal sanctions implementation in {member_state.value}",
        }

    if profile.sanction_type == SanctionType.ADMINISTRATIVE_ONLY:
        return {"criminal_risk": "none", "sanction_type": "administrative_only"}

    criminal_risk = "low"
    if intent_present and is_management:
        criminal_risk = "high"
    elif intent_present or (is_management and "obstruction" in violation_type.lower()):
        criminal_risk = "medium"

    return {
        "criminal_risk": criminal_risk,
        "member_state": member_state.value,
        "referral_mechanism": profile.referral_mechanism,
        "max_imprisonment_years": profile.max_imprisonment_years,
        "personal_liability_scope": profile.personal_liability_scope,
        "recommendation": (
            "Engage criminal defence counsel alongside regulatory counsel"
            if criminal_risk == "high"
            else "Monitor NCA investigation — criminal referral possible if evidence of intent emerges"
        ),
    }

The Art.37 / Art.20 Personal Liability Intersection

Art.37 criminal exposure is highest where Art.20 management accountability has already been triggered. If an NCA finds that:

  1. Management was aware of a cybersecurity risk (evidenced by board minutes, risk assessments, audit findings)
  2. Management approved a decision not to remediate (resource allocation decisions)
  3. A significant incident subsequently occurred

...then an Art.23 failure-to-notify or Art.21 non-compliance finding creates a documented chain from management knowledge to material harm — which is precisely the factual pattern that criminal prosecutors seek.

Practice implication: Board-level documentation of cybersecurity risk decisions creates a record that can be used both to demonstrate Art.20 compliance and as evidence in criminal proceedings. The Art.20 governance documentation that protects the company from fines also creates a paper trail for individual liability.


Art.38: Confidentiality and Data Protection in NCA Supervision

What Art.38 Requires of NCAs

Art.38 imposes three distinct obligations on NCAs, single points of contact, and CSIRTs when they handle information obtained through NIS2 supervisory activities:

  1. Confidentiality of supervisory information — information obtained in the exercise of NIS2 functions must not be disclosed beyond what is necessary for the legitimate supervisory purpose
  2. Purpose limitation — supervisory information may only be used for the purposes for which it was collected
  3. Data protection compliance — processing of personal data in NIS2 supervision must comply with GDPR (for natural persons) and national implementing legislation
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
from typing import List, Optional


class InformationCategory(Enum):
    TECHNICAL_VULNERABILITY = "technical_vulnerability"
    INCIDENT_REPORT = "incident_report"
    SECURITY_AUDIT_FINDING = "security_audit_finding"
    COMMERCIAL_SECRET = "commercial_secret"
    PERSONAL_DATA = "personal_data"
    NCA_INTERNAL_ANALYSIS = "nca_internal_analysis"


class DisclosurePermission(Enum):
    PERMITTED = "permitted"
    CONDITIONAL = "conditional"
    PROHIBITED = "prohibited"


@dataclass
class SupervisoryInformation:
    category: InformationCategory
    contains_personal_data: bool
    is_commercially_sensitive: bool
    source_entity: str
    obtained_via: str
    obtained_at: datetime = field(default_factory=datetime.now)

    def assess_disclosure_permission(
        self, recipient_type: str, disclosure_purpose: str
    ) -> DisclosurePermission:
        """
        Art.38 confidentiality assessment: can this information be disclosed?
        """
        prohibited_categories = {
            InformationCategory.COMMERCIAL_SECRET,
            InformationCategory.TECHNICAL_VULNERABILITY,
        }

        if self.category in prohibited_categories:
            return DisclosurePermission.PROHIBITED

        if self.contains_personal_data:
            # Requires GDPR legal basis for disclosure
            if recipient_type not in ("other_nca", "csirt", "competent_authority"):
                return DisclosurePermission.PROHIBITED
            return DisclosurePermission.CONDITIONAL

        # Incident reports may be shared within NIS2 cooperation framework
        if (
            self.category == InformationCategory.INCIDENT_REPORT
            and recipient_type in ("other_nca", "enisa", "csirt_network")
        ):
            return DisclosurePermission.CONDITIONAL

        if recipient_type == "public":
            return DisclosurePermission.PROHIBITED

        return DisclosurePermission.PERMITTED

    def can_share_with_law_enforcement(self) -> bool:
        """Art.38(4): NCAs may share with law enforcement for criminal proceedings."""
        return self.category == InformationCategory.INCIDENT_REPORT


class Art38ComplianceChecker:
    def __init__(self, nca_jurisdiction: str):
        self.jurisdiction = nca_jurisdiction
        self.disclosure_log: List[dict] = []

    def validate_disclosure_request(
        self,
        information: SupervisoryInformation,
        recipient: str,
        purpose: str,
    ) -> dict:
        permission = information.assess_disclosure_permission(recipient, purpose)

        result = {
            "permitted": permission == DisclosurePermission.PERMITTED,
            "conditional": permission == DisclosurePermission.CONDITIONAL,
            "prohibited": permission == DisclosurePermission.PROHIBITED,
            "basis": self._determine_legal_basis(information, recipient, purpose),
            "conditions": self._get_conditions(information, permission, recipient),
        }

        self.disclosure_log.append({
            "timestamp": datetime.now().isoformat(),
            "information_category": information.category.value,
            "recipient": recipient,
            "decision": permission.value,
        })

        return result

    def _determine_legal_basis(
        self, info: SupervisoryInformation, recipient: str, purpose: str
    ) -> Optional[str]:
        if info.contains_personal_data:
            return "GDPR Art.6(1)(e) — public task in NIS2 supervisory function"
        if recipient in ("other_nca", "enisa"):
            return "NIS2 Art.38(3) — NCA cooperation mechanism"
        if recipient == "law_enforcement" and purpose == "criminal_investigation":
            return "NIS2 Art.38(4) — criminal proceedings exception"
        return None

    def _get_conditions(
        self,
        info: SupervisoryInformation,
        permission: DisclosurePermission,
        recipient: str,
    ) -> List[str]:
        if permission == DisclosurePermission.PROHIBITED:
            return ["Disclosure not permitted under Art.38"]
        conditions = []
        if info.contains_personal_data:
            conditions.append("GDPR data minimisation — share only necessary fields")
            conditions.append("Document GDPR legal basis in disclosure record")
        if permission == DisclosurePermission.CONDITIONAL:
            conditions.append("Recipient must be bound by equivalent confidentiality obligations")
            conditions.append("Purpose limitation must be documented before disclosure")
        return conditions

The Art.38 / GDPR Intersection for Developers

When NCAs process personal data obtained during NIS2 supervision (e.g., names of individuals named in incident reports, technical staff contact details from Art.23 notifications), GDPR applies in full. For developers building systems that interact with NIS2 supervisory processes:


Art.39: Peer Reviews of National Cybersecurity Strategies

What Art.39 Creates

Art.39 establishes a voluntary peer review mechanism for EU Member States' national cybersecurity strategies, capabilities, and NIS2 implementation quality. ENISA organises the reviews, which are conducted by cybersecurity experts from other Member States and ENISA staff.

The peer review framework has two levels:

  1. National cybersecurity strategy review — assessing whether Art.7 national strategies are effective and coherently implemented
  2. Implementation quality review — examining how NCAs are applying NIS2 in practice, including supervisory consistency, sector coverage, and cross-border cooperation
from dataclasses import dataclass
from typing import List
from enum import Enum


class ReviewScope(Enum):
    NATIONAL_STRATEGY = "national_strategy"
    NCA_IMPLEMENTATION = "nca_implementation"
    CSIRT_CAPABILITIES = "csirt_capabilities"
    SECTOR_COVERAGE = "sector_coverage"
    CROSS_BORDER_COOPERATION = "cross_border_cooperation"


class ReviewFinding(Enum):
    GOOD_PRACTICE = "good_practice"
    IMPROVEMENT_RECOMMENDED = "improvement_recommended"
    GAP_IDENTIFIED = "gap_identified"
    BEST_PRACTICE_CANDIDATE = "best_practice_candidate"


@dataclass
class PeerReviewResult:
    reviewed_member_state: str
    reviewing_experts_from: List[str]
    review_scope: List[ReviewScope]
    findings: List[dict]
    good_practices_identified: List[str]
    recommendations: List[str]
    review_year: int

    def has_cross_border_gaps(self) -> bool:
        return any(
            f.get("scope") == ReviewScope.CROSS_BORDER_COOPERATION.value
            and f.get("finding") == ReviewFinding.GAP_IDENTIFIED.value
            for f in self.findings
        )

    def generate_improvement_roadmap(self) -> List[dict]:
        return [
            {
                "recommendation": rec,
                "priority": "high" if "CSIRT" in rec or "cross-border" in rec.lower() else "medium",
                "responsible_body": "NCA + ENISA support",
            }
            for rec in self.recommendations
        ]

Why Art.39 Peer Reviews Matter for Developers

Peer reviews have indirect but real effects on the regulatory environment that developers operate in:

Supervisory consistency pressure: When a peer review identifies that an NCA is applying NIS2 inconsistently (e.g., granting exemptions to certain sectors that other NCAs do not), ENISA publishes the findings. This creates political pressure toward harmonisation — potentially tightening enforcement in historically lenient jurisdictions.

Good practice diffusion: Art.39(6) requires ENISA to share good practices identified in peer reviews across all Member States. A German NCA practice of requiring specific API security standards in Art.21 implementation could become a de-facto EU standard through this mechanism before formal technical guidelines are issued.

Investment in cybersecurity infrastructure: Peer reviews frequently identify gaps in national CSIRT capabilities, threat intelligence sharing, and sector-specific oversight. Countries that receive peer review findings typically increase investment in these areas — affecting the operational environment that regulated entities work within.


Art.40: EU-CyCLONe — Cyber Crisis Liaison Organisation Network

The Three-Layer Crisis Coordination Architecture

NIS2 creates a three-layer structure for EU-level cybersecurity coordination:

┌─────────────────────────────────────────────────────────────┐
│          COOPERATION GROUP (Art.14)                         │
│   Strategic / Policy / Long-term threat landscape           │
│   Members: NCAs, Commission, ENISA (observer)               │
│   Meets: Regular intervals, not crisis-responsive           │
├─────────────────────────────────────────────────────────────┤
│          CSIRT NETWORK (Art.15)                             │
│   Technical / Operational / Incident response               │
│   Members: National CSIRTs + CERT-EU                        │
│   Meets: Regular + ad-hoc for significant incidents         │
├─────────────────────────────────────────────────────────────┤
│          EU-CyCLONe (Art.40)                                │
│   Crisis management / Large-scale cross-border incidents    │
│   Members: National cyber crisis authorities                │
│   Meets: Crisis-triggered + at least 1x/year exercise       │
└─────────────────────────────────────────────────────────────┘

EU-CyCLONe (formerly IICB-coordinated, now a formal NIS2 body) fills the gap between technical CSIRT coordination and strategic policy response. It activates when an incident or threat exceeds what a single Member State or the CSIRT Network can handle operationally.

Art.40 Activation Criteria

EU-CyCLONe activates for large-scale cybersecurity incidents or crises — defined in the NIS2 implementing framework as incidents that:

  1. Affect at least three EU Member States simultaneously
  2. Have actual or potential significant impact on critical infrastructure, the internal market, or public security
  3. Exceed the technical response capacity of the affected Member States' individual CSIRTs
  4. Require coordinated political and operational response (not just technical mitigation)
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import List, Optional


class CrisisLevel(Enum):
    NATIONAL = "national"           # Handled by Member State NCA/CSIRT alone
    BILATERAL = "bilateral"         # Two-Member-State coordination via CSIRT Network
    EU_CYCLONE = "eu_cyclone"       # Large-scale, 3+ Member States, Art.40 activation
    INTEGRATED_CRISIS = "integrated_crisis"  # EU-CyCLONe + Council Crisis Mechanism


@dataclass
class CyberCrisisEvent:
    name: str
    affected_member_states: List[str]
    sectors_affected: List[str]
    estimated_impact: str
    detected_at: datetime = field(default_factory=datetime.now)
    csirt_capacity_exceeded: bool = False
    political_coordination_required: bool = False

    def classify_crisis_level(self) -> CrisisLevel:
        if len(self.affected_member_states) < 2:
            return CrisisLevel.NATIONAL
        if len(self.affected_member_states) == 2 and not self.csirt_capacity_exceeded:
            return CrisisLevel.BILATERAL
        if len(self.affected_member_states) >= 3 or self.csirt_capacity_exceeded:
            if self.political_coordination_required:
                return CrisisLevel.INTEGRATED_CRISIS
            return CrisisLevel.EU_CYCLONE
        return CrisisLevel.BILATERAL

    def requires_eu_cyclone_activation(self) -> bool:
        level = self.classify_crisis_level()
        return level in (CrisisLevel.EU_CYCLONE, CrisisLevel.INTEGRATED_CRISIS)

    def get_activation_rationale(self) -> str:
        if self.requires_eu_cyclone_activation():
            return (
                f"EU-CyCLONe activation required: "
                f"{len(self.affected_member_states)} Member States affected "
                f"({'CSIRT capacity exceeded' if self.csirt_capacity_exceeded else '3+ MS threshold met'})"
            )
        return f"Crisis level {self.classify_crisis_level().value} — EU-CyCLONe not required"


@dataclass
class EUCyCLONeResponse:
    crisis: CyberCrisisEvent
    coordinating_presidency: str
    enisa_support_requested: bool
    commission_briefed: bool
    situational_awareness_reports: List[dict] = field(default_factory=list)
    activated_at: Optional[datetime] = None

    def activate(self) -> dict:
        self.activated_at = datetime.now()
        return {
            "activation_time": self.activated_at.isoformat(),
            "crisis": self.crisis.name,
            "affected_ms": self.crisis.affected_member_states,
            "coordinating_presidency": self.coordinating_presidency,
            "enisa_support": self.enisa_support_requested,
            "next_step": "Situational awareness report within 4h of activation",
        }

    def add_situational_report(self, content: str, authors: List[str]) -> None:
        self.situational_awareness_reports.append({
            "timestamp": datetime.now().isoformat(),
            "content": content,
            "authors": authors,
            "report_number": len(self.situational_awareness_reports) + 1,
        })


# Example: Ransomware campaign across 5 EU energy sector operators
energy_ransomware_crisis = CyberCrisisEvent(
    name="Ransomware campaign: EU energy grid operators",
    affected_member_states=["DE", "FR", "NL", "BE", "AT"],
    sectors_affected=["energy", "electricity_distribution"],
    estimated_impact="Potential disruption to distribution networks in 5 Member States",
    csirt_capacity_exceeded=True,
    political_coordination_required=True,
)

print(energy_ransomware_crisis.classify_crisis_level())  # INTEGRATED_CRISIS
print(energy_ransomware_crisis.get_activation_rationale())

EU-CyCLONe and the Developer's Crisis Response Obligations

For developers building systems in Art.3 covered entities, EU-CyCLONe activation has concrete operational implications:

Increased NCA contact tempo: During an EU-CyCLONe event, NCAs will contact affected covered entities more frequently and with shorter response windows. An Art.23 "significant incident" notification that normally has a 72-hour initial report window may be accelerated by NCA request during a declared crisis.

Mandatory technical cooperation with CSIRTs: EU-CyCLONe coordinates CSIRT assistance. If your sector is affected, your national CSIRT may request:

Cross-border incident linkage: EU-CyCLONe's situational awareness function actively identifies whether individual incidents reported to NCAs are part of a coordinated campaign. If your incident is linked to a larger EU-CyCLONe event, your NCA may share your incident report details with other Member State NCAs under Art.38 confidentiality constraints.


Putting Art.37–40 Together: The Enforcement Architecture Complete

NIS2 ENFORCEMENT & COOPERATION COMPLETE ARCHITECTURE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

INCIDENT OCCURS
     │
     ├─→ Art.23 Notification ──→ NCA / CSIRT receives
     │                              │
     │              ┌───────────────┴────────────────┐
     │              │                                │
     │        Administrative                   Criminal
     │        Enforcement                      Referral
     │         (Art.32/33)                     (Art.37)
     │              │                                │
     │    ┌─────────┴─────────┐            National prosecutor
     │    │                   │            (DE: Staatsanwaltschaft
     │    Essential         Important      FR: Parquet National Cyber
     │    Entity             Entity        NL: Openbaar Ministerie)
     │    Art.32             Art.33                  │
     │    (proactive)        (reactive)              │
     │         │                   │                 │
     │    Art.35 fines       Art.36 fines      Art.37 criminal
     │    €10M/2%            €7M/1.4%          proceedings
     │         │                   │
     │         └────────┬──────────┘
     │                  │
     │           Art.38 confidentiality applies
     │           (NCA cannot disclose supervisory
     │            data beyond legitimate purpose)
     │
LARGE-SCALE
CROSS-BORDER
INCIDENT?
     │
     └─→ 3+ Member States? → EU-CyCLONe (Art.40) activates
                               + Cooperation Group (Art.14)
                               + CSIRT Network (Art.15)
                               + ENISA support (Art.39 peer review data)

Developer Compliance Checklist: Art.37–40

Art.37 — Criminal Sanctions Preparedness

Art.38 — Confidentiality in NCA Interactions

Art.39 — Peer Reviews (Indirect Impact)

Art.40 — EU-CyCLONe Crisis Readiness


Summary

Articles 37–40 complete NIS2's enforcement architecture by addressing the dimensions that purely administrative frameworks leave open:

For the average developer maintaining a covered service, Art.37 is the high-priority article: criminal exposure risk should be assessed for each jurisdiction of operation, and the Art.20 governance documentation that protects the company is the same documentation that documents individual management knowledge.


This post is part of the NIS2 Directive series covering all articles with developer-focused Python implementations.