Concluded·28 Aug 2026 · 5 min read

What a plant stack is actually made of

syft 1.51.1 · grype 0.118.0 · CISA KEV catalogue 2026-08-27 · Ignition 8.3.8, Node-RED 4.1, EMQX, Mosquitto 2.0.22, N3uron 1.22.4, TimescaleDB pg16, Grafana

The EU Cyber Resilience Act's reporting obligation starts 11 September 2026. It does not ask whether you are secure. It asks whether you can determine, within 24 hours, that a product you placed on the market contains a vulnerability somebody is actively exploiting.

That is an inventory question, so I measured the inventory.

The rig

Subjectseven products forming a small plant stack
SBOMsyft 1.51.1, run against local images via the Docker socket
Vulnerabilitiesgrype 0.118.0
Exploited-in-the-wildCISA Known Exploited Vulnerabilities catalogue, 1,685 entries, fetched 2026-08-27
Exploit predictionEPSS scores as carried in grype's output
$ syft -q docker:inductiveautomation/ignition:8.3.8 -o json
$ grype -q docker:inductiveautomation/ignition:8.3.8 -o json

No agents, no registry account, no commercial scanner. Both tools are run as containers against images already on the host.

What is in there

Grafana796 components · 13 CVEs
Node-RED 4.1763 components · 94 CVEs
N3uron 1.22.4551 components · 154 CVEs
Ignition 8.3.8429 components · 155 CVEs
EMQX96 components · 106 CVEs
TimescaleDB pg1680 components · 74 CVEs
Mosquitto 2.0.2220 components · 15 CVEs
components distinct CVEs2,735 components · 342 distinct CVEs · 1,286 matches
Component count against distinct CVE count, per product. The two do not track each other — Mosquitto carries 20 components and 15 CVEs; Grafana carries 796 and 13.
productcomponentsmatchesdistinct CVEscriticalhigh
Grafana7966513043
Node-RED 4.1763194949102
N3uron 1.22.45513121541361
Ignition 8.3.8429286155112
EMQX961911061550
TimescaleDB pg1680211740125
Mosquitto 2.0.22202715017
total2,7351,286342 distinct

Ignition's 429 break down as 288 Java archives, 127 Debian packages, 9 binaries, 4 Go modules and one Python package — which is worth knowing on its own, because "a Java application" turns out to mean 288 separately-versioned things.

Note that matches exceed distinct CVEs everywhere: the same vulnerability is frequently found in several components of the same image.

The intersection that matters

The CRA clock is triggered by active exploitation, not by a CVE existing. The closest public proxy is CISA's Known Exploited Vulnerabilities catalogue.

342
distinct CVEs found
1,685
CISA KEV entries, 2026-08-27
0
reportable under a 24-hour clock today
Every distinct CVE in the stack, one dot each, checked against the 1,685-entry known-exploited catalogue. A match would be drawn in red. There are none.

342 distinct CVEs. 1,685 KEV entries. Intersection: zero.

EPSS agrees. Exactly one CVE in the stack scores above 0.5 predicted probability of exploitation:

epss=0.733  pct=0.994  CVE-2011-3389  libgnutls30t64  (n3uron)
epss=0.070  pct=0.938  CVE-2017-17740 libldap2        (n3uron)
epss=0.053  pct=0.920  CVE-2015-3276  libldap2        (n3uron)

CVE-2011-3389 is BEAST — a TLS 1.0 CBC weakness from 2011, mitigated in practice by everything that has since moved to TLS 1.2+.

An honest caveat on the zero. This is one stack on one day. KEV grows weekly, and a CVE moving onto it is precisely the event the 24-hour clock exists for. The finding is not "this stack is safe" — it is that the reportable set today is empty, and that the check to establish this is cheap and repeatable.

Age distribution, or: this is not a museum

