Concluded·23 Aug 2026 · 4 min read

Counting the plant three ways

nmap 7.x SYN scan · Proxmox VE 9.1 · Ignition 8.3.8 file-based config · pve-firewall · 192.168.29.0/24 plant segment

Prompted by the July 2026 water-sector intrusions, where the recurring detail was that operators did not know their equipment was reachable. The question here is narrow: on an estate whose every component I chose myself, do the available inventories agree?

The rig

Segment192.168.29.0/24, a flat plant network
Control systemIgnition 8.3.8, file-based config, an edge gateway
Scannernmap, SYN scan, -Pn -n --open
Firewallpve-firewall on the hypervisor
Hosts probed in fullthe five plant machines

Inventory A — the control system's configuration

Grepping every resource under the gateway's config tree for network endpoints:

$ grep -rhoE '[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+(:[0-9]+)?' config/resources/core/ignition/
      1 192.168.29.76:5544

One. The historian. Every other JDBC and OPC string in the tree is either localhost or vendor documentation text.

Plus two gateway-network peers:

gateway-network-incoming/central-a-master_3007b9a9…/config.json
  { "securityStatus": "Approved" }
gateway-network-incoming/central-a-backup_2829c77e…/config.json
  { "securityStatus": "Approved" }

Both refer to machines that have since been destroyed. The trust survived the hardware. This is the same property measured in what a gateway backup actually restores — approval is a config resource, so it outlives whatever it approved.

exists
192.168.29.76:5544
the historian
destroyed · still approved
central-a-master
securityStatus: Approved
destroyed · still approved
central-a-backup
securityStatus: Approved
Wrong in both directions at once: missing almost everything real, and holding two things that are not.
The entire external world according to the control system's own configuration: one real endpoint and two machines that were destroyed, still carrying approved trust.

Inventory B — what answers

A 22-port sweep of ports you would actually expect on an industrial segment (22, 80, 443, 502, 1883, 4840, 5432, 5544, 8060, 8088, 8443, 9090, 3000, 1880, 18083, 8883, 20000, 44818, 47808, 5900, 102, 161):

hostroleopen
.1ISP router80, 443, 8443
.36SCADA gateway22, 8060, 8088
.76container host / historian22, 5544
.106knowledge-graph host22, 1883
.203ops host22
.228unidentified80
.239IoT stack22, 1883, 3000, 8883
.10unidentified22

Eight hosts, sixteen open ports. A ping sweep found eleven hosts alive, so three answer to ICMP but expose nothing on the expected ports.

.239 deserves attention. It runs Mosquitto, Grafana, TimescaleDB, Telegraf, a simulator and a web app — a complete second plant data path, with its own broker on 1883 and 8883. It appears in inventory A not at all.

The scan that tripled

The 22-port list is a reasonable list. That is exactly the problem with it. Re-running against all 65,535 ports on the five plant hosts:

.36 SCADA gateway33
.76 container host216 (+14)
.106 knowledge graph215 (+13)
.203 ops host13 (+2)
.239 IoT stack45 (+1)
22 expected ports — 12 found all 65,535 ports — 42 found
The same five machines, scanned twice on the same afternoon. The left bar is a sensible 22-port list; the right is every port. Nothing on the network changed in between.
host22-port scanfull scan
.36 SCADA gateway33
.76 container host216
.106 knowledge graph215
.203 ops host13
.239 IoT stack45
total1242

Nothing on the network changed between the two runs. .76 exposed fourteen services I had not thought to ask about, including 11434 (a local model server), 8096, 9696, 8989, 7878 and a 6881 BitTorrent tracker port. .106 exposed thirteen, including 6432 pgbouncer, 10051 Zabbix and a spread of 808x application ports.

The 22-port scan was not wrong. It answered precisely the question it was asked, and the question was the wrong one. The port list is the finding: an inventory built from a scan inherits every blind spot of whoever wrote the list.

Note also that the earlier 22-port sweep missed .239's web app on 8080 entirely, so even the "second plant" I did find was under-reported by my own instrument.

Inventory C — what is permitted

$ pve-firewall status
Status: disabled/running

$ iptables -L INPUT -n
Chain INPUT (policy ACCEPT)

Everything listening is reachable from anywhere on the segment. There is no third list; the third list is the second list.

This is defensible on an isolated lab segment and would be a serious finding on a utility network. The point for this experiment is that neither of the other two inventories would have told me, and I did not know it until I ran the command, on an estate I built.

what the control system references1 endpoint · 2 ghosts
what answers on the wire42 open ports, 11 hosts alive
what the firewall permitseverything — it is disabled
Three counts of one small estate, drawn to the same scale. Each is produced by a different team, at a different time, and nobody puts them side by side.

Where the three disagree

inventory A (config)inventory B (scan)inventory C (firewall)
hosts3 named11 alive, 8 responsiveall
endpoints1 real, 2 ghosts42 open portsunrestricted
catches decommissioned kitno — keeps it, approvedyes — it does not answern/a
catches undeclared kitnoonly if you scanned its portn/a
catches misconfigurationnonoyes

Each inventory catches something the other two structurally cannot. Configuration knows intent, and is blind to reality. Scanning knows reality, filtered through your expectations. The firewall knows policy, and nothing about what is behind it.

Verdict

There is no single correct inventory, and the effort should go into the diff rather than into any one list. The two disagreements that mattered here were:

  1. Ghost trust — two approved peers for machines that no longer exist, invisible to scanning because dead hosts do not answer, and invisible to the firewall because it does not care.
  2. The blind-spot multiplier — the same estate has 16 or 42 open ports depending only on the port list, which means any inventory derived from a targeted scan carries an unknown and unmeasured shortfall.

Run the full sweep occasionally. It is slow, dull, and the only version capable of telling you something you did not already believe.

The argument for a general audience is in there is more on the plant network than anyone thinks.

Newsletter

New essays, by email.

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