2026-04-16·15 min read·

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

SubparagraphRequirementPrimary Owner
Art.21(2)(a)Risk analysis and information system security policiesCISO / 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 securityProcurement / 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 trainingHR / 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:

  1. Risk analysis process — a repeatable, documented methodology for identifying, assessing, and treating information security risks
  2. Risk register — a maintained inventory of identified risks, their likelihood, impact, owner, and treatment status
  3. 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:

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 CategoryExamplesNIS2 Relevance
Customer data storesProduction databases, data warehousesData breach → Art.23 reporting
Authentication systemsOAuth providers, MFA infrastructureAccount takeover → Art.21(2)(j)
CI/CD pipelinesGitHub Actions, build artifacts, container registriesSupply chain → Art.21(2)(d)
Cloud infrastructureAWS accounts, Kubernetes clusters, VPCsAvailability → Art.21(2)(c)
Internal toolsAdmin panels, support dashboardsPrivilege escalation → Art.21(2)(i)
Third-party integrationsPayment processors, email providers, CDNSupply 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 CategoryRepresentative ThreatMITRE ATT&CK Tactic
Malicious codeRansomware, supply chain malwareExecution, Impact
Social engineeringPhishing, business email compromiseInitial Access
Web application attacksSQL injection, SSRF, account takeoverInitial Access, Credential Access
Insider threatPrivilege abuse, data exfiltrationCollection, Exfiltration
Cloud misconfigurationPublic S3 bucket, exposed admin APIInitial Access, Discovery
DDoS / availability attackVolumetric flood, application layer L7Impact
Third-party / supply chainCompromised npm package, CI/CD credential leakInitial 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):

ScoreLabelDefinition
1RareLess than once every 5 years
2UnlikelyOnce every 2-5 years
3PossibleOnce every 1-2 years
4LikelyOnce per year
5Almost CertainMore than once per year

Impact (I):

ScoreLabelDefinition
1NegligibleNo customer impact, no regulatory notification
2MinorLimited customer impact, internal resolution
3ModerateSubset of customers affected, internal escalation
4MajorSignificant customer data breach or service disruption, Art.23 threshold likely met
5CriticalComplete service loss or mass data breach, NCA mandatory notification, potential fines

Risk Level = L × I (1-25 scale):

Risk LevelRangeAction
Critical20-25Treat immediately, escalate to management
High12-19Treat within 30 days
Medium6-11Treat within 90 days
Low1-5Accept 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:

  1. Version history — the register must show when risks were added, modified, or closed
  2. Review cadence — quarterly minimum for Critical/High risks, annual for all risks
  3. 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 ScoreSeverityRecommended Risk Register LikelihoodImpact Adjustment
9.0–10.0Critical5 (Almost Certain if exposed)Use asset criticality
7.0–8.9High4 (Likely)Use asset criticality
4.0–6.9Medium3 (Possible)Use asset criticality
0.1–3.9Low1-2Use 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:

# 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 SeveritySLAEscalation
Critical (9.0+)7 daysCISO + Management notification
High (7.0-8.9)14 daysCISO notification
Medium (4.0-6.9)30 daysRisk Register update
Low (0.1-3.9)90 daysRisk 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:

  1. When was the last full risk assessment? — Annual minimum required
  2. When were high/critical risks last reviewed? — Quarterly minimum
  3. What triggered any out-of-cycle reviews? — Incidents, architecture changes, new threats

Trigger Events for Out-of-Cycle Review

TriggerAction
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 publicationPolicy review within 60 days
Significant personnel change (CISO, CTO)Management sign-off renewal within 30 days

Annual Full Risk Reassessment Checklist


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)

Risk Register (Items 9-14)

Risk Appetite and Treatment (Items 15-19)

ISMS Policy Framework (Items 20-25)


11. 12-Week Implementation Timeline (April → June 2026 NCA Audit)

WeekMilestone
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