We were about to add 173 industrial ports to the scanner. The catalogue work behind it was done, every port cited to a vendor page, and the door it opened had already been built. The obvious next step was to ship it.
Instead we measured what the scanner already transmitted, and found two channels it should never have touched.
What was measured
UDP 500 on a host running iFIX carries the failover synchronisation between a primary and a standby SCADA node. Our UDP path sent 20 bytes at it.
UDP 2222 is CIP class 1 implicit I/O. On a GuardLogix that is the safety I/O channel: the cyclic traffic that carries safety data between the controller and the field. Our UDP path sent a single null byte at it, the generic fallback datagram a UDP probe sends to a port with no registered payload.
Neither was a decision. Both were the consequence of a port appearing in a list and a probe having nothing better to send. The only reason it did not happen more often is that those ports were rarely in the scanned set, which is to say: the protection was that we had not got around to scanning them yet.
Omission is not a control
That is the sentence the whole piece of work turned on. A safety property that holds because of what has not been implemented yet is not a safety property. It is a coincidence with a short shelf life, and adding 173 ports was exactly the change that would have ended it.
So the gate came first and the ports came second, in the same release. The never-send gate is applied last in port resolution, after the preset, after the range, after custom ports, and after the operator's own typed force-add. A port we have decided not to disturb is not made safe by someone typing it into a box.
On TCP a withheld port can still be connect-and-closed where that is the documented safe behaviour. On UDP it is dropped from the set entirely, because there is no connect-and-close on UDP: a port with no registered payload still receives the fallback datagram, so "presence only" is not expressible there and pretending otherwise would be the same defect in a politer form.
Proving it with a socket
A test that asserts a function returns false proves that the function returns false. It does not prove that nothing left the machine.
So the proof binds a real socket on the loopback interface, drives the production UDP scan path, and reads what arrived. The denied port receives zero bytes. A control port beside it receives one byte.
The control port is the point. Without it, a scanner that had been broken into transmitting nothing at all would pass the test and prove only that it ran. The one byte the control receives is the bare null fallback, which is to say: it is the exact datagram that used to go to the safety I/O channel, now arriving somewhere it is allowed to arrive.
What the operator sees
When a port is withheld, the run says so by name and reason. A capability declined in silence reads as a capability the product does not have, and an operator who cannot see the refusal cannot audit it.
The same principle governs the one genuinely aggressive industrial read we ship. IEC 60870-5-104 is Europe's most widely deployed telecontrol protocol, and the closest thing to a safe active read it offers is a STARTDT followed by a general interrogation. Our own research says why that is not simply safe: STARTDT claims a master role on the link, and some RTUs permit exactly one active master, so the probe can displace or be refused by the site's real SCADA master.
An internal recommendation had it routed through the read-only class, which is on by default for an industrial audit. That would have accepted the master-role risk on the operator's behalf, silently, on every audit. Displacing a substation's live SCADA master is not a read. It ships as its own named opt-in instead, default off, never inherited from anything, and when it is off the scanner emits a policy block that names the risk rather than staying quiet.
Frequently asked questions
What two industrial ports was Nexich's scanner already sending traffic to before this fix?
Before a fix, the scanner sent 20 bytes to UDP 500 on hosts running iFIX, which carries the failover synchronisation between a primary and standby SCADA node, and a single null byte to UDP 2222, the CIP class 1 implicit I/O channel that on a GuardLogix controller carries safety I/O data between the controller and the field.
Why is UDP port 2222 sensitive on a GuardLogix controller?
UDP 2222 is the CIP class 1 implicit messaging port. On a GuardLogix safety controller that same port carries the cyclic safety I/O traffic between the controller and the field. An unexpected datagram lands on the channel carrying live safety data, which is why the port is withheld entirely rather than merely probed carefully.
Why does the post say omission is not a safety control?
A safety property that holds only because a feature hasn't been built yet isn't a safety property, but a coincidence with a short shelf life. The two sensitive ports were untouched only because they were rarely included in the scanned set, not because of any decision, and adding 173 new ports would have ended that coincidence.
How does the never-send gate work in port selection?
The gate is applied last in port resolution, after the preset, after the port range, after custom ports and after the operator's own typed force-add. On TCP a withheld port can still be connect-and-closed if that's the documented safe behaviour; on UDP it's dropped from the set entirely, since UDP has no connect-and-close to signal presence safely.
How is the never-send gate actually tested?
The test binds a real socket on the loopback interface, runs the production UDP scan path, and reads what arrives. The denied port must receive zero bytes. A control port beside it must receive exactly one byte, the same bare null fallback datagram that used to reach the safety I/O channel, now arriving somewhere it's allowed to.
Why isn't the IEC 60870-5-104 active read part of the default read-only class?
The read uses STARTDT followed by a general interrogation, and STARTDT claims a master role on the link. Some RTUs permit only one active master, so the probe can displace or be refused by the site's real SCADA master. Rather than accept that risk silently inside the default read-only class, it ships as its own named opt-in, off by default.