# The OT-exposure cure

**Industrial exposure has one root cause, and it fits in a sentence: _trusted, not authenticated._ A controller obeys a well-formed command from anything that can reach it. The plant is flat, the asset has no identity it can prove, and the protocol underneath authenticates nothing at all. That is not a bug you patch; it is the OT default.**

Whisper cures it at the layer the abuse actually lives on: _reachability_. It makes the address _be_ the asset: a routable IPv6 `/128` derived from the key the asset already holds, [DNSSEC](/docs/dnssec)-anchored and [DANE-pinned](/docs/dane), that no foothold can forge and one call can revoke worldwide. _Who may reach and speak to an asset_ stops being a matter of position on a segment and becomes a verifiable, revocable question any party (asset owner, integrator, vendor, regulator) can answer from public DNS, across the org boundary no VPN ever closed. This page walks the root cause end-to-end, the reframe, the exact live calls, and the MUD angle that makes declared egress enforceable. It is also candid about the last inch identity does _not_ reach.

## The root cause: trusted, not authenticated

OT was not breached into this state. It was built into it. The protocols were designed for isolated, physically-guarded networks; the assets outlive the software that secures them; and IT, OT and connected-device networks have since converged onto one fabric. Strip the sector's 2023–25 incidents down and they rest on the same chain, and no link in it is an exploit.

```
   the OT-exposure chain: no zero-day required, every link a missing identity

  01 · EXPOSED      02 · ACCESS       03 · CONVERGE     04 · FLAT-NET     05 · PROTOCOL      06 · IMPACT
  Plant already ──▶ Default / no  ──▶ IT foothold   ──▶ Owns an IP    ──▶ Obeys anyone   ──▶ Write lands,
  on Shodan         credential        laterals into OT  = trusted         who reaches it     no attribution
  145k+ ICS exposed a claim, not      no identity       trust by          Modbus·DNP3·       who? · revoke where?
                    a machine         boundary          position          OPC UA

  The chain collapses to one equation:
  ① no asset identity + a protocol that authenticates nothing ⇒ authority = reachability.
  ② nothing checks who spoke ⇒ you can't say which asset did it, nor revoke the party across an org boundary.
```

No exploit chain: an internet-reachable asset, a protocol that obeys any speaker, a flat network that trusts a position. Every link is a missing identity, and none can be closed with a patch.

**Exposed, then accessed.** The front door is already indexed: research found **145,000+** internet-exposed ICS services across 175 countries (Censys, 2024), and opportunistic actors scan for exposed VNC and HMI at internet scale (CISA AA25-343A). Access rarely needs an exploit: in one documented class a commodity crew compromised `75+` internet-exposed PLCs using the vendor's default password, or none at all (CISA AA23-335A). Where a credential exists it is a bearer secret anyone can copy: a _claim_, never the identity of the machine behind it.

**Converged, then flat.** The air gap is folklore. An IT-ransomware or vendor-account foothold flows straight into OT because no independent identity boundary stops it, and remote-access sprawl widens the bridge every year: **55%** of OT environments run four or more remote-access tools, 33% run six or more (Claroty Team82). Inside, IP and VLAN segmentation authorize by position: a foothold that owns a trusted address is indistinguishable from the operator. **13%** of mission-critical OT assets have an insecure internet connection, and **36%** of exposed engineering-workstations and HMIs (the crown jewels) carry a known-exploited vulnerability (Claroty Team82).

**The protocol obeys, and no one can say who.** Modbus, DNP3-base and PROFINET carry no authentication; of **14,220** exposed OPC UA servers, over half allow unauthenticated access and roughly 80% support the plaintext "None" mode (Bitsight TRACE). A `FrostyGoop`-class attack weaponized Modbus/TCP `:502` register writes to cut heating to ~600 apartment buildings for around two days in sub-zero temperatures, against a landscape of ~46,000 internet-exposed Modbus devices. And with no device identity, the operator cannot say _which_ controller or session issued the destructive write, nor revoke the party that sent it across the vendor boundary.

> The recurring pattern the whole wave shares: **(a)** a default or absent credential on an internet-reachable device; **(b)** an unauthenticated protocol that cannot verify the authority of the speaker; **(c)** no device identity and no attribution: the network trusts an IP and a topology, never an identity. So authority collapses to _reachability_, and reachability is exactly what the internet, remote-access tools and IT/OT convergence hand an attacker for free. Detection tells you an asset is misbehaving, inside your own plant, after the fact. The strictly-stronger move is to change _what the network trusts_.

