NIS2 Incident Reporting: Meeting the 24-Hour Mandate with Sentinel
NIS2's incident reporting requirements are, in practical terms, the most operationally challenging aspect of the directive. Article 23 mandates a three-phase reporting timeline that most security teams cannot meet without significant automation. This section walks through exactly how to configure Microsoft Sentinel to satisfy each deadline.
The Article 23 Timeline
Article 23 of Directive (EU) 2022/2555 establishes three mandatory reporting phases for "significant incidents":
-
Early Warning (24 hours): Within 24 hours of becoming aware of a significant incident, you must submit an early warning to the competent CSIRT. This must indicate whether the incident is suspected of being caused by unlawful or malicious acts and whether it could have cross-border impact.
-
Incident Notification (72 hours): Within 72 hours, you must submit an initial assessment including severity, impact, and indicators of compromise (IoCs) where available.
-
Final Report (1 month): Within one month, a comprehensive final report covering root cause analysis, mitigation measures, and cross-border impact assessment.
The critical word in phase one is "aware." The clock starts when your organisation knows an incident has occurred - which means your detection capabilities directly determine your compliance posture. If you can't detect in time, you can't report in time.
Defining "Significant Incident" in Sentinel
Not every alert triggers NIS2 reporting. A significant incident is defined in Article 23(3) as one that:
- Has caused or is capable of causing severe operational disruption or financial loss
- Has affected or is capable of affecting other natural or legal persons by causing considerable material or non-material damage
These translate into Sentinel severity tiers:
// KQL: Define NIS2 "significant incident" criteria
SecurityIncident
| where TimeGenerated > ago(24h)
| where Severity in ("High", "Critical")
| where Status != "Closed"
| extend IsSignificant = case(
// Data exfiltration affecting customer data
Title has_any ("exfiltration", "data theft", "mass download"), true,
// Ransomware or destructive malware
Title has_any ("ransomware", "encrypt", "wiper", "destructive"), true,
// Identity compromise of privileged accounts
Title has_any ("global admin", "privileged", "domain admin") and Title has_any ("compromise", "breach"), true,
// Service disruption
Title has_any ("denial of service", "DDoS", "service outage"), true,
// Supply chain compromise
Title has_any ("supply chain", "third party", "vendor compromise"), true,
false
)
| where IsSignificant == true
| project TimeGenerated, IncidentNumber, Title, Severity, Status, Owner
Phase 1: The 24-Hour Early Warning
The early warning must reach your national CSIRT within 24 hours. This is automated with a Sentinel automation rule that triggers a Logic App playbook.
Step 1: Create the Analytics Rule
Navigate to Microsoft Sentinel > Analytics > Create > Scheduled query rule and configure:
- Name: NIS2 Significant Incident Detection
- Severity: High
- Query frequency: Every 5 minutes
- Lookup period: 5 minutes
- Alert threshold: Greater than 0
The query should combine high-severity alerts from multiple Defender products:
// Analytics rule query for NIS2-reportable incidents
let HighSeverityAlerts = SecurityAlert
| where TimeGenerated > ago(5m)
| where AlertSeverity in ("High")
| where ProviderName in ("Microsoft Defender for Endpoint",
"Microsoft Defender for Office 365",
"Microsoft Defender for Identity",
"Microsoft Defender for Cloud Apps",
"Microsoft Entra ID Protection")
| where Status != "Resolved";
let CriticalAlerts = SecurityAlert
| where TimeGenerated > ago(5m)
| where AlertSeverity == "Critical";
union HighSeverityAlerts, CriticalAlerts
| extend NIS2Category = case(
AlertName has_any ("Ransomware", "Encryption"), "Ransomware/Destructive",
AlertName has_any ("Exfiltration", "Data theft"), "Data Breach",
AlertName has_any ("Privilege escalation", "Admin compromise"), "Identity Compromise",
"Other High Severity"
)
| summarize AlertCount = count(), Alerts = make_set(AlertName) by NIS2Category
Step 2: Create the Automation Rule and Playbook
Navigate to Microsoft Sentinel > Automation > Create > Automation rule:
- Trigger: When incident is created
- Conditions: Incident severity equals High or Critical, AND incident provider equals any Defender product
- Actions: Run playbook "NIS2-EarlyWarning-Notification"
The playbook (Logic App) should:
- Extract incident details from the Sentinel API
- Format a structured early warning following the ENISA template
- Send via authenticated email to your designated CSIRT contact
- Send internal notification to your CISO and incident commander
- Create a tracking ticket in your ITSM tool with the NIS2 timeline embedded
- Log the notification timestamp as evidence
# PowerShell snippet within the Logic App for CSIRT email formatting
$earlyWarning = @{
ReportType = "NIS2 Early Warning - Article 23(4)(a)"
OrganisationName = "Your Entity Name"
IncidentRef = $incidentNumber
DetectionTime = $incidentCreatedTime
ReportingTime = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
SuspectedMalicious = $true
CrossBorderImpact = "Under Assessment"
InitialDescription = $incidentTitle
AffectedServices = $affectedResources -join ", "
ContactPerson = "CISO Name <ciso@entity.com>"
}
$earlyWarning | ConvertTo-Json | Out-File "./nis2-early-warning-$incidentNumber.json"
Phase 2: The 72-Hour Incident Notification
By 72 hours, you need severity classification, impact assessment, and IoCs. This can be pre-populated from Sentinel investigation data:
// KQL: Generate 72-hour incident notification data
let IncidentId = "INC-12345"; // Replace with actual incident ID
SecurityIncident
| where IncidentNumber == IncidentId
| extend IoCs = parse_json(AdditionalData).IoCs
| project
IncidentNumber,
Title,
Severity,
Status,
CreatedTime,
Classification,
ClassificationComment,
Description,
AlertCount = parse_json(AdditionalData).alertsCount,
BookmarkCount = parse_json(AdditionalData).bookmarksCount,
Owner,
Labels
For IoC extraction, the related alerts can be queried:
// Extract IoCs from alerts related to the incident
SecurityAlert
| where SystemAlertId in (
SecurityIncident
| where IncidentNumber == "INC-12345"
| mv-expand AlertIds = parse_json(AlertIds)
| project tostring(AlertIds)
)
| mv-expand Entity = parse_json(Entities)
| where Entity.Type in ("ip", "dns", "filehash", "url", "mailbox")
| project
EntityType = tostring(Entity.Type),
EntityValue = case(
Entity.Type == "ip", tostring(Entity.Address),
Entity.Type == "dns", tostring(Entity.DomainName),
Entity.Type == "filehash", tostring(Entity.Value),
Entity.Type == "url", tostring(Entity.Url),
Entity.Type == "mailbox", tostring(Entity.MailboxPrimaryAddress),
"Unknown"
)
| distinct EntityType, EntityValue
Phase 3: The 1-Month Final Report
The final report requires root cause analysis, which Sentinel supports through the investigation graph and hunting queries. The report should be structured around:
- Timeline reconstruction - built from the Sentinel incident timeline
- Root cause - identified through Defender XDR advanced hunting
- Scope of impact - entities affected, data exposed, services disrupted
- Remediation actions - documented in incident comments and linked tickets
- Preventive measures - new detections, policy changes, architecture improvements
Evidence Preservation Workflow
NIS2 doesn't just require reporting - it requires that you preserve evidence for potential regulatory review. The recommended Sentinel data retention configuration:
- SecurityAlert table: 2 years (increased from default 90 days)
- SecurityIncident table: 2 years
- SigninLogs and AuditLogs: 2 years via diagnostic settings to a dedicated Log Analytics workspace
Navigate to Log Analytics workspace > Usage and estimated costs > Data Retention and set interactive retention to 365 days, total retention to 730 days.
Additionally, incident artefacts should be archived to immutable blob storage:
# Archive NIS2 incident evidence to immutable storage
$context = New-AzStorageContext -StorageAccountName "nis2evidence" -StorageAccountKey $key
$containerName = "incident-$incidentNumber"
New-AzStorageContainer -Name $containerName -Context $context
Set-AzStorageContainerImmutabilityPolicy -Container $containerName -Context $context -ImmutabilityPeriod 730
# Upload all incident artefacts
Get-ChildItem "./incident-evidence/" | ForEach-Object {
Set-AzStorageBlobContent -File $_.FullName -Container $containerName -Context $context -Blob $_.Name
}
Tabletop Exercise Design for NIS2
Regulators will ask whether you've tested your incident reporting process. Quarterly tabletops should cover these scenarios:
- Ransomware with data exfiltration - Tests the full 24/72/30-day chain, including cross-border notification where customer data spans jurisdictions
- Supply chain compromise via OAuth app - Tests MDCA detection, app governance response, and third-party notification obligations
- Insider threat with privileged access abuse - Tests PIM audit trail, Insider Risk Management alert correlation, and the "suspected malicious acts" assessment in the early warning
Each exercise produces a documented report including:
- Time from simulated detection to simulated CSIRT notification
- Gaps identified in automation or staffing
- Remediation actions with owners and deadlines
Common Failure Modes
In practice, the three most common reasons organisations fail to meet the 24-hour deadline:
-
Alert fatigue: Too many high-severity alerts desensitise the SOC. Fix this by tuning analytics rules aggressively, the target should be fewer than 10 high/critical incidents per week, with a >80% true positive rate.
-
No on-call rotation: NIS2 doesn't pause for weekends. You need 24/7 coverage or an automated first-response that buys you time.
-
Unclear escalation paths: The SOC analyst who sees the alert doesn't know who approves CSIRT notification. Define this in a RACI matrix and embed it in the playbook, the Logic App should automatically notify the designated decision-maker.
Build the automation now. When a real incident hits, you won't have time to figure out your CSIRT's email address.