34202005202621 CVEs in the first sixteen years
Cumulative CVE count by year of assignment. The curve is flat for fifteen years and then vertical, which is a picture of maintenance rather than of decay.
periodCVEsshare
202627580%
20253410%
2021–2024124%
2020 and earlier216%

The oldest is from 2005, the newest from this year. The industrial-software-is- ancient story does not survive contact with the dates: 90% of what a scanner flags here was assigned in the last two years.

This is a measurement of advisory activity, not of rot. A component with an active security team generates CVEs; an abandoned one generates silence. Reading the count as a danger score gets the sign wrong.

The duplication problem

This is the finding that would actually cost you the 24 hours.

tar3 versions
1.35+dfsg-3.11.35+dfsg-3build16.2.1
busybox2 versions
1.37.0-r301.37.0-r31
openssl2 versions
3.5.6-1~deb13u23.5.7-r0
util-linux3 versions
2.39.3-9ubuntu6.52.41-52.41.5-0+deb13u1
perl-base2 versions
5.38.2-3.2ubuntu0.25.40.1-6
libssl3consistent
3.5.7-r0
63 CVE-carrying components shared across products · 50 at inconsistent versions
Shared components and the versions they are present at. libssl3 is the only one of these that is consistent everywhere — the rest force a per-product answer.

63 CVE-carrying components appear in more than one of the seven products. 50 of those 63 appear at more than one version.

componentproductsversions present
tar41.35+dfsg-3.1, 1.35+dfsg-3build1, 6.2.1
busybox41.37.0-r30, 1.37.0-r31
libssl343.5.7-r0
openssl33.5.6-1~deb13u2, 3.5.7-r0
util-linux32.39.3-9ubuntu6.5, 2.41-5, 2.41.5-0+deb13u1
perl-base35.38.2-3.2ubuntu0.2, 5.40.1-6
zlib1g3two Debian/Ubuntu builds

When an advisory names openssl 3.5.6, the answer to "do we ship that?" is neither yes nor no. It is yes in one product, no in another, and the difference is a base-image choice made by a vendor you do not control.

That is why the inventory has to be a stored artifact rather than a question you answer by thinking. Under time pressure, from memory, nobody gets this right.

Cost of the census

stagetime
Ignition 8.3.8 (429 components)113 s
Grafana + EMQX72 s
TimescaleDB46 s
Node-RED30 s
N3uron13 s
Mosquitto2 s
total4 m 36 s
Mosquitto
+2s → 2s
N3uron
+13s → 15s
Node-RED
+30s → 45s
TimescaleDB
+46s → 91s
Grafana + EMQX
+72s → 163s
Ignition
+113s → 276s
total 4 m 36 s · against a 24-hour reporting obligation
Scan time accumulating across the stack. The whole census finishes in the time it takes to read this article, against an obligation measured in days.

Against a 24-hour reporting obligation, the technical work is four and a half minutes and produces a file. Everything else about CRA compliance is organisational.

One failure worth recording

The first EMQX scan produced a zero-byte result and a non-obvious error:

ERROR failed to catalog: an error occurred attempting to resolve
'emqx/emqx:latest': docker: unable to provide image from tarball:
file blobs/sha256/37416ecf…4c not found in tar

The locally cached image was missing a layer blob. A re-pull fixed it. Worth noting because a scanner that silently produces nothing for one product of seven is exactly how an inventory ends up 14% short without anybody noticing — the failure mode is an empty file, not an error in a report.

Verdict

The obligation is answerable and the tooling is free. The number to watch is not the CVE count, which measures how carefully your suppliers are looking; it is the intersection with what is actually being exploited, which today is empty and next month may not be.

Do the boring thing: generate the inventory, date it, store it, and reconcile the fifty components that exist at more than one version before an advisory forces you to do it in a hurry.

The argument for a general audience is in nobody knows what software is inside your plant.

Newsletter

New essays, by email.

SCADA, cloud, AI, and the plant floor — a short email when something new is published. No noise, unsubscribe anytime.