## The reframe: the address is the asset

Whisper has one primitive: **the address is the identity**. A routable IPv6 `/128` out of `2a04:2a01::/32` (announced by **AS219419**), deterministically derived from a key, DNSSEC-signed to the IANA root, [DANE-EE](/docs/dane) pinned (`3 1 1`), and [RDAP](/docs/rdap)-registered, re-derivable and verifiable by anyone with `dig`.

Point it at the asset. Whisper derives each PLC's (or each gateway's, historian's or RTU's) `/128` from the **public key** it _already_ holds: the public half of its OPC UA `ApplicationInstanceCertificate`, an IEEE **802.1AR IDevID**, a TPM, or a secure element, with the OPC UA **ApplicationUri** (or a bare asset serial) as the domain separator. The private key never leaves the asset; only its public `SubjectPublicKeyInfo` is an input. There is no re-flashing of a brownfield plant: you bind the identity the asset was born with, and even an identity-less Modbus PLC behind a gateway gets a verifiable network identity, a PTR and an RDAP object for the first time.

The reframe is precise about _where_ it acts. Whisper works at the **reachability layer** (the IP, DNS and transport boundary), not inside the fieldbus command path. It changes _who may reach and speak to_ the asset from a position on a flat segment into a cryptographic identity the asset holds and demonstrates with its own key. A request either proves it is the asset (or the party) it claims to be, verifiable across the org boundary, or it has no reach at all, before a single detection rule ever runs.

```
  TODAY: authority is a POSITION  (trust by reachability)

  Any foothold        Flat OT segment          every asset on the segment reachable
  owns a trusted ──▶  position ✓ · identity ✗ ──▶ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢
  IP                                              ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢ ▢
                                                  reach the segment → reach every PLC on it
  ─────────────────────────────────────────────────────────────────────────────────────
  UNDER IDENTITY: reach is bound to the ASSET's key

  Reach + speak   ─key ✓─▶  One asset: its pinned /128         The foothold still owns its IP…
  terminates at              DANE-EE 3 1 1 · verifiable         …but reach now demands the asset's key,
  the asset's /128 ─no key─▶ no reach it can prove   cross-org  which it never holds.
                                                                "Owns an IP → trusted" loses its leverage:
                                                                a trusted address inherits nothing it can prove.
```

Detection asks "is this asset behaving?" Identity asks "is this the asset, and may this party reach it?" That's a strictly stronger question a foothold on the segment cannot answer. Trust by position closes because reach is now bound to a key.

> **The ApplicationUri is the public name: the `/128` is its cryptographic counterpart.** OPC UA already binds a globally-unique `ApplicationUri` into the instance certificate's SAN and _fails the session_ if they disagree (`BadCertificateUriInvalid`). That's genuinely good key-derived naming, but it lives in a local, per-site TrustList, the spec _explicitly discourages_ commercial CAs, and revocation is a local CRL edit no integrator or vendor outside the plant ever sees. Pass that `ApplicationUri` (or the asset serial) as the `device_id`; the `/128` derives from the asset's key _with the `ApplicationUri` as the domain separator_, so **the `ApplicationUri` alone yields nothing**. 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 whereabouts. The same asset under two operators yields two unrelated `/128`s: no outsider can link it across sites.

## What changes

Nothing here is a new detection rule. Each row is an exposure technique that stops being _possible_ at the reachability layer, not one you catch after the fact.

| The exposure today | Why it dies under identity |
|---|---|
| **Owns a trusted IP → speaks to every asset** | Authority is the asset's key, not its place on the segment. A foothold that inherits a trusted address inherits nothing it can prove. Every forgery is a DNSSEC / DANE inconsistency any verifier catches with stock tools: `dig -x` names an identity whose `AAAA` and TLSA don't agree. |
| **Rotate egress on a remote-maintenance session** | Identity is not the source IP. The _last IP_ was never the credential, so a vendor session hopping clouds or residential proxies changes nothing about whether the caller can prove the asset. The graph still names the operator behind it. |
| **No one can verify an asset across the org boundary** | The asset owner, the integrator and the vendor each verify the same `/128` from _public DNS_: no shared flat network, no shared private CA, no VPN or jump-host in the middle. The gap none of those ever closed. |
| **Revocation is a local CRL edit no one outside sees** | One `revoke` tears down the `/128`, its PTR, and its DANE pin worldwide at DNS-TTL speed, cross-org, verifiable with the same stock tools. Compromise one asset and you have cut off _that asset_, not filed a TrustList change no vendor will ever fetch. |

