# OT

**A reachable socket on a flat network shouldn't be enough to command a plant you never set foot in.**

An OT control plane (a SCADA head-end, an engineering workstation, a vendor's remote-access tunnel) trusts an IP and a topology, never the machine on the other end. **Modbus**, **DNP3** and **PROFINET** carry no notion of *who* is speaking, so a PLC obeys whoever reaches it, and much of OPC UA is deployed with no authentication at all. Whisper closes that gap with one primitive: the asset's address *is* its identity. This page is the OT front door to the Whisper docs. The full technical library (DNSSEC, DANE, RDAP, the control-plane API) sits one click down the sidebar, shared verbatim with [whisper.online/docs](https://whisper.online/docs).

## The problem: reachability is the whole exploit

This is a *missing-identity* problem that patching cannot close, because the insecurity is by design in the protocol and structural in the network. A device cannot prove who it is, and a controller cannot verify what it is told. A valid-looking command from anywhere with reachability is obeyed as if it came from the plant operator. An attacker discovers an exposed asset (Shodan and Censys already catalogue it), walks in on a default or absent credential, rides IT/OT convergence and remote-access sprawl across the boundary, and lands on a flat network where segmentation is by IP and VLAN, so a foothold that owns an address inherits trust. Then the protocol obeys: a plaintext, spoofable, replayable `Modbus/TCP:502` register write, a forced disconnect, a setpoint. And because the session rides shared, rotating egress with no device identity behind it, the SOC can't say *which* controller or session issued the destructive write, nor revoke it across the vendor/integrator boundary. The root cause has a name: [OWASP broken authorization / BOLA](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/). At plant scale, the network trusts an IP and a topology, never the *machine*.

```
   reachability is the whole exploit: every last IP is disposable

  Reachable socket        Flat OT network           every asset on the segment
  = trusted by      ─no──▶ trust = IP + VLAN  ─reach─▶ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢   PLC · RTU · HMI · EWS
  the asset          auth  one segment,                ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢
  Modbus · DNP3 ·          no boundary           ┌─────────────────────────────────────┐
  OPC UA "None"           claim ✓ · machine ✗    │ obey: register write · setpoint ·    │
                                                 │       disconnect / reconnect         │
                                                 └─────────────────────────────────────┘

  …driven through disposable egress:
  [ AWS ] [ GCP ] [ Azure ] [ remote-access swarm ] ─▶ SOC logs a meaningless last IP
                                                       (which session issued the write? unknown)
```

The whole kill chain leans on two structural facts: the asset has no identity to check the speaker against, so *reachable = trusted*; and the egress is disposable, so the *last IP* the SOC records was never the attacker.

This is not hypothetical. **145,000+** internet-exposed ICS services sit across 175 countries (Censys 2024); a documented class of intrusions took over **≥75** internet-reachable PLCs through a factory-default password like `1111`, or none at all (CISA AA23-335A); **FrostyGoop** (the first malware to weaponize Modbus, and the ninth ICS-specific family ever seen) wrote to `Modbus/TCP:502` registers and cut heating to roughly **600** apartment buildings for about two sub-zero days, against a backdrop of **~46,000** internet-exposed Modbus devices; and OPC UA is no refuge: Bitsight TRACE found **14,220** exposed servers, more than **50%** accepting unauthenticated sessions and about **80%** supporting the plaintext `None` security mode. Across 2024 there were **1,693** ransomware attacks on industrial organisations (**+87%** YoY), one in four causing a full OT-site shutdown (Dragos 8th YIR). Detection will always trail a command that is genuinely valid on the wire. The strictly-stronger move is to change what the network trusts.

## The cure: the address is the asset

> **Shipped & live.** Deriving an asset or PLC `/128` from the device's own key + the OPC UA `ApplicationUri` (or asset serial) it already carries is in production today. Provision one with the control-plane call below and verify it from the DNSSEC root with tools already on your machine.

Whisper gives each asset (each PLC, RTU, IED, HMI, gateway or historian) a routable IPv6 `/128` out of `2a04:2a01::/32` (announced by **AS219419**). It's derived *deterministically* from the device's **public key** (its `SubjectPublicKeyInfo`: the public half of the OPC UA application-instance certificate, an [IEEE 802.1AR IDevID](https://www.rfc-editor.org/rfc/rfc8994), a TPM, or a secure element) with the **OPC UA ApplicationUri** (or an asset serial) as the domain separator. The **ApplicationUri** is already a globally-unique identity: OPC UA Part 6 §6.2.2 requires the application-instance certificate SAN to carry *exactly one* `uniformResourceIdentifier` equal to it, and a session fails with `BadCertificateUriInvalid` if the two don't match. So OPC UA already binds a key-derived name to the asset's key. The private key never leaves the device; only its public SPKI is an input. The result is [DNSSEC](/docs/dnssec)-anchored, [DANE-EE `3 1 1`](/docs/dane) pinned, and [RDAP](/docs/rdap)-registered, re-derivable and verifiable by anyone with `dig`.

```
        already in the asset                        public · verifiable · revocable: what Whisper adds

  Asset key + ApplicationUri  ─public SPKI + URI─▶  /128                 ─DNSSEC · DANE 3 1 1 · RDAP─▶  A name anyone verifies
  OPC UA app-instance cert SAN                      2a04:2a01:7a2::4840                                whisper verify --trustless
  802.1AR IDevID · TPM · SE                         routable · tenant-bound                            (our API not in the trust path)
  private key stays on-device                                                                         └─▶ op:revoke → gone worldwide at DNS-TTL
```

The OPC UA ApplicationUri is already a globally-unique name bound into the asset's certificate. It's a good key-derived identity, trapped in a private, per-site TrustList. Whisper binds it to a routable, publicly verifiable `/128` and gives it the cross-org off-switch a local CRL never had.

Because the derivation is **tenant-bound**, the same asset under two operators yields two unrelated `/128`s: an outsider cannot link a unit across the vendor/integrator boundary. And because the domain separator is the ApplicationUri, **the ApplicationUri alone yields nothing**: it is a structured identifier that flows through every OPC UA deployment and is not a secret, yet you cannot go ApplicationUri → `/128` without the key, there is no enumerable directory, and RDAP and reverse-DNS return the registry object, never the asset's live whereabouts.

What becomes true the moment an asset holds one:

- **"Reachable = trusted" stops being true across the conduit.** You cannot present an asset identity whose key you don't hold; a source with no key behind it can't establish a session across a governed boundary, and every forgery is a DNSSEC/DANE inconsistency any verifier catches.
- **IP and topology trust become irrelevant.** Identity is not the source IP or the VLAN: the "last IP" was never the credential, so rotating it across clouds or a remote-access pool changes nothing.
- **A command from an unproven, off-segment source can't establish a session.** A reachable socket with no asset key behind it authenticates to nothing at the transport boundary. It governs *who may reach and speak to* the asset. The last-inch, purely-internal insecure-protocol write still has to be enforced in the command path, and the honest scope below says so plainly.
- **One `revoke` cuts a compromised asset off worldwide** at DNS-TTL speed: the cross-org off-switch OPC UA never had. Its own trust is a *local* per-site TrustList; revocation is a CRL edit invisible to your integrator, your vendor, or a regulator.

**Additive, never a replacement.** Whisper complements the anchors you already ship: the OPC UA application-instance certificate and its local TrustList, the 802.1AR IDevID and BRSKI onboarding, IEC 62443 zones & conduits, TPM/HSM/secure elements, and the device's own MUD profile. It is the publicly verifiable, DNSSEC/DANE-anchored layer *on top*, anchoring the asset↔network boundary. You can even [DANE-pin](/docs/dane) your existing OPC UA endpoint's certificate to DNSSEC and cut single-CA and self-signed trust risk. Whisper never reaches into the Modbus, DNP3 or PROFINET wire, adds no authentication to those protocols, and does not sit inside the PLC's command path; it keeps the ApplicationUri's key-derived property and adds the two things a private TrustList lacks: public verifiability and DNS-TTL revocation, without re-flashing the brownfield fleet.

And nothing is issued in the dark: every mint and every revoke lands in a public, append-only [Merkle transparency log](/docs/transparency) (Ed25519-signed and anchored to Bitcoin via OpenTimestamps) that you and your assessor can audit. (Honest status: tamper-evident and Bitcoin-anchored today; independent third-party witnessing is the next step, and the log already speaks the witness protocol.)

## Provision an asset identity

Provisioning is one control-plane call over the public API: `POST https://graph.whisper.security/api/query` with your `X-API-Key`. Hand it the device's base64 SPKI and the ApplicationUri; it returns the deterministic `/128` and a WireGuard config for source-bound egress:

```
CALL whisper.agents({op:'connect', args:{
  tier:'wireguard',
  identity_public_key:'<base64 SPKI of the asset key>',
  device_id:'urn:example-plant:line2:PLC7:server'   // the OPC UA ApplicationUri
}}) YIELD op, ok, status, result, error
RETURN op, ok, status, result, error
```

Send it with your key. The heredoc keeps the single-quoted Cypher literals intact, so this runs as-is:

```sh
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H 'content-type: application/json' \
  --data @- <<'JSON'
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'<base64 SPKI>', device_id:'urn:example-plant:line2:PLC7:server'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
```

```json
// response
{ "op": "connect", "ok": true, "status": "created",
  "result": {
    "address": "2a04:2a01:7a2::4840",
    "fqdn":    "opcua-plc7.asset.<tenant>.agents.whisper.online",
    "wireguard": { /* peer, keys, allowed-ips */ }
  } }
```

The call is **idempotent and liberal in what it accepts, strict in what it returns**: re-running with the *same* key and ApplicationUri returns the *same* `/128`; a *different* `device_id` for a key already registered on your tenant is a clear `409`, not a silent overwrite; a non-string `device_id` is a `400` that tells you exactly what was wrong, never an opaque 500. The `device_id` argument is generic: pass an OPC UA ApplicationUri, an 802.1AR IDevID serial, or a bare nameplate serial, whatever native identifier the asset carries. Even a dumb Modbus PLC behind a protocol gateway gets a verifiable network identity, a PTR and an RDAP object for the first time.

> A dedicated `--applicationuri` CLI flag is on the roadmap; today, asset provisioning is the control-plane call above (which is live). The shipped CLI verbs are `whisper verify --trustless`, `whisper create --register`, `whisper kill --revoke`, `whisper policy`, and `whisper logs`. See [CLI & one-command](/docs/cli).

## Verify it yourself: no account

Every asset identity is checkable with no key and no login, from the internet's own records. The `whisper` CLI does the full walk in one call:

```
whisper verify --trustless opcua-plc7.asset.<tenant>.agents.whisper.online

✓ DNSSEC chain valid to the IANA root
✓ DANE-EE (TLSA 3 1 1) leaf matches the asset's OPC UA cert key
✓ RDAP: registered under AS219419 · 2a04:2a01::/32
identity: VERIFIED (our own API was never trusted)
```

Or reach for the raw records directly. It's the same answer, from stock tools:

```sh
# the public verify endpoint: evidence chain in JSON
curl -s https://whisper.online/verify-identity/2a04:2a01:7a2::4840 | jq
# { "is_whisper_agent": true, "dane_ok": true, "jws_ok": true, "evidence": { … } }

# the address is the asset: forward-confirmed reverse DNS names it
dig -x 2a04:2a01:7a2::4840 +short
# opcua-plc7.asset.<tenant>.agents.whisper.online.

# the registry object: who holds the address, and under which allocation
curl -s https://whisper.online/ip/2a04:2a01:7a2::4840 | jq
```

None of these calls Whisper as an authority. `--trustless` re-derives the proof against the public DNSSEC root, exactly as any resolver could. This is the verifiability OPC UA's own model explicitly lacks: it discourages public CAs precisely so no external party decides which apps are trusted, which is why a peer operator, an integrator or a regulator can't check your asset without joining your TrustList. See [Verify an agent](/docs/verify) for the full keyless check and [DANE & TLSA](/docs/dane) for the pin, byte for byte.

## Revoke, worldwide

A compromised PLC, a module swap, a change of integrator, a decommission: one call tears down the `/128`, its PTR, and its DANE pin everywhere at DNS-TTL speed:

```
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:7a2::4840'}})

# after the TTL: dig -x returns nothing, verify returns false
whisper kill --revoke 2a04:2a01:7a2::4840
```

Compromise one asset and you've compromised *that asset*, not the plant. The flat-network blast radius is structurally contained. Revocation is the kill-switch; the same control plane also governs what a *live* asset may reach in between, which the MUD section below covers in full.

## Attribution: name whoever already scraped you

Identity stops the next forgery; the graph names the operator behind the sessions already in your logs: attribution that survives IP rotation because it fingerprints the operator and the tooling, not the ephemeral egress IP. Behavioral OT monitoring is excellent inside the plant, but it stops at the Purdue boundary and the firewall. Run this as read-only Cypher over the same public API with your key (there is no CLI subcommand for this; it is the graph API directly):

```sh
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H 'content-type: application/json' \
  -d '{"query":"CALL whisper.identify(\"45.83.x.x\")"}'
# operator fingerprinted across AWS / GCP / Azure; remote-access pool collapsed by JA4
```

The read-only verbs (`identify`, `origins`, `walk`, `variants`, `history`) each return a reproducible, replayable JSON evidence chain your OT SOC, your PSIRT, and a regulator can replay. Two levers, kept honestly separate: infrastructure genealogy clusters cloud rotation by shared ASN, hosting and certificate lineage; a `JA4/JA3` client fingerprint collapses a rotating remote-access or residential-proxy pool to one operator, invisible to the proxy because it lives in the TLS handshake. More in [Graph & cognition](/docs/graph-api).

## Lookups: see who's enumerating your plant

An identity you can prove is also an identity you can *watch*. Because every asset's name resolves through Whisper's own authoritative DNS and RDAP, the owner can ask who looked. It's a reconnaissance tripwire the OPC UA local, out-of-band TrustList never gave you. `op:lookups` returns who resolved or RDAP-queried an asset's identity, so you see someone *enumerating* your plant before the write lands, not in the post-mortem afterward:

```
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:7a2::4840', window:'24h'}})

# the same reverse-observability view, keyless, per address
curl -s https://whisper.online/ip/2a04:2a01:7a2::4840/lookups | jq
# → who resolved this asset's PTR/AAAA/TLSA and hit its RDAP object, and when
```

Paired with `op:logs` (the asset's own outbound activity) and `/ip/<addr>/transparency` (its ordered lifecycle), you have both halves of the picture: what an asset reaches out to, and who is reaching in to look at it.

## MUD egress governance: make the device's declaration enforceable

This is the OT differentiator. Under **[MUD (RFC 8520)](https://www.rfc-editor.org/rfc/rfc8520)**, an industrial device already *declares* exactly what it should communicate with: a manufacturer-signed manifest of `from-device` and `to-device` ACLs, emitted over DHCP (options `161`/`112`), an LLDP vendor TLV, or the `id-pe-mud-url` X.509 extension co-located with an 802.1AR IDevID. MUD's fatal weakness: the manifest is fetched from the manufacturer's domain and enforced by a *local* MUD manager at the nearest switch. The rules are only a suggestion, and a compromised device still reaches anything the local admin didn't think to block. Whisper binds that declaration to the asset's verifiable `/128` and *enforces* it as default-deny egress governance where traffic actually leaves, cross-org and checkable by anyone.

```
                                    ┌─ enforced at the nearest switch ─▶  Today: only a suggestion
  MUD manifest                      │                                     a compromised device still reaches
  RFC 8520 · DHCP/LLDP/X.509  ──────┤                                     anything the admin missed
  device declares its egress        │
                                    └─ bound to the asset's /128 ──────▶  Whisper: default-deny egress governance
                                       DNSSEC/DANE                         historian ✓   controller ✓   vendor OTA ✓
                                                                          everything else ✕ · enforced where traffic leaves
                                                                          op:policy · op:firewall · op:budget · op:revoke
```

Same manifest, two fates. Pinned to a globally-verifiable identity and enforced at the `/128` where traffic actually egresses, the manufacturer's declaration of intent finally becomes a cryptographic, cross-org control, not a suggestion at the nearest hop.

Egress is source-bound to the asset's `/128`, so policy is enforced by name and by address, a conduit at asset granularity, not a VLAN:

```sh
# default-deny: this PLC may reach ONLY its historian, controller and vendor OTA endpoint
whisper policy set --default deny --allow historian.example-plant.com,controller.line2.local,ota.vendor.com

# per-asset firewall (allow/deny by host, cidr or port) and a traffic budget + kill-switch
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:7a2::4840', deny:['0.0.0.0/0'], allow:['historian.example-plant.com:4840']}})
CALL whisper.agents({op:'budget',   args:{agent:'2a04:2a01:7a2::4840', max_mb_per_day:50}})
```

This directly implements the CISA CPG 2.0 pattern (an IPv6-addressable asset register plus logical segmentation permitting only required communications) and the IEC 62443-3-3 conduit, but keyed to a *cryptographically verifiable* identity rather than a spoofable MUD URL or a VLAN tag. And because each historian feed and telemetry stream can be bound to, and [signed under](/docs/sign-outputs), the asset's forge-proof `/128`, the integrator, the auditor and downstream settlement can trust the numbers came from the real asset, not a spoofed source on the flat network. See [Egress governance](/docs/egress-governance) for the full policy surface.

**The honest edge.** Egress governance changes *who may reach and speak to* an asset, and constrains what a governed asset may reach: it contains C2, exfil and lateral movement across the convergence bridge and the remote-access surface. It does *not* add authentication to Modbus, DNP3 or PROFINET on the wire, and it does not stop a purely-internal manipulation by an attacker who already holds an OT-segment foothold: closing that last inch needs identity enforced in the command path, at the PLC, a protocol-aware broker-gateway, or the EWS. We say so before your assessor does.

## What ships today, and what's on the roadmap

We label these honestly so you can plan against them.

| Shipped & live | On the roadmap |
|---|---|
| Asset/PLC `/128` from the device key + ApplicationUri (or serial): DNSSEC + DANE-EE + RDAP | A dedicated `--applicationuri` / `--mud` CLI flag (provision via the control-plane call today) |
| Control-plane provision, verify, revoke; MUD egress governance (`policy`/`firewall`/`budget`); `op:lookups`; the Merkle transparency log; the attribution graph over the public API | **STIX 2.1 over TAXII** export |
| The **Splunk**, **Microsoft Sentinel** and **OpenCTI** connectors (signed, replayable JSON → CEF / ECS fields) | sector-ISAC (E-ISAC / WaterISAC) machine-readable JSON export |

The integration guides below describe **proposed** integrations at the asset and IP boundary, designed to complement the stack you already run, not endorsed by any vendor, and never named against a specific operator as a breach victim.

## The five OT guides

The OT story, in depth: each page is self-contained and copy-paste runnable.

- **[Asset & PLC identity →](/docs/asset-identity)**: derive a routable `/128` from the OPC UA `ApplicationUri` (or an 802.1AR IDevID / serial) an asset already holds. Deterministic, tenant-bound, DNSSEC + DANE-EE pinned, the device-identity spine.
- **[OT-exposure cure →](/docs/ot-exposure-cure)**: why a reachable socket on a flat network is the whole exploit, and how a forge-proof address ends "trusted because it owns an IP": the missing-identity root cause, cured at the identity layer.
- **[OPC UA · MUD · 62443 →](/docs/ot-integrations)**. Proposed integrations at the asset/IP boundary: OPC UA GDS & ApplicationUri, MUD (RFC 8520), 802.1AR / BRSKI, IEC 62443 zones & conduits, OT-visibility feeds. Complements, never replaces.
- **[IEC 62443 · EU CRA · TSA →](/docs/ot-compliance)**: map identity, egress and attribution evidence to EU CRA Annex I, IEC 62443-4-2 CR 1.2 & 3-3 zones-and-conduits, CISA CPG 2.0, NIST SP 800-82r3 and the TSA SDs, as a network primitive, not a binder.
- **[MUD egress · verify · attribute →](/docs/ot-recipes)**. Runnable recipes: enforce a device's MUD manifest as default-deny egress at its `/128`, verify an asset trustless, back-trace a suspicious controller on the graph.

## The full technical library

OT rides on the same address-is-identity platform as every other agent on the network, so the whole shared library applies here unchanged, and every page has a clean Markdown twin at the same path + `.md`. Start with these; the rest is in the sidebar.

- **[Quickstart →](/docs/quickstart)**: install, register your first identity, connect it, confirm it, one terminal, start to finish.
- **[Verify an agent →](/docs/verify)**: the full keyless identity check. Every proof you run with `dig`, `curl`, and `openssl`.
- **[DANE & TLSA →](/docs/dane)**: the `3 1 1` pin that makes an address forge-proof, byte for byte, no CA in the path.
- **[Control plane →](/docs/control-plane)**: the full `whisper.agents` API (provision, connect, policy, logs, revoke) over the public endpoint.

---

← [For OT security](https://ot.whisper.online/for-ot-security) · [Asset & PLC identity →](/docs/asset-identity)
