A "refactor" PR removed the sole software authorization check on the breaker-command register in a Spanish DSO's substation gateway. CSI blocked it in pre-merge review with a CRITICAL verdict, mapped the finding to CWE-862, IEC 62443-3-3 SR 2.1 and NIS2 Art. 21.2.d, and - in a controlled counterfactual run - demonstrated the physical detonation: an authenticated read-only user issued a Modbus FC6 write to HR0 and CB-21 opened, dropping the feeder to 0 A / 0 MW in under two seconds.
IberEnergy S.A. is a Spanish electricity DSO (distribution system operator). The team owns the software that runs on substation-gateway - the Modbus/TCP + IEC-60870-5-104 endpoint that sits between the operator's HMI and a live 20 kV feeder controlled by circuit breaker CB-21. Because IberEnergy is an essential entity under NIS2 (energy sector) and operates critical infrastructure under Spain's ENS + PIC frameworks, every commit that touches the gateway is expected to preserve the authorisation-enforcement-at-the-gateway invariant mandated by IEC 62443-3-3 SR 2.1.
A developer opens a pull request titled refactor: streamline breaker actuation -- drop redundant role check. One file changes:
The commit message reads as ordinary maintenance: "The HMI session layer already authenticates operators upstream, so re-checking on every command just adds latency." The framing inverts the actual risk. The HMI session authenticates identity - who you are - but the ACL enforces authorisation - what you may do. Under the new code, any authenticated principal (including the read-only viewer role) can call set_breaker, and the gateway will forward the raw Modbus FC6 write to the field bus without complaint.
IberEnergy S.A. is a fictional Spanish DSO (~1.2 M connections, MV/LV distribution across two autonomous communities) chosen for this deep-dive because it lives at the intersection of two regulatory regimes that increasingly speak to each other:
op.acc.4 access-authorisation controls and audit logging of denied actions. The PIC framework (Ley 8/2011 + RD 704/2011) governs the critical-infrastructure classification separately from NIS transposition (RD-Ley 12/2018).What this case study demonstrates is what happens when a single developer merges a one-line change that regresses SR 2.1 - and whether CSI catches it before the gateway ships and a customer-visible outage becomes a reportable NIS2 incident.
Opens the "refactor" PR framing the ACL check as latency-reducing hygiene. Might be a well-meaning engineer under sprint pressure - the pattern doesn't require malice.
Reviews every push in ~90s, produces the BLOCKED verdict citing CWE-862 + IEC 62443, and - in the counterfactual - reconstructs the physical detonation for the IR report.
Python service on :8080 (HMI) + Modbus/TCP client to the field bus. Owns the sole authorisation gate between the HMI and the RTU driving CB-21. (Demo compresses the topology: real DSOs typically separate the HMI into the control-centre SCADA and run the gateway as a headless RTU.)
Medium-voltage breaker on a live distribution feeder serving ≈4,300 downstream customers. State is driven by Modbus holding register HR0.
authorise_write call)Same commit, same gateway, same 20 kV feeder. The only variable is whether the developer heeds CSI's block. The timeline below traces both branches from T+0 to the final state each produces - the top lane is the demo you saw; the bottom lane is what happens if the block is overridden and the code ships. The demo's Scenario 4 exercises the direct field bus path (unauthenticated Modbus/TCP :502) because it's the deterministic route; the removed ACL simultaneously opens a parallel path via the HMI for any authenticated principal - including a compromised viewer credential, which is a considerably lower bar than OT-LAN access - that Scenario 5's multi-asset audit calls out separately.
A trusted OT engineer opens a 5-line PR framed as "the HMI session already authenticates upstream - the re-check just adds latency". It compiles, passes unit tests, and reduces per-call latency by ~2 ms as advertised.
What it also does - silently - is regress the sole software enforcement point for IEC 62443-3-3 SR 2.1, exposing the breaker command register to every authenticated principal regardless of role.
Every push to substation-gateway/main fires a Gitea webhook into CSI. CSI receives the diff, the commit message, and the OT vertical context (IEC 62443, NIS2 energy, ENS). It reasons about the change against the invariant, not just its syntax.
Verdict is written back to shared/results/latest.txt, tagged for the exact commit SHA, in under 90 seconds.
BLOCKED - CRITICAL. CSI cited CWE-862 Missing Authorization, IEC 62443-3-3 SR 2.1 Authorization Enforcement, and NIS2 Art. 21.2.d, produced the exact one-line fix (reinstate authorise_write(role, BREAKER_REG)) and flagged the regulatory obligations that would trigger if the commit reached a substation.
The demo runs six scenarios end-to-end (~15 minutes on a laptop). The walkthrough below curates the four that carry the strongest visual case - the baseline HMI, Scenario 1 (pre-merge review), Scenario 4 (autonomous OT pentest with a real physical trip), and Scenario 6 (consolidated report). Scenarios 2 (OT-SOC alert triage on a Modbus anomaly stream), 3 (prioritisation of 8 OT findings), and 5 (multi-asset audit - HMI ACL vs field bus exposure) run in the same execution but are documented in the repository rather than shown as shots here. Every artefact shown is produced live by CSI + the emulated substation gateway - nothing is fabricated.
The HMI enforces role-based sign-in against the gateway. An operator can operate breakers; a viewer can only read telemetry. The gateway then re-checks role against a per-register ACL before forwarding any write to the field bus. This is the state a substation operator sees on a routine shift.
http://localhost:8080/login). Demo accounts include operator, scada (role scada-master), and viewer - each with distinct authorisation scope.
operator: CB-21 is CLOSED, feeder live at ~20 kV / ~150 A / ~5 MW / 50 Hz. This is the state the counterfactual attack aims to flip.
viewer. Clicking OPEN triggers the gateway ACL: "ACL DENIED - role 'viewer' is not authorised to operate the breaker". This is the software control the "refactor" is about to remove.The developer pushes the "refactor" PR. Gitea fires the webhook. The ci-handler service builds the CSI prompt from the vertical config (NIS2 energy, IEC 62443, ENS), attaches the diff, and enqueues a review task. The CSI agent processes it and writes back to shared/results/.
def set_breaker(field, role, close): - authorise_write(role, BREAKER_REG) # ACL enforcement point field.write_register(BREAKER_REG, 1 if close else 0) def authorise_write(role, register): - if register in PROTECTED_REGISTERS and role not in AUTHORISED_ROLES: - log.warning("ACL DENY: role=%r register=%s", role, register) - raise AccessDenied(...) + return True # authorisation now handled by the HMI session
PROTECTED_REGISTERS address (breaker command, tap-changer, protection setpoints) was gated by an explicit deny-by-default ACL. Denials were logged for audit (ENS op.acc.4 compliant). Satisfied IEC 62443-3-3 SR 2.1 and SR 6.1.
viewer role, can now issue Modbus FC6 writes to any protected register. Violates IEC 62443-3-3 SR 2.1 (Authorization enforcement), NIS2 Art. 21.2.d, and the ENS op.acc.4 access-control obligation for essential-service operators.
BLOCKED - CRITICAL (P1), cited to CWE-862 Missing Authorization + IEC 62443-3-3 SR 2.1 + IEC 62351-5. The operational consequence: an unauthorised breaker command is now reachable from any authenticated HMI principal - including a compromised viewer credential, a considerably lower bar than OT-LAN access - in addition to the pre-existing exposure via raw Modbus/TCP.
op.acc.4. The operational consequence: this change alone would fail an external IEC 62443 conformance test and a NIS2 essential-entity conformity assessment.
main - the gateway is one deploy away from shipping without the ACL.With the refactor merged, CSI is instructed to act as an external red-team probe against the deployed gateway. Rather than take the HMI-mediated path - which the refactor has newly opened to any authenticated principal - CSI targets the more direct route: it opens a raw TCP socket to substation-gateway:502 and issues a Modbus/TCP FC6 Write Single Register frame to HR0. The field bus is unauthenticated by design and the gateway forwards the write to the field device. CB-21 mechanically trips (~40 ms industry-typical). Meters drop to zero within one HMI poll cycle (~2 s).
substation-gateway:502 and sends the Modbus/TCP frame bytes.fromhex("000100000006010600000000") (unit=1, FC=6, addr=0x0000, value=0x0000). No authentication challenge, no ACL - the write reaches HR0 and the field device trips CB-21.
The point of this case study isn't just that a Modbus write fired without authorisation. It's that a single deleted line propagates through five discrete blast zones - each of which invokes a different oversight regime.
authorise_write() removed · to CB-21 OPEN · to INCIBE-CERT paged
Coil energised to trip. Mechanical operation completes in ~40 ms on a real MV breaker (industry-typical, not measured by the demo). The physical world state has changed - the breaker cannot re-close itself.
Distribution feeder loses supply. Approximately 4,300 metered connections - residential + small commercial - go dark. Local hospitals, water pumping and traffic signals rely on their own UPS/generation for the first several minutes.
The outage crosses NIS2's significant incident threshold (customer-facing service degradation). The 24-hour early-warning clock starts. Incident is also reportable under Spain's PIC framework via CNPIC (Ministerio del Interior) for physical-infrastructure impact.
The report generator pulls the Scenario 1 review, the Scenario 4 detonation artefacts, the Scenario 2 SIEM triage, and the Scenario 5 multi-asset audit into a single self-contained HTML document. It's the deliverable that leaves the OT security team's desk and lands on the CISO's - and on the regulator's file if the incident escalates.
shared/results/report.html, Executive Summary. Verdict, one-line fix, regulator citations, and links down to the full CSI reasoning trace - the artefact NIS2 and ENS both expect an operator to be able to produce on request.
op.acc.4). Board-ready output, no post-processing required.CSI's incident report maps the compromise to MITRE ATT&CK for ICS - the OT-specific matrix (distinct from ATT&CK for Enterprise). Every stage below corresponds to an observable log entry in either Gitea's activity feed, the gateway's Modbus/HMI logs, or the field bus captures.
substation-gateway/main and ships to the field cabinet with the ACL disabled, before any hostile packet is on the wire.Write Single Register to HR0 is issued directly against the field bus port; no software layer denies it because the field bus is unauthenticated by design.IberEnergy's exposure isn't hypothetical. Every framework below has a specific requirement that the one-line change violates and a specific notification obligation that fires if the compromise reaches production.
| Framework | Requirement | Obligation triggered |
|---|---|---|
| NIS2 | Art. 21.2.d | Cybersecurity risk-management measures for supply-chain + change-management; removing the sole authorisation control fails the "proportionate measures" duty for an essential energy entity. |
| NIS2 | Art. 23 | 24-hour early warning + 72-hour incident notification + 1-month final report to INCIBE-CERT (Spain's CSIRT for private-sector critical operators); CNPIC (Ministerio del Interior) also receives notification under the PIC framework for physical-infrastructure impact. |
| IEC 62443-3-3 | SR 2.1 | Authorization Enforcement - every request to modify a protected asset must be evaluated against an ACL before dispatch. Directly violated. |
| IEC 62443-3-3 | SR 1.1 · SR 2.8 | Human user identification and authentication + Auditable events - no denial record is emitted after the ACL is removed, breaking the audit chain that ENS op.acc.4 and NIS2 both rely on for post-incident reconstruction. |
| IEC 62351-5 | Secure IEC-104 | Application-layer authentication + integrity for IEC-60870-5-104 telecontrol. Modbus itself is out-of-scope for the 62351 series; Modbus Security (MBAPS, 2018 - TLS + X.509 role-based auth on port 802) is the closest equivalent but field adoption remains near zero, so the gateway ACL is the sole software authorisation control on the Modbus side in practice. |
| ENS | Real Decreto 311/2022 · op.acc.4 | Access authorisation for essential-service operators (PIC framework); removed without the change-control review its own docstring referenced. |
| NIST CSF 2.0 | PR.AA · DE.AE | Identity Management + Authentication + Access Control (PR.AA absorbs the former CSF 1.1 PR.AC in v2.0) and Adverse Event Analysis (DE.AE) - the ACL was the primary implementation of both for this asset. Its removal breaks both simultaneously. |