> **What it does _not_ change, stated up front.** A forge-proof address governs _who may reach and speak to_ the asset. It does **not** add authentication to Modbus, DNP3 or PROFINET on the wire, and it does **not** stop a purely-internal insecure-protocol write once an attacker already has an OT-segment foothold and the controller can't verify command authority. Closing that last inch needs identity enforced _in the command path_: see [Honest scope](#honest-scope), below, before you read another line.

## Provision an asset identity

Provisioning is one control-plane call to `POST https://graph.whisper.security/api/query` with your `X-API-Key` header. You pass the asset's **public** key material (the base64 `SubjectPublicKeyInfo` of its OPC UA `ApplicationInstanceCertificate` / IDevID / TPM / secure-element key) and its `ApplicationUri` as the `device_id`; you get back the deterministic `/128` and a WireGuard config to source the asset's traffic from that address.

### The call

```
CALL whisper.agents({op:'connect', args:{
  tier:'wireguard',
  identity_public_key:'<base64 SPKI of the asset key>',   # public half of its OPC UA app-instance / IDevID / TPM cert
  device_id:'urn:example-plant:opcua:line3.plc-42'       # the OPC UA ApplicationUri (or a bare asset serial)
}}) YIELD op, ok, status, result, error
RETURN op, ok, status, result, error
```

**Over stock tools**: `jq` builds the JSON body so the Cypher's own quotes never fight the shell:

```sh
# the public key only. The private key never leaves the asset's secure element
Q="CALL whisper.agents({op:'connect', args:{tier:'wireguard', \
   identity_public_key:'MFkwEwYHKoZIzj0…SPKI', device_id:'urn:example-plant:opcua:line3.plc-42'}}) \
   YIELD op, ok, status, result, error RETURN op, ok, status, result, error"

curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  --data "$(jq -nc --arg q "$Q" '{query:$q}')"
```

### The response

```
# result carries the deterministic identity plus a ready-to-apply tunnel
address        2a04:2a01:a55::502
fqdn           uri-3f2a4e0.asset.<tenant>.agents.whisper.online
ptr            uri-3f2a4e0.asset.<tenant>.agents.whisper.online
state          active                       # DNSSEC + DANE-EE (3 1 1) live at provision time
wireguard_config   [Interface] …            # source the asset's traffic from its own /128
```

The asset now has a name it can prove and an address it egresses from. Reverse DNS resolves the `/128` to that identity, a TLSA record pins the leaf key, and RDAP registers the object under `2a04:2a01::/32`: the full [seven-proof](/docs/verify) chain, published atomically with the allocation, and each mint recorded in the [Merkle transparency log](/docs/transparency).

### Idempotency and errors

The call is deterministic and honest about conflicts: conservative in what it emits, liberal in what it accepts.

| You send | You get |
|---|---|
| The **same** asset key + `ApplicationUri` again | The **same** `/128`: idempotent, safe to retry, safe to run on every commissioning boot. No duplicate identities across a re-run. |
| The same asset key with a **different** `device_id` on your tenant | `409 Conflict`: a device key binds to exactly one identifier. The `error.detail` tells you which `ApplicationUri` it is already bound to. |
| A **non-string** `device_id` (a number, an array, null) | `400` with an actionable `detail`, never an opaque `500`. Send the `ApplicationUri` as a string. |

