NIS2 Art.21(2)(a): Risk Analysis and Information Security Policies — SaaS Developer Guide (2026)
Before you can handle incidents, secure your supply chain, or measure control effectiveness, you need to know what you are protecting and why it matters. NIS2 Directive Art.21(2)(a) establishes this foundation: risk analysis and information system security policies are the first and architecturally primary measure in the ten mandatory Art.21(2) requirements.
Art.21(2)(a) is not optional context for the other nine measures — it is the decision framework that determines which controls are required, at what strength, and why. Without a documented risk analysis, every other NIS2 compliance argument collapses under NCA audit scrutiny. Auditors in June 2026 will not accept control implementations without a risk register that explains why those controls were chosen.
This guide builds the Art.21(2)(a) framework for SaaS development teams: ISO 27005 methodology, a production-ready Risk Register, a Risk Appetite Statement, CVSS-integrated scoring, an ISMS Policy Framework, a Python NIS2RiskAssessor, and a 25-item checklist for audit readiness.
1. Art.21(2)(a) in the Full NIS2 Context
NIS2 Art.21(2) mandates ten cybersecurity risk-management measures. Art.21(2)(a) is the first — and the logical prerequisite for all others.
The Ten Mandatory Measures
| Subparagraph | Requirement | Primary Owner |
|---|---|---|
| Art.21(2)(a) | Risk analysis and information system security policies | CISO / Management |
| Art.21(2)(b) | Incident handling (see incident handling guide) | SOC / DevSecOps |
| Art.21(2)(c) | Business continuity, backup management, disaster recovery (see BCM guide) | Ops / SRE |
| Art.21(2)(d) | Supply chain security | Procurement / DevSecOps |
| Art.21(2)(e) | Security in acquisition, development and maintenance (see SDL guide) | Engineering |
| Art.21(2)(f) | Policies to assess effectiveness of cybersecurity measures (see effectiveness guide) | Audit / GRC |
| Art.21(2)(g) | Basic cyber hygiene and training | HR / Security Awareness |
| Art.21(2)(h) | Cryptography and encryption policies (see cryptography guide) | Architecture |
| Art.21(2)(i) | HR security, access control and asset management (see IAM guide) | IT / HR / Engineering |
| Art.21(2)(j) | Multi-factor authentication and continuous authentication (see MFA guide) | IT / IAM / Engineering |
Art.21(2)(a) is the decision engine. Every other measure answers: "How do we protect against the risks we identified in (a)?"
The Exact Regulatory Text
Art.21(2)(a) requires:
"policies on risk analysis and information system security"
ENISA's technical guidelines expand this into three operational requirements:
- Risk analysis process — a repeatable, documented methodology for identifying, assessing, and treating information security risks
- Risk register — a maintained inventory of identified risks, their likelihood, impact, owner, and treatment status
- Information security policy — a formal management-approved policy that defines security objectives, scope, roles, and compliance obligations
The Recital 79 of NIS2 reinforces that risk management must be proportionate to risk exposure — NCA auditors expect evidence of proportionality, not just existence of a policy document.
2. Why SaaS Developers Are the Primary Audience
Risk analysis for SaaS organisations spans layers that traditional enterprise frameworks often miss: cloud provider shared responsibility, ephemeral infrastructure, multi-tenant attack surfaces, and CI/CD pipeline exposure.
The SaaS-Specific Risk Landscape
A SaaS company's risk surface differs from on-premise organisations in four ways:
1. Infrastructure is code — The risk profile changes with every Terraform commit. Risk analysis must be integrated into the development lifecycle, not performed annually in isolation.
2. Cloud shared responsibility — AWS/GCP/Azure handle physical security and hypervisor isolation, but encryption key management, IAM configuration, and logging are the tenant's responsibility. Risk analysis must explicitly address the boundary.
3. Multi-tenancy attack surface — A breach affecting one customer's data in a shared database may trigger NIS2 incident reporting and GDPR Art.33 simultaneously. Cross-tenant isolation is a risk treatment control that must appear in the register.
4. Third-party dependency velocity — npm, PyPI, and container registries update continuously. Supply chain risk (Art.21(2)(d)) depends on a risk analysis that identifies which dependencies have high blast radius.
Developer Ownership of Risk
Developers own many of the controls that treat the risks in the register:
- SAST/DAST pipelines (treatment for code injection risks)
- Dependency version pinning and scanning (treatment for supply chain risks)
- Secrets management (treatment for credential exposure risks)
- Database encryption and access controls (treatment for data breach risks)
- Infrastructure drift detection (treatment for misconfiguration risks)
Without developer participation in risk analysis, the Risk Register describes controls that no one has committed to implementing.
3. ISO 27005 Risk Assessment Methodology
NIS2 does not mandate a specific methodology, but ISO 27005:2022 ("Information security, cybersecurity and privacy protection — Guidance on managing information security risks") is the ENISA-preferred framework and the most commonly accepted by NCA auditors.
The ISO 27005 Process
┌─────────────────────────────────────────────┐
│ RISK IDENTIFICATION │
│ Assets → Threats → Vulnerabilities │
│ → Initial Risk Scenarios │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ RISK ANALYSIS │
│ Likelihood × Impact = Inherent Risk Level │
│ (Qualitative 1-5 or Quantitative ALE) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ RISK EVALUATION │
│ Inherent Risk vs. Risk Appetite │
│ → Accept / Treat / Transfer / Avoid │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ RISK TREATMENT │
│ Control Selection → Risk Treatment Plan │
│ → Statement of Applicability (SOA) │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ MONITORING & REVIEW │
│ Quarterly Review → Annual Full Reassessment │
│ → Control Effectiveness (feeds Art.21(2)(f))│
└─────────────────────────────────────────────┘
Asset Inventory as Risk Analysis Input
Risk analysis begins with an asset inventory. For SaaS organisations, the critical asset categories are:
| Asset Category | Examples | NIS2 Relevance |
|---|---|---|
| Customer data stores | Production databases, data warehouses | Data breach → Art.23 reporting |
| Authentication systems | OAuth providers, MFA infrastructure | Account takeover → Art.21(2)(j) |
| CI/CD pipelines | GitHub Actions, build artifacts, container registries | Supply chain → Art.21(2)(d) |
| Cloud infrastructure | AWS accounts, Kubernetes clusters, VPCs | Availability → Art.21(2)(c) |
| Internal tools | Admin panels, support dashboards | Privilege escalation → Art.21(2)(i) |
| Third-party integrations | Payment processors, email providers, CDN | Supply chain → Art.21(2)(d) |
Each asset category gets a criticality rating (1-5) based on data sensitivity, availability requirements, and regulatory exposure.
Threat Catalogue
NCA auditors expect evidence that your threat identification was systematic, not ad-hoc. The ENISA Threat Landscape and MITRE ATT&CK for Enterprise provide the reference catalogues.
Key threat categories for SaaS under NIS2:
| Threat Category | Representative Threat | MITRE ATT&CK Tactic |
|---|---|---|
| Malicious code | Ransomware, supply chain malware | Execution, Impact |
| Social engineering | Phishing, business email compromise | Initial Access |
| Web application attacks | SQL injection, SSRF, account takeover | Initial Access, Credential Access |
| Insider threat | Privilege abuse, data exfiltration | Collection, Exfiltration |
| Cloud misconfiguration | Public S3 bucket, exposed admin API | Initial Access, Discovery |
| DDoS / availability attack | Volumetric flood, application layer L7 | Impact |
| Third-party / supply chain | Compromised npm package, CI/CD credential leak | Initial Access |
Likelihood and Impact Scales
Use consistent, defined scales. A 5-point qualitative scale is sufficient for most SaaS organisations at initial ISMS implementation:
Likelihood (L):
| Score | Label | Definition |
|---|---|---|
| 1 | Rare | Less than once every 5 years |
| 2 | Unlikely | Once every 2-5 years |
| 3 | Possible | Once every 1-2 years |
| 4 | Likely | Once per year |
| 5 | Almost Certain | More than once per year |
Impact (I):
| Score | Label | Definition |
|---|---|---|
| 1 | Negligible | No customer impact, no regulatory notification |
| 2 | Minor | Limited customer impact, internal resolution |
| 3 | Moderate | Subset of customers affected, internal escalation |
| 4 | Major | Significant customer data breach or service disruption, Art.23 threshold likely met |
| 5 | Critical | Complete service loss or mass data breach, NCA mandatory notification, potential fines |
Risk Level = L × I (1-25 scale):
| Risk Level | Range | Action |
|---|---|---|
| Critical | 20-25 | Treat immediately, escalate to management |
| High | 12-19 | Treat within 30 days |
| Medium | 6-11 | Treat within 90 days |
| Low | 1-5 | Accept or treat within 12 months |
4. Risk Register Format
The Risk Register is the primary evidence artefact for Art.21(2)(a). NCA auditors expect a maintained, versioned register — not a point-in-time spreadsheet that was last updated before the audit.
YAML Risk Register Template
# NIS2 Art.21(2)(a) Risk Register
# Version: 1.0
# Last Updated: 2026-04-16
# Next Review: 2026-07-16
# Owner: CISO
# Approved: Board / Management 2026-04-01
risks:
- id: RISK-001
title: "Ransomware attack on production database"
asset: "Customer data store (PostgreSQL, production)"
asset_criticality: 5
threat: "Malicious code — ransomware"
threat_actor: "Financially motivated external attacker"
vulnerability: "Insufficient network segmentation, backup restore not tested"
inherent:
likelihood: 3
impact: 5
level: 15 # HIGH
treatment: treat
controls:
- "Database network isolation (VPC private subnet)"
- "Daily encrypted backups with 30-day retention"
- "Quarterly backup restore test (Art.21(2)(c))"
- "EDR on all compute instances"
- "Immutable backup storage (S3 Object Lock)"
residual:
likelihood: 2
impact: 4
level: 8 # MEDIUM
risk_owner: "Head of Infrastructure"
treatment_deadline: "2026-06-01"
review_date: "2026-07-01"
nca_evidence: "backup-restore-test-2026-Q1.pdf, edr-deployment-evidence.pdf"
- id: RISK-002
title: "Supply chain compromise via malicious npm package"
asset: "CI/CD pipeline, production application"
asset_criticality: 4
threat: "Supply chain attack — malicious dependency"
threat_actor: "Nation-state, financially motivated attacker"
vulnerability: "Unpinned dependency versions, no SCA scanning in CI/CD"
inherent:
likelihood: 3
impact: 4
level: 12 # HIGH
treatment: treat
controls:
- "Dependabot / Snyk SCA in CI/CD pipeline (Art.21(2)(e))"
- "Dependency version pinning in package-lock.json"
- "npm audit in pre-commit hook"
- "SBOM generation on each release"
residual:
likelihood: 2
impact: 3
level: 6 # MEDIUM
risk_owner: "Head of Engineering"
treatment_deadline: "2026-05-01"
review_date: "2026-07-01"
nca_evidence: "sca-pipeline-config.pdf, dependabot-alerts-q1-2026.pdf"
- id: RISK-003
title: "Credential exposure via secrets in git repository"
asset: "Cloud infrastructure credentials, API keys, database passwords"
asset_criticality: 5
threat: "Insider threat / external attacker — credential harvesting"
threat_actor: "External attacker, accidental insider"
vulnerability: "No automated secrets scanning, developer habit of committing .env files"
inherent:
likelihood: 4
impact: 5
level: 20 # CRITICAL
treatment: treat
controls:
- "GitGuardian / truffleHog in CI/CD and pre-commit"
- "AWS Secrets Manager / HashiCorp Vault for production credentials"
- "Annual developer security training on secrets management (Art.21(2)(g))"
- "Repository scanning for historical commits"
residual:
likelihood: 2
impact: 5
level: 10 # MEDIUM
risk_owner: "Head of Engineering"
treatment_deadline: "2026-04-30"
review_date: "2026-06-01"
nca_evidence: "secrets-scanning-config.pdf, vault-deployment-evidence.pdf"
- id: RISK-004
title: "Multi-tenant data isolation failure"
asset: "Customer data, multi-tenant database"
asset_criticality: 5
threat: "Web application attack — IDOR, SQL injection, RLS bypass"
threat_actor: "Malicious customer, external attacker"
vulnerability: "Inadequate row-level security, missing tenant ID validation"
inherent:
likelihood: 2
impact: 5
level: 10 # MEDIUM
treatment: treat
controls:
- "PostgreSQL Row-Level Security (RLS) on all tenant tables"
- "Automated RLS bypass testing in CI/CD (DAST)"
- "Tenant ID validation in all API handlers"
- "SAST checks for missing tenant context in queries"
residual:
likelihood: 1
impact: 5
level: 5 # LOW
risk_owner: "Head of Engineering"
treatment_deadline: "2026-05-15"
review_date: "2026-07-01"
nca_evidence: "rls-policy-audit.pdf, dast-scan-results-q1-2026.pdf"
- id: RISK-005
title: "Unavailability of service due to cloud provider outage"
asset: "SaaS application, customer-facing APIs"
asset_criticality: 4
threat: "Natural events / technical failure — cloud provider outage"
threat_actor: "Environmental / technical (non-adversarial)"
vulnerability: "Single-region deployment, no cross-region failover"
inherent:
likelihood: 2
impact: 4
level: 8 # MEDIUM
treatment: treat
controls:
- "Multi-AZ deployment within primary region"
- "Cross-region read replicas for critical databases"
- "Documented RTO/RPO targets in BCM policy (Art.21(2)(c))"
- "SLA monitoring with automated alerts"
residual:
likelihood: 1
impact: 3
level: 3 # LOW
risk_owner: "Head of Infrastructure"
treatment_deadline: "2026-06-30"
review_date: "2026-07-01"
nca_evidence: "bcm-policy-v1.pdf, multi-az-architecture-diagram.pdf"
Risk Register Management Requirements
NCA auditors check three things beyond the register content:
- Version history — the register must show when risks were added, modified, or closed
- Review cadence — quarterly minimum for Critical/High risks, annual for all risks
- Management sign-off — formal approval signature/timestamp from authorised management
A Risk Register in a git repository satisfies all three: commit history provides version control, dates are auditable, and signed commits or merge approvals provide management sign-off evidence.
5. Risk Appetite Statement
The Risk Appetite Statement defines the boundary between acceptable and unacceptable risk exposure. It is the threshold that determines whether identified risks require treatment or can be accepted. Without it, the Risk Register has no decision criterion.
Risk Appetite Statement Template
# Risk Appetite Statement
**Organisation:** [Company Name]
**Version:** 1.0
**Approved by:** [CEO / Board]
**Date:** 2026-04-01
**Review Date:** 2027-04-01
## Statement
[Company Name] accepts a LOW risk appetite for information security risks affecting:
- Customer personal data (GDPR Art.5 data minimisation and integrity obligations)
- Service availability above 99.5% monthly uptime SLA
- Compliance with NIS2 Directive Art.21 and Art.23 obligations
[Company Name] accepts a MEDIUM risk appetite for:
- Internal operational disruptions that do not affect customer data or availability
- New technology adoption where security controls are in active development
[Company Name] accepts a HIGH risk appetite for:
- Innovation, product development, and feature velocity that does not increase the attack surface
- Non-sensitive internal process inefficiencies
## Quantitative Boundaries
| Risk Category | Maximum Accepted Residual Risk Level | Action if Exceeded |
|---|---|---|
| Customer data breach | LOW (≤5) | Treat immediately. Escalate to Board. |
| Service unavailability | MEDIUM (≤11) | Treat within 30 days. |
| Regulatory non-compliance | LOW (≤5) | Treat immediately. Legal review. |
| Internal operational | HIGH (≤19) | Document and monitor. |
## Annual Loss Expectancy Threshold
For risks where quantitative ALE calculation is applied, the organisation will not accept
any single risk scenario with ALE > €50,000 without documented treatment in progress.
## Exceptions
Exceptions to this Risk Appetite Statement require:
- Written justification by Risk Owner
- CISO approval
- Board notification for risks with Impact ≥ 4
- Annual review of all accepted exceptions
6. CVSS-Integrated Risk Scoring
For technical vulnerabilities — particularly those identified through penetration testing (Art.21(2)(f)) or SCA scanning (Art.21(2)(d)) — CVSS v3.1 provides a standardised scoring foundation that integrates naturally into the Risk Register.
Mapping CVSS to ISO 27005 Risk Levels
CVSS provides the inherent vulnerability severity. The Risk Register adds business context (asset criticality, threat actor capability, compensating controls) to derive the residual risk level.
| CVSS Score | Severity | Recommended Risk Register Likelihood | Impact Adjustment |
|---|---|---|---|
| 9.0–10.0 | Critical | 5 (Almost Certain if exposed) | Use asset criticality |
| 7.0–8.9 | High | 4 (Likely) | Use asset criticality |
| 4.0–6.9 | Medium | 3 (Possible) | Use asset criticality |
| 0.1–3.9 | Low | 1-2 | Use asset criticality |
CVSS Environmental Score for NIS2 Context
CVSS v3.1 Environmental metrics allow organisations to adjust the base score for their specific deployment context. For NIS2 purposes, the most relevant adjustments:
- Confidentiality Requirement (CR): Set to HIGH for customer personal data, MEDIUM for internal data
- Integrity Requirement (IR): Set to HIGH for payment systems, authentication systems
- Availability Requirement (AR): Set to HIGH for systems with >99.5% SLA obligations
# Example: Environmental Score Adjustment
# Base CVSS: 8.1 (HIGH) — SQL injection in API endpoint
# Asset: Customer database (personal data, GDPR scope)
# Environmental adjustment:
# CR: HIGH (personal data) → increases score
# IR: HIGH (data integrity required)
# AR: MEDIUM (availability SLA 99.5%)
# Adjusted CVSS: 9.4 (CRITICAL) → Risk Register: CRITICAL, treat immediately
cvss_base = 8.1
cr_multiplier = 1.5 # HIGH confidentiality requirement
adjusted = min(cvss_base * (cr_multiplier / 1.0), 10.0)
# → 9.4 CRITICAL in organisational context
SLA-Based Remediation from CVSS
The Risk Treatment Plan should define binding remediation SLAs tied to CVSS severity:
| CVSS Severity | SLA | Escalation |
|---|---|---|
| Critical (9.0+) | 7 days | CISO + Management notification |
| High (7.0-8.9) | 14 days | CISO notification |
| Medium (4.0-6.9) | 30 days | Risk Register update |
| Low (0.1-3.9) | 90 days | Risk Register update |
These SLAs feed directly into the Art.21(2)(f) effectiveness assessment KPIs (MTTR by severity).
7. ISMS Policy Framework
The Risk Analysis operates within an ISMS (Information Security Management System) Policy Framework. Art.21(2)(a) requires both the risk analysis process and the information system security policies — these are distinct but linked documents.
ISMS Policy Hierarchy
┌────────────────────────────────────────────┐
│ INFORMATION SECURITY POLICY │
│ (Top-level, board-approved, 1-3 pages) │
│ Defines scope, objectives, principles, │
│ management commitment, compliance │
└──────────────┬─────────────────────────────┘
│
┌─────────┴─────────┐
│ │
┌────▼────────┐ ┌───────▼──────────────┐
│ RISK MGMT │ │ TOPIC-SPECIFIC │
│ POLICY │ │ POLICIES │
│ │ │ │
│ Risk Analysis│ │ - Access Control │
│ Methodology │ │ - Cryptography │
│ Risk Appetite│ │ - Incident Response │
│ Review Cycle │ │ - BCM/DR │
│ Treatment │ │ - Supply Chain │
│ Escalation │ │ - Acceptable Use │
└──────┬──────┘ └───────┬──────────────┘
│ │
┌──────▼─────────────────▼──────────────┐
│ OPERATING PROCEDURES │
│ Specific "how to" for each policy │
│ (Run books, playbooks, checklists) │
└───────────────────────────────────────┘
Information Security Policy Template
# Information Security Policy
**Classification:** Internal
**Version:** 1.0
**Owner:** CISO
**Approved by:** [CEO/Board]
**Approval Date:** 2026-04-01
**Review Date:** 2027-04-01
## 1. Purpose and Scope
This policy establishes the information security objectives and management commitment
for [Company Name] in compliance with NIS2 Directive Art.21, ISO/IEC 27001:2022,
and applicable national cybersecurity legislation.
**Scope:** All information systems, data assets, and personnel (employees, contractors,
third parties) involved in processing customer data or operating essential/important
services as defined under NIS2.
## 2. Information Security Objectives
[Company Name] commits to:
- Protecting the confidentiality, integrity, and availability of customer data
- Meeting NIS2 Art.21 mandatory cybersecurity risk-management measures
- Achieving and maintaining ISO/IEC 27001:2022 certification by [target date]
- Reporting significant incidents to [national NCA] within 24 hours per Art.23
- Conducting annual risk assessments and quarterly risk register reviews
## 3. Management Commitment
The Board of [Company Name] approves the allocation of resources for information
security risk management, including:
- Designated CISO with board-level reporting line
- Annual security budget allocation
- Security awareness training for all personnel (Art.21(2)(g))
- External audit/pentest budget (Art.21(2)(f))
## 4. Roles and Responsibilities
| Role | Responsibilities |
|---|---|
| Board / Management | Approve policy, risk appetite, resource allocation |
| CISO | Own ISMS, maintain risk register, report to board |
| Risk Owners | Manage assigned risks, report residual risk status |
| All Personnel | Comply with policies, report security incidents |
| DevSecOps | Implement technical controls in Art.21(2)(d)(e)(f) |
## 5. Compliance Obligations
- NIS2 Directive (EU) 2022/2555 — Art.21 (risk management), Art.23 (incident reporting)
- GDPR (EU) 2016/679 — Art.32 (security of processing)
- ISO/IEC 27001:2022 — ISMS framework
- [National NCA implementing legislation]
## 6. Policy Review
This policy is reviewed annually or upon:
- Significant changes to the threat landscape
- New regulatory requirements
- Material changes to information systems or business operations
- Following a significant security incident
## 7. Exceptions
Exceptions require CISO approval and must be documented in the risk register
with compensating controls and a defined expiry date.
Statement of Applicability (SOA)
The SOA maps ISO 27001 Annex A controls (or NIS2 Art.21(2) measures) to implementation status. It answers the question: "Which controls apply, why, and are they implemented?"
# Statement of Applicability — NIS2 Art.21(2)
# Version: 1.0
# Date: 2026-04-16
controls:
- id: "NIS2-Art.21(2)(a)"
title: "Risk analysis and information system security policies"
applicable: true
justification: "Foundation of all NIS2 compliance — mandatory for all entities"
implementation_status: "implemented"
evidence: "risk-register-v1.yaml, information-security-policy-v1.pdf"
- id: "NIS2-Art.21(2)(b)"
title: "Incident handling"
applicable: true
justification: "Essential service — significant incident reporting obligation"
implementation_status: "implemented"
evidence: "ir-policy-v1.pdf, ir-playbook-v1.pdf"
- id: "NIS2-Art.21(2)(c)"
title: "Business continuity, backups, disaster recovery"
applicable: true
justification: "Customer data protection and availability SLA requirements"
implementation_status: "implemented"
evidence: "bcm-policy-v1.pdf, backup-restore-test-q1-2026.pdf"
- id: "NIS2-Art.21(2)(d)"
title: "Supply chain security"
applicable: true
justification: "npm/PyPI/container dependency exposure"
implementation_status: "in-progress"
evidence: "sca-pipeline-config.pdf"
gap: "SBOM generation not yet automated for all services"
treatment_deadline: "2026-05-01"
- id: "NIS2-Art.21(2)(e)"
title: "Security in acquisition, development and maintenance"
applicable: true
justification: "Software development as core business activity"
implementation_status: "implemented"
evidence: "sdl-policy-v1.pdf, sast-dast-pipeline-config.pdf"
- id: "NIS2-Art.21(2)(f)"
title: "Effectiveness assessment of cybersecurity measures"
applicable: true
justification: "Required to validate all other controls"
implementation_status: "in-progress"
evidence: "effectiveness-assessment-policy-v1.pdf"
gap: "First annual pentest scheduled Q2 2026"
treatment_deadline: "2026-06-15"
- id: "NIS2-Art.21(2)(g)"
title: "Cyber hygiene and training"
applicable: true
justification: "All entities must provide basic security training"
implementation_status: "planned"
gap: "Annual security awareness training not yet formalised"
treatment_deadline: "2026-05-31"
- id: "NIS2-Art.21(2)(h)"
title: "Cryptography and encryption policies"
applicable: true
justification: "Customer data encryption at rest and in transit required"
implementation_status: "implemented"
evidence: "cryptography-policy-v1.pdf, tls-config-audit.pdf"
- id: "NIS2-Art.21(2)(i)"
title: "HR security, access control, asset management"
applicable: true
justification: "Multi-tenant SaaS with privileged access to customer data"
implementation_status: "implemented"
evidence: "iam-policy-v1.pdf, access-review-q1-2026.pdf"
- id: "NIS2-Art.21(2)(j)"
title: "Multi-factor authentication"
applicable: true
justification: "Administrative and developer access to production systems"
implementation_status: "implemented"
evidence: "mfa-policy-v1.pdf, mfa-coverage-report-q1-2026.pdf"
8. Python NIS2RiskAssessor
The following Python class automates risk register maintenance, residual risk calculation, and NCA audit evidence generation.
#!/usr/bin/env python3
"""
NIS2RiskAssessor — Risk Register Manager for Art.21(2)(a) Compliance
Automates: risk scoring, residual calculation, treatment tracking, SOA generation
"""
from dataclasses import dataclass, field
from typing import Optional
from datetime import date, timedelta
import json
import yaml
RISK_APPETITE = {
"customer_data": 5, # LOW appetite: max acceptable residual = 5
"availability": 11, # MEDIUM appetite: max acceptable residual = 11
"compliance": 5, # LOW appetite: max acceptable residual = 5
"operational": 19, # HIGH appetite: max acceptable residual = 19
}
REMEDIATION_SLA_DAYS = {
"critical": 7,
"high": 14,
"medium": 30,
"low": 90,
}
@dataclass
class RiskEntry:
id: str
title: str
asset: str
asset_criticality: int # 1-5
threat: str
vulnerability: str
inherent_likelihood: int # 1-5
inherent_impact: int # 1-5
treatment: str # treat | accept | transfer | avoid
controls: list[str] = field(default_factory=list)
residual_likelihood: Optional[int] = None
residual_impact: Optional[int] = None
risk_owner: str = ""
treatment_deadline: Optional[date] = None
review_date: Optional[date] = None
risk_category: str = "operational" # customer_data | availability | compliance | operational
nca_evidence: list[str] = field(default_factory=list)
@property
def inherent_level(self) -> int:
return self.inherent_likelihood * self.inherent_impact
@property
def residual_level(self) -> Optional[int]:
if self.residual_likelihood and self.residual_impact:
return self.residual_likelihood * self.residual_impact
return None
@property
def inherent_severity(self) -> str:
return _level_to_severity(self.inherent_level)
@property
def residual_severity(self) -> Optional[str]:
if self.residual_level is not None:
return _level_to_severity(self.residual_level)
return None
@property
def exceeds_appetite(self) -> bool:
if self.residual_level is None:
return self.inherent_level > RISK_APPETITE.get(self.risk_category, 11)
return self.residual_level > RISK_APPETITE.get(self.risk_category, 11)
@property
def treatment_overdue(self) -> bool:
if self.treatment_deadline is None:
return False
return date.today() > self.treatment_deadline
def recommended_deadline(self) -> date:
sla_days = REMEDIATION_SLA_DAYS.get(self.inherent_severity.lower(), 90)
return date.today() + timedelta(days=sla_days)
def _level_to_severity(level: int) -> str:
if level >= 20:
return "Critical"
elif level >= 12:
return "High"
elif level >= 6:
return "Medium"
return "Low"
class NIS2RiskAssessor:
def __init__(self):
self.risks: list[RiskEntry] = []
self.assessment_date = date.today()
def add_risk(self, risk: RiskEntry):
self.risks.append(risk)
def risks_exceeding_appetite(self) -> list[RiskEntry]:
return [r for r in self.risks if r.exceeds_appetite]
def overdue_treatments(self) -> list[RiskEntry]:
return [r for r in self.risks if r.treatment_overdue and r.treatment != "accept"]
def generate_nca_summary(self) -> dict:
total = len(self.risks)
by_severity = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0}
treated = sum(1 for r in self.risks if r.residual_level is not None)
for risk in self.risks:
by_severity[risk.inherent_severity] = by_severity.get(risk.inherent_severity, 0) + 1
exceeds = self.risks_exceeding_appetite()
overdue = self.overdue_treatments()
return {
"assessment_date": str(self.assessment_date),
"total_risks": total,
"risks_by_inherent_severity": by_severity,
"risks_with_treatment": treated,
"risks_exceeding_appetite": [r.id for r in exceeds],
"overdue_treatments": [r.id for r in overdue],
"nca_audit_readiness": "PASS" if not exceeds and not overdue else "GAPS",
"missing_evidence": [
r.id for r in self.risks
if r.treatment == "treat" and not r.nca_evidence
],
}
def generate_soa_yaml(self) -> str:
entries = []
for risk in self.risks:
entries.append({
"id": risk.id,
"title": risk.title,
"risk_category": risk.risk_category,
"inherent_severity": risk.inherent_severity,
"residual_severity": risk.residual_severity or "not-assessed",
"treatment": risk.treatment,
"controls_count": len(risk.controls),
"evidence_provided": bool(risk.nca_evidence),
"exceeds_appetite": risk.exceeds_appetite,
"treatment_deadline": str(risk.treatment_deadline) if risk.treatment_deadline else None,
})
return yaml.dump({"risks": entries}, default_flow_style=False, sort_keys=False)
def print_report(self):
summary = self.generate_nca_summary()
print(f"\n{'='*60}")
print(f"NIS2 Art.21(2)(a) Risk Assessment Report")
print(f"Date: {summary['assessment_date']}")
print(f"{'='*60}")
print(f"Total risks: {summary['total_risks']}")
print(f"By severity: {summary['risks_by_inherent_severity']}")
print(f"Treatments documented: {summary['risks_with_treatment']}/{summary['total_risks']}")
print(f"\nNCA Audit Readiness: {summary['nca_audit_readiness']}")
if summary['risks_exceeding_appetite']:
print(f" ⚠ Risks exceeding appetite: {summary['risks_exceeding_appetite']}")
if summary['overdue_treatments']:
print(f" ⚠ Overdue treatments: {summary['overdue_treatments']}")
if summary['missing_evidence']:
print(f" ⚠ Missing NCA evidence: {summary['missing_evidence']}")
print(f"{'='*60}\n")
# Example usage
if __name__ == "__main__":
assessor = NIS2RiskAssessor()
assessor.add_risk(RiskEntry(
id="RISK-001",
title="Ransomware attack on production database",
asset="Customer data store (PostgreSQL)",
asset_criticality=5,
threat="Ransomware",
vulnerability="Insufficient network segmentation",
inherent_likelihood=3,
inherent_impact=5,
treatment="treat",
controls=["VPC isolation", "Daily encrypted backups", "EDR"],
residual_likelihood=2,
residual_impact=4,
risk_owner="Head of Infrastructure",
treatment_deadline=date(2026, 6, 1),
risk_category="customer_data",
nca_evidence=["backup-restore-test-q1-2026.pdf"],
))
assessor.add_risk(RiskEntry(
id="RISK-003",
title="Credential exposure via secrets in git",
asset="Cloud infrastructure credentials",
asset_criticality=5,
threat="Credential harvesting",
vulnerability="No automated secrets scanning",
inherent_likelihood=4,
inherent_impact=5,
treatment="treat",
controls=["GitGuardian in CI/CD", "Vault for production credentials"],
residual_likelihood=2,
residual_impact=5,
risk_owner="Head of Engineering",
treatment_deadline=date(2026, 4, 30),
risk_category="customer_data",
nca_evidence=[], # Missing — flagged in report
))
assessor.print_report()
print(assessor.generate_nca_summary())
Expected output:
============================================================
NIS2 Art.21(2)(a) Risk Assessment Report
Date: 2026-04-16
============================================================
Total risks: 2
By severity: {'Critical': 1, 'High': 0, 'Medium': 1, 'Low': 0}
Treatments documented: 2/2
NCA Audit Readiness: GAPS
⚠ Missing NCA evidence: ['RISK-003']
============================================================
9. Risk Review Cadence
Art.21(2)(a) compliance is not a point-in-time activity. NCA auditors check:
- When was the last full risk assessment? — Annual minimum required
- When were high/critical risks last reviewed? — Quarterly minimum
- What triggered any out-of-cycle reviews? — Incidents, architecture changes, new threats
Trigger Events for Out-of-Cycle Review
| Trigger | Action |
|---|---|
| Significant security incident (Art.23 threshold) | Full review of affected risk categories within 30 days |
| Major architecture change (new cloud region, new service) | Risk assessment for new components before go-live |
| New threat intelligence (e.g., actively exploited CVE in used software) | Targeted review of relevant risks within 14 days |
| NCA guidance update or new ENISA threat landscape publication | Policy review within 60 days |
| Significant personnel change (CISO, CTO) | Management sign-off renewal within 30 days |
Annual Full Risk Reassessment Checklist
- Asset inventory validated against current architecture (IaC drift check)
- Threat catalogue reviewed against ENISA Threat Landscape update
- All existing risks re-scored with current likelihood/impact
- New risks identified from: incident register, pentest findings, vulnerability scans
- Residual risks recalculated with updated control effectiveness data
- Risk Appetite Statement reviewed and re-approved by management
- SOA updated with implementation status changes
- Risk Register version incremented with approval signature
- Results communicated to risk owners and management
10. NIS2 Art.21(2)(a) 25-Item Checklist
Use this checklist as your NCA audit preparation tool. Each item maps to a specific evidence artefact.
Risk Analysis Process (Items 1-8)
- 1. Documented risk assessment methodology — ISO 27005 or equivalent, written policy
- 2. Asset inventory maintained — All production systems, data stores, integrations listed with criticality ratings
- 3. Threat catalogue defined — Aligned to ENISA Threat Landscape or MITRE ATT&CK
- 4. Likelihood and impact scales defined — Consistent 3-5 point scales with written definitions
- 5. Risk level matrix documented — L×I = Risk Level mapping with treatment thresholds
- 6. CVSS integration for technical vulnerabilities — Pentest/SCA findings mapped to Risk Register
- 7. Annual full risk assessment completed — Evidence: dated, signed Risk Register v[N]
- 8. Quarterly reviews for High/Critical risks — Evidence: dated Risk Register updates
Risk Register (Items 9-14)
- 9. Risk Register maintained in version-controlled system — Git or equivalent with history
- 10. All risks have assigned owners — Named individuals, not team names
- 11. All "treat" decisions have documented controls — Specific controls, not generic statements
- 12. Residual risk levels calculated — After controls, residual L×I documented
- 13. Treatment deadlines assigned — Absolute dates for all open treatments
- 14. NCA evidence artefacts linked — Evidence files referenced per risk entry
Risk Appetite and Treatment (Items 15-19)
- 15. Risk Appetite Statement approved by management — Board/CEO signature, dated
- 16. Risks exceeding appetite have active treatment plans — No untreated risks above appetite threshold
- 17. Accepted risks have formal acceptance justification — Written rationale, expiry date, owner
- 18. Risk treatment deadlines are current — No overdue treatments without approved extension
- 19. Exception process documented and followed — Written exceptions with CISO approval
ISMS Policy Framework (Items 20-25)
- 20. Information Security Policy approved by board — Dated, signed, version controlled
- 21. Risk Management Policy defines methodology — References ISO 27005 or named equivalent
- 22. Statement of Applicability complete — All 10 NIS2 Art.21(2) measures addressed
- 23. Topic-specific policies exist for Art.21(2)(b-j) — IR policy, BCM policy, access control policy, etc.
- 24. Policy review dates are current — No overdue annual reviews
- 25. Risk outputs feed Art.21(2)(f) effectiveness assessment — Risk Register linked to KPI framework
11. 12-Week Implementation Timeline (April → June 2026 NCA Audit)
| Week | Milestone |
|---|---|
| Week 1-2 (Apr 16-30) | Asset inventory (production systems, data stores). Threat catalogue selection (ENISA TL / MITRE ATT&CK). |
| Week 3-4 (May 1-14) | First full risk assessment. Risk Register populated (inherent risks). Risk Appetite Statement drafted. |
| Week 5-6 (May 15-28) | Risk treatments prioritised. Controls mapped to risks. Residual risks calculated. |
| Week 7-8 (May 29 – Jun 11) | ISMS Policy Framework drafted (IS Policy, Risk Mgmt Policy, SOA). Management review and sign-off. |
| Week 9-10 (Jun 12-18) | Treatment evidence collected (backup tests, scan configs, access review reports). NCA evidence pack assembled. |
| Week 11-12 (Jun 19-30) | First Art.21(2)(f) review: KPI baseline established from Risk Register. Policy gaps closed. Final board approval. |
12. EU Sovereignty and the CLOUD Act Risk
For SaaS organisations subject to NIS2, the CLOUD Act introduces a risk that belongs explicitly in the Risk Register: US law enforcement access to EU customer data hosted on US-owned cloud infrastructure.
CLOUD Act as a NIS2 Risk
- id: RISK-010
title: "US CLOUD Act access to EU customer data"
asset: "Customer personal data (EU data subjects)"
threat: "Legal compulsion — US CLOUD Act extraterritorial access"
threat_actor: "US law enforcement / intelligence"
inherent:
likelihood: 2
impact: 4
level: 8 # MEDIUM (reputational + GDPR Art.48 violation risk)
treatment: treat
controls:
- "EU-sovereign infrastructure (no US-owned cloud providers)"
- "EU-owned PaaS platform for production workloads"
- "Data processing agreements with EU-established processors only"
- "Legal review of all subprocessor agreements for CLOUD Act exposure"
residual:
likelihood: 1
impact: 2
level: 2 # LOW
risk_category: "compliance"
note: "Resolved by deploying on EU-sovereign PaaS (e.g., sota.io) with no US-ownership exposure"
Deploying on sota.io — an EU-native PaaS with no US-company ownership — eliminates the CLOUD Act risk at the infrastructure layer. This is a risk treatment control, not just a purchasing preference. It belongs in your Risk Register with the residual risk calculation.
GDPR Art.48 prohibits transfers to third countries without an adequacy decision or appropriate safeguards. US CLOUD Act compliance by a data processor constitutes a GDPR violation in most EU DPA interpretations. Documenting EU-sovereign hosting as a risk treatment strengthens both NIS2 Art.21(2)(a) and GDPR Art.32 compliance simultaneously.
13. Common Audit Failures in Art.21(2)(a)
Based on NCA audit preparation guidance and ENISA implementation reports, the most frequent Art.21(2)(a) failures are:
Failure 1: Risk Assessment Without Management Sign-off
The Risk Register exists but was never formally approved by management. NCA auditors require evidence of management accountability — an unsigned risk register is equivalent to no risk register.
Fix: Every version of the Risk Register requires a dated signature block or a board meeting minutes reference.
Failure 2: Risk Register Not Linked to Controls
Risks are listed but controls are described generically ("we have a firewall") without specific evidence of implementation.
Fix: Each risk entry must reference specific control artefacts (policy name, tool configuration file, test result) that can be inspected.
Failure 3: No Residual Risk Calculation
Organisations document inherent risks but not residual risks after controls. Auditors need evidence that controls actually reduce risk to below the appetite threshold.
Fix: For every treated risk, document residual likelihood and impact, explain why the controls reduce the scores, and confirm residual level is within appetite.
Failure 4: Risk Appetite Undefined or Implicit
Risk treatments are made on ad-hoc judgement without a documented threshold. "We treated it because it seemed important" is not a defensible audit answer.
Fix: Publish a Risk Appetite Statement with explicit numeric thresholds per risk category, approved by management.
Failure 5: Risk Register is Static
The register was created for the last audit cycle and not updated since. It shows risks that have been treated (closing dates in the past) with no evidence of ongoing review.
Fix: Implement quarterly review reminders. Use the incident register and vulnerability scan outputs to trigger risk updates. Each quarterly review generates a dated update in the version history.
The Art.21(2)(a) Foundation
Art.21(2)(a) is not a compliance checkbox — it is the decision engine that makes every other NIS2 measure defensible. Without it, your incident response playbook exists without a documented threat model. Your encryption policy exists without a justification for which data requires it. Your MFA deployment exists without a risk calculation showing which accounts require it.
The organisations that pass NCA audits in June 2026 will not be those with the most security controls. They will be those who can answer: "Here is our risk. Here is our treatment. Here is the evidence that our treatment works." That is the complete Art.21(2)(a) argument.
For SaaS development teams, Art.21(2)(a) compliance is achievable in 12 weeks without specialised GRC consultants — it requires a version-controlled Risk Register, a management-approved Risk Appetite Statement, and systematic integration with your existing CI/CD security outputs (SCA, SAST, DAST). The Python NIS2RiskAssessor above automates the scoring and audit report generation. The 25-item checklist maps directly to NCA evidence requirements.
Start with the Risk Register. Everything else follows from it.
See Also
- NIS2 Art.21(2)(b): Incident Handling and Response Guide — risk register feeds threat scenarios directly into IR playbooks
- NIS2 Art.21(2)(f): Effectiveness Assessment of Cybersecurity Measures — assesses whether the risk treatments defined here are working
- NIS2 Art.21(2)(g): Basic Cyber Hygiene and Security Training — hygiene controls are the first-line treatments for the risks identified in this guide
- NIS2 Art.20: Management Body Obligations — management must approve the Risk Appetite Statement produced here
- NIS2 Art.21 Complete Risk Management Framework — overview of all ten Art.21(2) measures