> **Shipped & live.** This provisioning path (an asset `/128` derived from the device's public key + its `device_id`) is in production today. The `device_id` argument is generic: pass an OPC UA `ApplicationUri`, an `802.1AR` serial, or any native asset identifier. A first-class typed `--applicationuri` flag is on the _roadmap_; today, provision through the control-plane call above, then drive it with `whisper verify` / `whisper policy` / `whisper logs` / `whisper kill --revoke`.

### Revoke worldwide: govern in between

A compromised controller, 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, and it is provable with the same stock tools that proved the identity existed, no Whisper software required.

```sh
# the control-plane op…
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:a55::502'}})
# …or the CLI
whisper kill --revoke 2a04:2a01:a55::502

# now prove it, worldwide, at DNS-TTL speed:
dig -x 2a04:2a01:a55::502 +short                       # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:a55::502
# -> {"is_whisper_agent": false, ...}
```

Revocation is the kill-switch, but the same control plane governs what a _live_ asset may reach in between. Because egress is source-bound to the asset's `/128`, policy is enforced by name and by address: **default-deny**, allow only the historian and the SCADA controller, cap the traffic, and kill it in one call. This is micro-segmentation at the _asset_, not the VLAN:

```sh
# default-deny: this PLC may reach ONLY its historian, its controller, and the vendor OTA endpoint
whisper policy set 2a04:2a01:a55::502 --default deny \
      --allow historian.example-plant.internal,scada-ctrl.example-plant.internal,ota.vendor.com

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

Compromise one asset and you've cut off _that asset_, not filed a change no one downstream will fetch. The cross-org blast-radius failure mode is structurally removed. Full policy surface: [Egress governance](/docs/egress-governance). And because each telemetry stream and setpoint acknowledgement can be [signed under](/docs/sign-outputs) the asset's forge-proof `/128`, the historian, the integrator and a regulator can trust the numbers came from the real asset, not a spoofed outstation.

## The MUD angle: declared egress, finally enforced

An identity you can prove is also an identity you can _govern_. OT already has the right idea for egress; it just never had a way to make the declaration portable, verifiable, or enforced anywhere but the nearest switch. That is the one place Whisper's egress governance turns a good idea into a control.

**RFC 8520 Manufacturer Usage Description** (an IETF Internet Standard) lets a device emit a URL (via DHCP option `161`/`112`, an LLDP vendor TLV, or the X.509 `id-pe-mud-url` extension that co-locates with an 802.1AR IDevID) declaring the exact hosts it should ever talk to: _"my manufacturer's cloud, my controller, the local subnet, and nothing else."_ Its fatal weakness is that the declaration is only a suggestion, validated and enforced at the nearest hop by a local MUD manager: spoofable, site-scoped, with no portable identity behind it and no shared revocation.

```
                                    ┌─ PLAIN RFC 8520: a suggestion at the nearest hop
                                    │  Local MUD manager ─▶ Allow-list on the nearest switch
  Device emits a MUD URL  ──────────┤                       spoofable · site-scoped · no shared revocation
  DHCP 161/112 · LLDP · X.509       │
                                    └─ WITH WHISPER: bound to a verifiable identity, enforced at egress
                                       Bound to the asset's /128 ─▶ Default-deny egress at the /128
                                       DNSSEC · DANE · cross-org     enforced wherever traffic egresses · externally checkable
```

MUD declared what an asset may talk to; the declaration was only a suggestion at the nearest switch. Whisper binds it to a globally-verifiable identity and enforces it wherever the traffic egresses: a direct implementation of RFC 8520 (NIST NCCoE SP 1800-15), keyed to a verifiable identity instead of a spoofable URL.

```sh
# default-deny at the asset's own /128, the MUD manifest, made enforceable
whisper policy set 2a04:2a01:a55::502 --default deny \
      --allow historian.example-plant.internal,scada-ctrl.example-plant.internal,ota.vendor.com
#   ✓ egress governed at /128: 3 destinations allowed, everything else denied

# cap a runaway asset; cut a compromised one off worldwide
whisper budget set 2a04:2a01:a55::502 --cap 50MB/day --then revoke
```

This is a near-verbatim fit for **CISA CPG 2.0**'s "permit only required communications" and a strengthening of RFC 8520 itself: the manufacturer-declared intent becomes cryptographically pinned, externally checkable, and enforced at the asset's own egress point instead of a switch ACL an attacker on the segment can route around. Recipes: [MUD egress · verify · attribute](/docs/ot-recipes).

## Verify it: keyless, no account

The identity is public by design, so anyone (the asset owner, an integrator, a vendor PSIRT, an auditor, a regulator) can check an asset without your key and without taking Whisper's word for it. This is the keyless half of the two-tier surface: verify with no key, provision and govern with your key.

```sh
# no key, no account: re-derive and verify the asset's identity, trustless to the IANA root
whisper verify --trustless 2a04:2a01:a55::502
#   ✓ DNSSEC chain valid to the IANA root
#   ✓ DANE-EE (TLSA) leaf matches the asset's key
#   ✓ RDAP: registered under AS219419 · 2a04:2a01::/32

# or with only curl, the keyless full-chain verdict
curl -s https://whisper.online/verify-identity/2a04:2a01:a55::502
# { "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:a55::502 +short
# uri-3f2a4e0.asset.<tenant>.agents.whisper.online.

# the registry object for the /128: RDAP, typed JSON
curl -s https://whisper.online/ip/2a04:2a01:a55::502 | jq '.handle, .parentHandle'
# "2A04:2A01:A55::502/128"
# "2A04:2A01::/32"
```

The `--trustless` flag is the point: nothing there calls back to Whisper's own API as an authority. The CLI re-derives the DNSSEC chain to the IANA root, on your machine, with your resolver. An integrator or a regulator can verify an asset _outside_ your plant's tenancy, with no shared network and no shared private CA: the cross-org check a local OPC UA TrustList could never offer. Full mechanics: [Verify an agent](/docs/verify) and [DANE & TLSA](/docs/dane).

## 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: a reconnaissance tripwire the OPC UA TrustList's private, out-of-band registry never gave you. Discovery is the first move of every OT incident; `op:lookups` surfaces it _while it's happening_, not in the post-mortem:

```sh
# who resolved / RDAP-queried this asset's identity, and when
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:a55::502', window:'24h'}})

# the same reverse-observability view, keyless, per address
curl -s https://whisper.online/ip/2a04:2a01:a55::502/lookups | jq
# → one source RDAP-queried 214 distinct asset identities in 9 minutes: a plant sweep, before any write lands
```

Paired with `op:logs` (the asset's _own_ outbound activity) and `/ip/<addr>/transparency` (its ordered lifecycle), you have both halves: what an asset reaches out to, and who is reaching in to enumerate it. Hand the enumerating source straight to the graph, below.

## Name what already got in

Identity stops the next forgery. It does not name the operator behind the remote-maintenance sessions already in your logs, and you will not re-key a brownfield plant by Monday. So the same platform back-traces them, and the attribution _survives_ the rotation because it fingerprints the operator's infrastructure and tooling, not the ephemeral egress IP.

```
   what your OT SOC sees: a rotating, meaningless "last IP"

  Suspect session ──┬─▶ AWS eu-central  3.68.x.x  ─┐
  remote maintenance│   GCP europe-w4   34.90.x.x ─┼─infra genealogy─▶  One operator
  from your SOC logs│   Azure westeu    20.61.x.x ─┘  (ASN + hosting)   ASN + hosting genealogy
                    └─▶ residential-proxy swarm    ─JA4 fingerprint─▶  + JA4 / JA3 fingerprint
                        71.x·Comcast 82.x·KPN 99.x·Orange                └─▶ evidence chain → your SIEM

   the JA4 fingerprint lives in the TLS handshake the proxy can't rewrite: the one input never relied on is the last IP
```

Attribution survives rotation because it tracks the infrastructure and the tooling, not the ephemeral egress IP. The JA4 fingerprint lives in the TLS handshake the proxy can't rewrite. The one input never relied on is the last IP.

Take a suspect egress IP straight from your SOC logs and ask the graph who really operates it. This runs read-only over the same public graph API, with your key:

```sh
# who really operates a remote-access host, even behind a CDN or a cloud front
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(\"34.90.x.x\")"}'
# operator fingerprinted across AWS / GCP / Azure; residential swarm collapsed by JA4
```

You can also express the abuse as a _question_ rather than a signature ("one source touching N distinct asset-identities in a window") in read-only Cypher, and catch a plant sweep by its shape, not by a pattern you had to know in advance:

```sh
# enumeration caught by its shape: read-only Cypher over the public graph API
curl -s https://graph.whisper.security/api/query -H "X-API-Key: whisper_live_xxx" \
  -H 'content-type: application/json' -d '{"query":"MATCH (src)-[t:TOUCHED]->(a:AssetIdentity)
  WHERE t.window = \"15m\" WITH src, count(DISTINCT a) AS assets
  WHERE assets > 50 RETURN src, assets ORDER BY assets DESC"}'
# → 1 source → 214 distinct asset identities / 15m: a plant sweep, one operator
```

The read-only verbs (`identify`, `origins`, `walk`, `variants`, `history`) run over that one endpoint against a live internet-infrastructure graph of fused BGP, DNS, WHOIS, TLS, hosting, and threat intelligence. Cloud rotation collapses through `origins` and `walk`, which cluster shared ASN, hosting, and certificate lineage into one infrastructure genealogy; `history` gives a timeline over a suspect operator. Every answer is reproducible, replayable JSON: the paper trail a cross-org remote-access finding needs, not a screenshot.

> These graph verbs are the **API surface**: you call the endpoint directly, as above. There is no `whisper identify` / `graph` / `export` CLI subcommand; the CLI covers the control plane (`create`, `verify`, `policy`, `logs`, `kill`), and the graph is the query API. See [Graph & cognition](/docs/graph-api).

## Honest scope: what this does and doesn't do

We will say this before your assessor does. Identity governs _who may reach and speak to_ an asset, attributes them, and revokes them across the org boundary. It does **not** reach inside the plant's insecure protocols. Read both columns: the boundary is the same one that makes the cure honest.

| What a forge-proof address closes | What it does not, and where the last inch must live |
|---|---|
| **Forge-proof asset identity at the reachability layer**: kills "trusted because it's on the segment / owns an IP." A foothold that inherits a trusted address inherits nothing it can prove. | It does **not** stop a purely-internal insecure-protocol write once an attacker already has an OT-segment foothold and the controller can't verify command authority. Closing _that_ needs identity enforced **in the command path** (at the PLC, a protocol-aware broker-gateway, or the engineering workstation) or the `FrostyGoop`-class local Modbus write still lands. |
| **Publicly verifiable across the org boundary**: asset owner, integrator and vendor each verify the same `/128` from public DNS, no shared flat network or shared private CA. The gap no VPN or jump-host ever solved. | It does **not** add authentication to Modbus, DNP3 or PROFINET _on the wire._ Whisper changes who may reach and speak to the asset, not what the asset accepts once reached; the protocol's plaintext, spoofable, replayable nature is unchanged. |
| **Attribution + cross-org revocation**: name the operator behind a rotating remote-access session, and tear an asset's identity down worldwide at DNS-TTL, not a local CRL edit invisible outside the plant. | It does **not** fix an unpatchable CVE, produce the asset's SBOM, patch its firmware, or cover human identity / MFA (IEC 62443 CR 1.1) or host-level audit logging. Those stay with your existing stack. |
| **MUD-style egress governance**: constrain an asset to only its declared destinations, keyed to a verifiable identity. It contains C2, exfil and lateral movement, and attacks the convergence-bridge and remote-access-sprawl surfaces directly. | It is **additive, never a replacement**: the asset still performs its own OPC UA / TLS handshake; Whisper makes that identity globally verifiable, attributable and revocable. It does not remove a stolen-but-legitimate enrolled identity, a supply-chain implant, or a physical/insider console, and a legitimate operator can still send a catastrophic setpoint. |

Net: Whisper converts OT from _"anyone with reachability is trusted"_ to _"only cryptographically-identified, egress-governed, attributable, cross-org-revocable parties are trusted."_ That neutralizes the access, convergence-bridge, flat-network, remote-access and attribution stages that carry the majority of 2023–25 incidents. Candid about the last inch: the insecure-protocol write must be enforced in-path. We complement that layer; we never claim to be it.

## Where it fits: standards, SIEM, integrations

Whisper is additive. It rides _on top of_ the anchors you already ship and the SIEM you already run: it replaces none of them, and it adds no inline chokepoint on an OT command path.

**Compliance.** A verifiable, revocable per-asset identity plus per-`/128` egress logs and the attribution graph are ready-made evidence for the clauses the standards actually require. It maps most directly to the **EU CRA** (Reg (EU) 2024/2847) Annex I essential requirements (**2(d)** "protection from unauthorised access by authentication, identity or access-management systems", **2(j)** minimising attack surface, and **2(l)** recording and monitoring access), a market-access deadline with the CE-marking obligations landing **11 December 2027** and reporting from 11 September 2026. It satisfies **IEC 62443-4-2 CR 1.2** (unique device identification) with a HW-key-derived, DANE-pinned identifier a certifier can verify externally, and implements **62443-3-3** zones-and-conduits as micro-segmentation at asset granularity. It is a near-verbatim fit for **CISA CPG 2.0**'s IPv6 asset inventory and "permit only required communications", aligns with **NIST SP 800-82r3** and **NISTIR 8259A** device identification, and serves the segmentation and continuous-monitoring intent of the **TSA** pipeline security directives and **NIS2 Art.21**. The clause-by-clause mapping lives in [IEC 62443 · EU CRA · TSA](/docs/ot-compliance).

> **Honest caveats, stated up front.** Identity is not full authentication: the asset still performs its own OPC UA / TLS handshake; we claim the identity and key-pinning, not end-to-end device auth. The `/128` egress logs are network-side, not host audit logging. We do not produce your SBOM or patch your firmware, and human I&A (CR 1.1) is out of scope. NIS2, the TSA SDs and CPG 2.0 are _organisational_ obligations (the product is evidence toward them, never "makes you compliant") and the CRA↔62443 harmonised-standards crosswalk is still settling, so we map to 62443 today.

**Nothing issued in the dark.** Every identity mint and every revoke lands in a public, append-only [RFC 6962 Merkle transparency log](/docs/transparency) (Ed25519-signed C2SP checkpoints, each root anchored to Bitcoin via OpenTimestamps), an auditable, non-repudiable issuance and revocation trail a regulator can replay. _Honest status:_ it is tamper-evident and Bitcoin-anchored today, but **not yet independently witnessed** (our two servers co-signing is availability, not independence); the log already speaks the C2SP witness protocol, so any external witness can co-sign.

> **Shipped vs roadmap.** The **Splunk**, **Microsoft Sentinel** and **OpenCTI** connectors ship today; findings arrive as signed JSON mapped to CEF and ECS fields. **Roadmap**, labelled as such and not yet available: **STIX 2.1 over TAXII** export, an ICS-sector machine-readable export, and the first-class typed `--applicationuri` CLI/API argument.

**Integrations (proposed, not vendor-endorsed).** Whisper anchors the cloud, IP and transport boundary. Never the Modbus, DNP3 or PROFINET command path, the IEC 61850 GOOSE/MMS substation bus, or a fieldbus authenticator:

- **OPC UA (ApplicationInstanceCertificate + GDS).** Derive the `/128` from the public half of the app-instance cert, and [DANE-pin](/docs/dane) that cert under the public DNSSEC chain: cross-org verifiable with no commercial CA, which the spec discourages anyway. Keeps the `ApplicationUri`↔cert binding OPC UA already enforces; adds public verifiability and DNS-TTL revocation the local TrustList / GDS can't. Complements the trust model; does not replace it.
- **RFC 8520 MUD.** Take the device's declared egress and enforce it as default-deny governance at the asset's verifiable `/128`: the missing enforcement and portable identity plain MUD never had. Complements the manufacturer declaration; does not replace it.
- **IEEE 802.1AR IDevID / BRSKI.** The cleanest bridge: derive the `/128` from the device's birth-certificate public key. The publicly verifiable, DNSSEC-anchored layer _on top_ of the silicon identity you already stamp; no second PKI, no re-flashing the fielded fleet.
- **The identity-less legacy floor.** A Modbus, DNP3-base or PROFINET asset behind a gateway gets a verifiable network identity, a PTR and an RDAP object for the first time: an overlay, exactly the compensating-control pattern NIST SP 800-82r3 sanctions where a device can't do per-device identity itself.
- **AWS IoT Core / Azure IoT.** Your X.509 + mTLS stays; the cloud remains the CA. Whisper adds the out-of-tenancy identity anchored in DNSSEC / DANE, so a peer can verify an asset outside that cloud. Complements IoT Core mTLS; does not replace it.

And it is built to **fail open**: a Whisper outage never bricks a controller; checks degrade to the anchors you already ship and connectivity is preserved. Anycast on AS219419, no single node in the path.

## Next

- [Asset & PLC identity](/docs/asset-identity): how the `/128` is derived from the device key and the `ApplicationUri`, in depth
- [Control plane](/docs/control-plane): the full `whisper.agents` op set the provisioning call belongs to
- [IEC 62443 · EU CRA · TSA](/docs/ot-compliance): the clause-by-clause evidence mapping
- [MUD egress · verify · attribute](/docs/ot-recipes): runnable recipes for enforcing a MUD manifest, verifying an asset keyless, and back-tracing a remote-access session

---

← [Asset & PLC identity](/docs/asset-identity) · [OPC UA · MUD · 62443 →](/docs/ot-integrations)
