diff --git a/README.md b/README.md index 8f2e79000d..2bdf1707a7 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,18 @@ And brings stand-alone (Java) utils such as: * OPC-UA Server: Enables you to communicate with legacy devices using PLC4X with OPC-UA. * PLC4X Server: Enables you to communicate with a central PLC4X Server which then communicates with devices via PLC4X. -It also provides (Java) tools for usage inside an application: Both the integration modules as also the OPC-UA Server and PLC4X Server are being released as part of the plc4x-extras release. +It also provides (Java) tools for usage inside an application: + * Connection Cache: New implementation of our framework for re-using and sharing PLC connections * OPM: Object-Plc-Mapping: Allows binding PLC fields to properties in java POJOs similar to JPA -* Scraper: Utility to do scheduled and repeated data collection. +* Event Pump: Utility to do scheduled and repeated data collection. You describe what to read and + when, and it delivers each response to a listener, handling the scheduling, connection leasing and + slow-response back-pressure for you. It replaces the `Scraper` from PLC4X 0.13 and earlier - see + the Event-Pump page on the website for a migration guide. +* Capture Replay: Development helper that replays recorded network traffic, so passive-mode drivers + can be worked on away from the plant. ## Getting started diff --git a/THREAT-MODEL.md b/THREAT-MODEL.md index 5146e94bdf..ecc8ce23c9 100644 --- a/THREAT-MODEL.md +++ b/THREAT-MODEL.md @@ -10,11 +10,20 @@ - **Scope of this model**: the **main `apache/plc4x` repository only** — driver layer, SPI, transports, code-generation runtime, and the four language bindings shipped here (`plc4j`, `plc4go`, `plc4c`, `plc4py`). + Which **drivers** are covered is not listed here: this model delegates + that to the per-language driver matrix in + `website/.../protocols/index.adoc` — a green check means in scope, + anything else means out. See §3 item 6 for the rule. The separately-released `plc4x-extras` repository (OPC-UA Server, PLC4X - Server, Calcite / Camel / Kafka-Connect / Karaf / NiFi integrations, - Connection-Cache, OPM, Scraper) is **out of scope** of this document - *(maintainer: Chris Dutz, 2026-05-29 16:06Z)* and will be modelled - separately if and when it is brought into the program. + Server, Calcite / Camel / Kafka-Connect / Karaf / NiFi integrations) + is **out of scope** of this document *(maintainer: Chris Dutz, + 2026-05-29 16:06Z)* and will be modelled separately if and when it is + brought into the program. The **Scraper** was previously listed here; + it no longer exists in either repository — it was deleted and replaced + by the Event Pump, and now falls under §3 item 11 instead. + **Connection-Cache** and **OPM** were also listed here, but they are in + *this* repository, at `plc4j/tools/connection-cache` and + `plc4j/tools/opm` — that classification is unresolved; see §14 Q33. - **Version / commit**: drafted against the default branch (`develop`), HEAD `32b4f0c` ("build(deps): bump jackson.version from 2.21.3 to 2.21.4" as of draft time). A vulnerability report against PLC4X release *N* is @@ -115,23 +124,24 @@ The main repo carves naturally into nine families with distinct threat profiles. Anything marked **out** below reappears in §3 with the reason. -| Family | Representative entry point | Touches outside the process? | In this model? | -|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| -| **Java public API (`plc4j/api`)** | `PlcDriverManager.getConnectionFactory().getConnection(url, auth)` | no — pure dispatch | **yes** | -| **Java SPI / runtime (`plc4j/spi`)** | `AbstractPlcConnection`, `ConversationContext`, `Plc4xProtocolBase` | no — orchestration | **yes** | -| **Per-protocol drivers (`plc4j/drivers/{s7,modbus,opcua,ads,knxnetip,bacnet,c-bus,can,canopen,ctrlx,eip,firmata,iec-60870,logix,open-protocol,plc4x,profinet,profinet-ng,umas}`)** | one `PlcDriver` per protocol | **yes — network**, via the registered transport | **yes** | -| **Transports (`plc4j/transports/{tcp,udp,serial,raw-socket,pcap-replay,pcap-shared,socketcan,can,virtualcan,test}`)** | Netty socket / serial / pcap | **yes — sockets, serial devices, libpcap** | **yes** | -| **OPC UA security subsystem** (`plc4j/drivers/opcua/.../security/`, `.../context/SecureChannel`) | certificate verifier, security-policy negotiator, secure-channel encoder | **yes — TLS-like primitives on the wire** | **yes — distinguished from other drivers; see §8 P1** | -| **Code-generation runtime (mspec read/write generators)** under `plc4j/.../readwrite/`, `plc4go/protocols/`, `plc4c/generated-sources/` | generated parsers / serializers | **yes — driven by remote-PLC bytes** | **yes** — same trust level as the driver that uses it | -| **Go bindings (`plc4go/`)** | `plc4go.NewPlcDriverManager()` | as core | **yes** | -| **C bindings (`plc4c/`)** | `plc4c_*` API | as core | **yes — but README declares "not ready for usage"** *(documented: `README.md`)* — see §3 | -| **Python bindings (`plc4py/`)** | `plc4py` import | as core | **yes — but README declares "not ready for usage"** *(documented: `README.md`)* — see §3 | -| **.NET bindings (`plc4net/`)** | n/a | n/a | **out** — README declares "not ready for usage - abandoned" *(documented: `README.md`)* | -| **PLC4X proxy driver (`plc4j/drivers/plc4x`)** | `plc4x:tcp://host/?remote-connection-string=...` | network — outbound to a remote PLC4X server | **yes for the client side; the server side is in `plc4x-extras` and out of scope** *(documented: `website/.../protocols/plc4x.adoc`)* | -| **Simulated / mock drivers (`plc4j/drivers/{simulated,mock}`)** | `simulated://...` | no | **yes for code-quality; not a security surface** | -| **Utilities (`plc4j/utils/{pcap-replay,pcap-shared,plc-simulator,raw-sockets,test-generator,test-utils}`)** | dev tooling | varies | **out** — dev / test only *(inferred — §14 Q2)* | -| **Code-generation tooling under `code-generation/`** | mspec compiler invoked at build | host filesystem | **out** — build-time, not in the deployed artifact *(inferred — §14 Q2)* | -| **`tools/`, `images/`, `media/`, `website/`, `licenses/`, `src/`, `.idea/`, `.github/`, `.mvn/`, `Dockerfile`, `docker-compose.yaml`, `Jenkinsfile`** | tooling, docs, build orchestration | n/a | **out** *(inferred — §14 Q2)* | +| Family | Representative entry point | Touches outside the process? | In this model? | +|-------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| **Java public API (`plc4j/api`)** | `PlcDriverManager.getConnectionFactory().getConnection(url, auth)` | no — pure dispatch | **yes** | +| **Java SPI / runtime (`plc4j/spi`)** | `AbstractPlcConnection`, `ConversationContext`, `Plc4xProtocolBase` | no — orchestration | **yes** | +| **Per-protocol drivers, green in the driver matrix** (`plc4j/drivers/*`, `plc4go/internal/*`) | one `PlcDriver` per protocol | **yes — network**, via the registered transport | **yes** — a green check in that language's column of `website/.../protocols/index.adoc` is the test; see §3 item 6 | +| **Per-protocol drivers, not green in the driver matrix** | a `PlcDriver` that is unfinished or only partially implemented | opens a socket, but is not a supported surface | **out** — not declared usable, therefore not a supported product surface *(documented: `website/.../protocols/index.adoc`)* — §3 item 6 | +| **Transports (`plc4j/transports/{tcp,udp,serial,raw-socket,pcap-replay,pcap-shared,socketcan,can,virtualcan,test}`)** | Netty socket / serial / pcap | **yes — sockets, serial devices, libpcap** | **yes** | +| **OPC UA security subsystem** (`plc4j/drivers/opcua/.../security/`, `.../context/SecureChannel`) | certificate verifier, security-policy negotiator, secure-channel encoder | **yes — TLS-like primitives on the wire** | **yes — distinguished from other drivers; see §8 P1** | +| **Code-generation runtime (mspec read/write generators)** under `plc4j/.../readwrite/`, `plc4go/protocols/`, `plc4c/generated-sources/` | generated parsers / serializers | **yes — driven by remote-PLC bytes** | **yes** — same trust level as the driver that uses it | +| **Go bindings (`plc4go/`)** | `plc4go.NewPlcDriverManager()` | as core | **yes** | +| **C bindings (`plc4c/`)** | `plc4c_*` API | as core | **yes — but README declares "not ready for usage"** *(documented: `README.md`)* — see §3 | +| **Python bindings (`plc4py/`)** | `plc4py` import | as core | **yes — but README declares "not ready for usage"** *(documented: `README.md`)* — see §3 | +| **.NET bindings (`plc4net/`)** | n/a | n/a | **out** — README declares "not ready for usage - abandoned" *(documented: `README.md`)* | +| **PLC4X proxy driver (`plc4j/drivers/plc4x`)** | `plc4x:tcp://host/?remote-connection-string=...` | network — outbound to a remote PLC4X server | **yes for the client side; the server side is in `plc4x-extras` and out of scope** *(documented: `website/.../protocols/plc4x.adoc`)* | +| **Simulated / mock drivers (`plc4j/drivers/{simulated,mock}`)** | `simulated://...` | no | **yes for code-quality; not a security surface** | +| **Utilities (`plc4j/utils/{pcap-replay,pcap-shared,plc-simulator,raw-sockets,test-generator,test-utils}`)** | dev tooling | varies | **out** — dev / test only *(inferred — §14 Q2)* | +| **Code-generation tooling under `code-generation/`** | mspec compiler invoked at build | host filesystem | **out** — build-time, not in the deployed artifact *(inferred — §14 Q2)* | +| **`tools/`, `images/`, `media/`, `website/`, `licenses/`, `src/`, `.idea/`, `.github/`, `.mvn/`, `Dockerfile`, `docker-compose.yaml`, `Jenkinsfile`** | tooling, docs, build orchestration | n/a | **out** *(inferred — §14 Q2)* | A finding is in-model only if it lands in a row marked "yes" — see §4 for the per-component reachability test. @@ -159,11 +169,14 @@ any of these will be closed with the cited disposition. PLC *(documented: `README.md`, `plc4j/api/.../PlcConnection.java` — single connect/read/write/subscribe shape; transport inventory is exclusively client)*. Server / gateway functionality (OPC-UA Server, - PLC4X Server, Calcite, Camel, Kafka-Connect, Karaf, NiFi adapters, - Connection-Cache, OPM, Scraper) is published in the separate - **`plc4x-extras`** repository *(documented: `README.md`)* and is - **explicitly out of scope of this document** *(maintainer: PMC chair, - 2026-05-29 16:06Z)*. → `OUT-OF-MODEL: unsupported-component`. + PLC4X Server, Calcite, Camel, Kafka-Connect, Karaf, NiFi adapters) is + published in the separate **`plc4x-extras`** repository *(documented: + `README.md`)* and is **explicitly out of scope of this document** + *(maintainer: PMC chair, 2026-05-29 16:06Z)*. → + `OUT-OF-MODEL: unsupported-component`. The Scraper, formerly listed + in this sentence, is gone entirely — §3 item 11. Connection-Cache and + OPM are in this repository under `plc4j/tools/`, not in extras — §14 + Q33. 3. **A defender against the embedding application itself.** The embedding application supplies every connection URL, the `PlcAuthentication`, every tag-address string, and every value written. Anything the @@ -196,6 +209,62 @@ any of these will be closed with the cited disposition. `Dockerfile` / `docker-compose.yaml` / `Jenkinsfile` are build, test, and documentation infrastructure *(documented: `README.md`; inferred for the rest — §14 Q2)*. → `OUT-OF-MODEL: unsupported-component`. + + The same carve-out covers **drivers that are not ready to be used**. + Rather than enumerate them here — a list that would go stale on every + driver change — this model **delegates the question to the + per-language driver matrix** in + `website/asciidoc/modules/users/pages/protocols/index.adoc`, which is + maintained alongside the code. The rule is: + + > **A driver is in scope of this threat model for a given language if + > and only if its cell in that language's column of the + > *"Current language support for protocols"* table is a green check + > (`icon:check[role="green"]`, "Implemented and usable").** + > + > The table has exactly two states. Anything that is not a green + > check is red `icon:times` — no driver exists, or one exists but is + > unfinished, only partially implemented, or judged by the + > maintainers not to be ready — and puts that language's driver + > **out of scope**. A vulnerability report against + > a driver that is not green is declined with + > `OUT-OF-MODEL: unsupported-component`, citing the matrix cell. + + The matrix is **per language**, and so is this rule: a protocol can + be in model in one language and out in another. C-Bus is the standing + example — the Go driver is green and in model, while the Java driver + is an unfinished skeleton and is not. + + Consequences worth stating plainly: + + - **The matrix is normative for scope.** Changing a cell changes what + this document covers, with no edit to this document required — + which is the point. §12 accordingly names a matrix change, not a + list change, as the revision trigger. + - **The second table on that page (*Features*) is not the scope + test.** It records which operations a driver implements, for + `plc4j` only. Scope is decided by the first table. + - **For `plc4j` the rule is enforced by the build.** + `plc4j-driver-all` bundles exactly the drivers green in the Java + column and nothing else *(documented: `plc4j/drivers/all/pom.xml`)*, + so a non-green driver reaches an application's classpath only if it + was depended on deliberately, by artifact. The other language + bindings have no equivalent meta-package, so there the matrix is + the only statement. Either way, presence on a classpath would not + make a driver an attack surface — being *drivable* does. Findings + in non-green drivers are code-quality issues, not vulnerabilities. + - **Green does not mean "implements every operation".** It means the + driver is declared ready: what it implements works, and it does not + advertise a capability it lacks. Several green drivers cover only + part of the operation set — AB-Ethernet reads but does not write, + Java CAN raw and Firmata write and subscribe but do not read (raw + CAN and Firmata have no meaningful read), IEC-60870-5-104 + subscribes only. Those still parse untrusted wire bytes on a path + an application drives, which is exactly the §4 B2 boundary, so they + are in scope. What puts a driver out is being unfinished — no + usable operation, or metadata that advertises what the code cannot + do *(maintainer: chrisdutz, 2026-08-21 — see §14 Q32)*. + 7. **The PLC device firmware, the network the device is on, and the physical process the device controls.** PLC4X is a wire-protocol library; bugs / exploits in the PLC itself (S7 vendor-specific @@ -226,6 +295,43 @@ any of these will be closed with the cited disposition. 10. **Supply-chain / build / release hygiene** — Maven action pinning, Maven Central signing, Jenkins build configuration, dependency freshness, reproducible builds. Out of model per the SKILL. +11. **Components that no longer exist on the `develop` branch.** This + model covers the code that is in the repository now. A report + against a module that has since been **removed or replaced** is not + actionable: there is nothing left to fix, no release will carry a + patch for it, and the disposition is not a judgement about whether + the report was technically correct *(maintainer: cdutz, + 2026-08-21)*. → `OUT-OF-MODEL: removed-component`. + + The worked example is the **Java Scraper**. It was a utility for + scheduled and repeated data collection; it has been deleted from the + tree and replaced by the **Event Pump** (`plc4j/tools/event-pump`, + commit `83023b0` "Implemented a new component for fetching data from + remote PLCs (replacement for the scraper)"). A finding in + `org.apache.plc4x.java.scraper.*` is closed as + `OUT-OF-MODEL: removed-component`, with the reporter pointed at the + Event Pump. Note that older editions of this document, and + `README.md`, still list the Scraper as a live component — see the + §14 Q33 cleanup item; those mentions are stale, not evidence that + the Scraper is still shipped. + + Two boundaries on this item, so it is not read too widely: + + - **It is about removal, not about age.** §1 says a report against + release *N* is triaged against the model as it stood at *N*. That + still holds for components that exist: a real flaw in a shipped + release is triaged on its merits even if the code has since been + refactored. Item 11 applies only where the component is *gone* on + `develop`, so no forward fix exists. If a removed component is + still present in a supported release line and the finding is + serious, that is a release-management question for the PMC, not a + model question — say so in the triage note rather than closing + silently. + - **"Moved" is not "removed".** Code that has been relocated — + renamed, split, or moved between repositories — is still in the + model at its new address, and the report should be re-pointed + rather than closed. Only code with no successor anywhere is + covered here. ## §4 Trust boundaries and data flow @@ -233,15 +339,15 @@ PLC4X has **two primary trust boundaries** plus one optional cryptographic one for OPC UA. A finding is in-model only when it cleanly maps to one of them. -| # | Transition | Authentication | Authorization | Notes | -|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| B1 | **Embedding application → PLC4X API surface** | trusted by construction | the API caller is trusted | URL string, `PlcAuthentication`, tag-address strings, write values *(documented)* | -| B2 | **PLC4X driver → remote PLC over the wire** (Modbus, S7 PUT/GET, BACnet/IP, IEC-60870-5-104, Profinet, CANopen, KNXnet/IP, C-Bus, DF1, AB-Ethernet, EtherNet/IP, UMAS, ADS/AMS without credentials) | **none — by protocol design** *(documented per protocol)* | none on the wire; whatever the embedding application enforces | The OT-network perimeter is the only security control. Wire bytes returned to the driver are **untrusted input crossing into the parser** *(inferred — §14 Q3)*. | -| B2-OPCUA | **PLC4X OPC UA driver → OPC UA server (encrypted policy)** | server cert verified against trust store *(when `trust-store-file` is set)*; client cert optional; username/password or token over the encrypted channel | server-side ACLs | Mutually authenticated (cert + cert) and encrypted at the higher security policies; **see §8 P1, §10 item 4** *(documented: `website/.../protocols/opcua.adoc`, `plc4j/drivers/opcua/.../security/`)* | -| B2-OPCUA-PERMISSIVE | **OPC UA driver in default (no `trust-store-file`) mode** | server certificates **are not validated** by default *(documented: `website/.../protocols/opcua.adoc` — "Unless explicitly disabled through configuration of `trust-store-file` all server certificates will be accepted without validation"; `plc4j/drivers/opcua/.../security/PermissiveCertificateVerifier.java`)* | n/a | This is the OPC UA driver's *default* behavior. See **§5a "insecure-default case"**. | -| B2-ADS | **ADS driver → Beckhoff TwinCAT (AMS route setup with credentials)** | username/password to the TwinCAT system for AMS-route setup, when a `PlcUsernamePasswordAuthentication` is supplied *(documented: `plc4j/drivers/ads/.../AdsProtocolLogic.java` lines 130–145)* | TwinCAT-side | The credentials are forwarded to the device; what the device does with them is outside PLC4X. ADS payload data itself is unauthenticated cleartext *(inferred — §14 Q9)*. | -| B3 | **PLC4X transport → host OS / NIC / serial port / libpcap** | OS-level | OS-level | Whatever the embedding process's UID, capabilities, and seccomp / AppArmor profile permit. The `raw-socket` transport needs `CAP_NET_RAW` on Linux *(inferred — §14 Q10)*. | -| B4 | **Code-generation runtime → operating system** at build time | n/a — only the developer runs this | n/a | Build-time only; not part of the deployed artifact (out per §3 item 6) | +| # | Transition | Authentication | Authorization | Notes | +|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| B1 | **Embedding application → PLC4X API surface** | trusted by construction | the API caller is trusted | URL string, `PlcAuthentication`, tag-address strings, write values *(documented)* | +| B2 | **PLC4X driver → remote PLC over the wire** (every cleartext protocol whose driver is green in the §3 item 6 matrix — Modbus, S7 PUT/GET, IEC-60870-5-104, CANopen, KNXnet/IP, C-Bus *(Go)*, AB-Ethernet, BACnet/IP *(Go)*, CAN raw *(Java)*, Firmata, EtherNet/IP incl. Logix, SLMP, UMAS, ADS/AMS without credentials) | **none — by protocol design** *(documented per protocol)* | none on the wire; whatever the embedding application enforces | The OT-network perimeter is the only security control. Wire bytes returned to the driver are **untrusted input crossing into the parser** *(inferred — §14 Q3)*. Protocols whose drivers are not green — Profinet (both the `profinet` and `profinet-ng` drivers), Java C-Bus, Java BACnet/IP, ctrlX, Open-Protocol — do not appear here: they are out of scope per §3 item 6, and the matrix, not this row, is the current list. | +| B2-OPCUA | **PLC4X OPC UA driver → OPC UA server (encrypted policy)** | server cert verified against trust store *(when `trust-store-file` is set)*; client cert optional; username/password or token over the encrypted channel | server-side ACLs | Mutually authenticated (cert + cert) and encrypted at the higher security policies; **see §8 P1, §10 item 4** *(documented: `website/.../protocols/opcua.adoc`, `plc4j/drivers/opcua/.../security/`)* | +| B2-OPCUA-PERMISSIVE | **OPC UA driver in default (no `trust-store-file`) mode** | server certificates **are not validated** by default *(documented: `website/.../protocols/opcua.adoc` — "Unless explicitly disabled through configuration of `trust-store-file` all server certificates will be accepted without validation"; `plc4j/drivers/opcua/.../security/PermissiveCertificateVerifier.java`)* | n/a | This is the OPC UA driver's *default* behavior. See **§5a "insecure-default case"**. | +| B2-ADS | **ADS driver → Beckhoff TwinCAT (AMS route setup with credentials)** | username/password to the TwinCAT system for AMS-route setup, when a `PlcUsernamePasswordAuthentication` is supplied *(documented: `plc4j/drivers/ads/.../AdsProtocolLogic.java` lines 130–145)* | TwinCAT-side | The credentials are forwarded to the device; what the device does with them is outside PLC4X. ADS payload data itself is unauthenticated cleartext *(inferred — §14 Q9)*. | +| B3 | **PLC4X transport → host OS / NIC / serial port / libpcap** | OS-level | OS-level | Whatever the embedding process's UID, capabilities, and seccomp / AppArmor profile permit. The `raw-socket` transport needs `CAP_NET_RAW` on Linux *(inferred — §14 Q10)*. | +| B4 | **Code-generation runtime → operating system** at build time | n/a — only the developer runs this | n/a | Build-time only; not part of the deployed artifact (out per §3 item 6) | ### Reachability preconditions per family @@ -330,6 +436,12 @@ follows. files describing the target devices are loaded from operator-configured paths *(documented: `plc4j/drivers/bacnet/.../configuration/BacNetIpConfiguration.java`)*. + **Out of scope**: EDE parsing exists only in the Java BACnet/IP + driver (`.../bacnetip/ede/EdeParser.java`), which is not green in the + §3 item 6 matrix; the Go BACnet/IP driver has no EDE support at all. + This whole input therefore falls outside the model — the KNX + `.knxproj` path in the row above is the only offline-file surface + that remains in it. - **What PLC4X does NOT do to its host** (negative claims, awaiting maintainer ratification — these are predominantly *(inferred)*, see §14 Q13): @@ -358,21 +470,21 @@ single global flag set. The security-relevant ones — those whose default value materially changes the security envelope — are collected here. -| Knob | Default | Maintainer stance | Effect | -|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| OPC UA `security-policy` | `NONE` *(documented: `plc4j/drivers/opcua/.../config/OpcuaConfiguration.java`)* | **maintainer ruling required** — is "no encryption" a supported production posture, or dev-default? *(inferred — §14 Q14)* | If `NONE`, the OPC UA channel runs unencrypted and unauthenticated; B2-OPCUA collapses to B2 (cleartext). | -| OPC UA `message-security` | `SIGN_ENCRYPT` *(documented)* | hardened default | When the security policy is not `NONE`, this forces sign-and-encrypt; flipping to `SIGN` (signed-cleartext) or `NONE` weakens the channel. | -| OPC UA `trust-store-file` | **unset** *(documented: `website/.../protocols/opcua.adoc` — "Unless explicitly disabled through configuration of `trust-store-file` all server certificates will be accepted without validation"; `plc4j/drivers/opcua/.../security/PermissiveCertificateVerifier.java`)* | **The OPC UA driver defaults to `PermissiveCertificateVerifier` — server certificates are accepted without validation**. This is **the single highest-priority maintainer ruling** in the document. *(inferred — §14 Q15)* | Without a trust store, an MITM attacker on the OT network can present any certificate and the driver will trust it. Even with the security policy at `Basic256Sha256` and `SIGN_ENCRYPT`, the encryption peer is unauthenticated. | -| OPC UA `key-store-file` / `key-store-password` | unset *(documented)* | operator must supply for mutual-TLS-like client auth | If unset and a security policy ≠ `NONE` is configured, the driver auto-generates a self-signed client certificate *(documented: `website/.../protocols/opcua.adoc`)*. Auto-generated certs are not recoverable across restarts; they cannot satisfy a peer that requires a known client identity. | -| OPC UA `discovery` | `true` *(documented)* | enabled by default; **the discovery phase is conducted with security policy `NONE`** *(documented: `OpcuaConfiguration.java`)* | An attacker on the path between the driver and the discovery endpoint sees / can rewrite the advertised endpoint, security policies, and server certificate before the driver picks one. | -| OPC UA `username` / `password` | unset *(documented)* | operator-supplied | Forwarded as the OPC UA `UserIdentityToken`; carried inside the secure channel when one exists, in cleartext otherwise. | -| OPC UA `channel-lifetime`, `session-timeout`, `negotiation-timeout`, `request-timeout` | 1 h / 2 min / 60 s / 30 s *(documented)* | reasonable defaults | DoS / timeout-tuning surface; not a security boundary. | -| ADS `PlcUsernamePasswordAuthentication` (when supplied) | unset (no AMS-route setup) | operator-supplied | When supplied, drives an AMS-route registration against the TwinCAT system using HTTP-style credentials *(documented: `plc4j/drivers/ads/.../AdsProtocolLogic.java`)*. The credentials are not protected by PLC4X on the wire; TwinCAT-side TLS is the device's responsibility. | -| BACnet/IP `ede-file-path` / `ede-directory-path` | unset | operator-supplied | If set, points at filesystem paths; standard file-permission rules apply. | -| KNX `.knxproj` parser | XXE / external-DTD / external-schema **disabled** *(documented: `EtsParser.java`)* | hardened — this is the safe defaults case | Reports of the shape "XXE in `.knxproj` parsing" are `KNOWN-NON-FINDING`. | -| S7 `controller-type` | unset (auto-discover via SZL) | operator-supplied for Siemens LOGO compatibility | Functional knob; not a security boundary. | -| Modbus connection options (`unit-id`, byte order) | per spec | functional knobs | not security boundaries. | -| `plc4c/`, `plc4py/`, `plc4net/` builds | **README declares these "not ready for usage" (with `plc4net` "abandoned")** *(documented: `README.md`)* | **OUT-OF-MODEL** for §8 properties; bugs reported against C / Python / .NET bindings should be triaged as code-quality / completeness, not as supported-product vulnerabilities until the maintainer reclassifies them | A finding in `plc4c/` is `OUT-OF-MODEL: unsupported-component` until the README line changes. | +| Knob | Default | Maintainer stance | Effect | +|----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| OPC UA `security-policy` | `NONE` *(documented: `plc4j/drivers/opcua/.../config/OpcuaConfiguration.java`)* | **maintainer ruling required** — is "no encryption" a supported production posture, or dev-default? *(inferred — §14 Q14)* | If `NONE`, the OPC UA channel runs unencrypted and unauthenticated; B2-OPCUA collapses to B2 (cleartext). | +| OPC UA `message-security` | `SIGN_ENCRYPT` *(documented)* | hardened default | When the security policy is not `NONE`, this forces sign-and-encrypt; flipping to `SIGN` (signed-cleartext) or `NONE` weakens the channel. | +| OPC UA `trust-store-file` | **unset** *(documented: `website/.../protocols/opcua.adoc` — "Unless explicitly disabled through configuration of `trust-store-file` all server certificates will be accepted without validation"; `plc4j/drivers/opcua/.../security/PermissiveCertificateVerifier.java`)* | **The OPC UA driver defaults to `PermissiveCertificateVerifier` — server certificates are accepted without validation**. This is **the single highest-priority maintainer ruling** in the document. *(inferred — §14 Q15)* | Without a trust store, an MITM attacker on the OT network can present any certificate and the driver will trust it. Even with the security policy at `Basic256Sha256` and `SIGN_ENCRYPT`, the encryption peer is unauthenticated. | +| OPC UA `key-store-file` / `key-store-password` | unset *(documented)* | operator must supply for mutual-TLS-like client auth | If unset and a security policy ≠ `NONE` is configured, the driver auto-generates a self-signed client certificate *(documented: `website/.../protocols/opcua.adoc`)*. Auto-generated certs are not recoverable across restarts; they cannot satisfy a peer that requires a known client identity. | +| OPC UA `discovery` | `true` *(documented)* | enabled by default; **the discovery phase is conducted with security policy `NONE`** *(documented: `OpcuaConfiguration.java`)* | An attacker on the path between the driver and the discovery endpoint sees / can rewrite the advertised endpoint, security policies, and server certificate before the driver picks one. | +| OPC UA `username` / `password` | unset *(documented)* | operator-supplied | Forwarded as the OPC UA `UserIdentityToken`; carried inside the secure channel when one exists, in cleartext otherwise. | +| OPC UA `channel-lifetime`, `session-timeout`, `negotiation-timeout`, `request-timeout` | 1 h / 2 min / 60 s / 30 s *(documented)* | reasonable defaults | DoS / timeout-tuning surface; not a security boundary. | +| ADS `PlcUsernamePasswordAuthentication` (when supplied) | unset (no AMS-route setup) | operator-supplied | When supplied, drives an AMS-route registration against the TwinCAT system using HTTP-style credentials *(documented: `plc4j/drivers/ads/.../AdsProtocolLogic.java`)*. The credentials are not protected by PLC4X on the wire; TwinCAT-side TLS is the device's responsibility. | +| BACnet/IP `ede-file-path` / `ede-directory-path` *(out of scope — Java-only driver, not green; see §3 item 6)* | unset | operator-supplied | If set, points at filesystem paths; standard file-permission rules apply. | +| KNX `.knxproj` parser | XXE / external-DTD / external-schema **disabled** *(documented: `EtsParser.java`)* | hardened — this is the safe defaults case | Reports of the shape "XXE in `.knxproj` parsing" are `KNOWN-NON-FINDING`. | +| S7 `controller-type` | unset (auto-discover via SZL) | operator-supplied for Siemens LOGO compatibility | Functional knob; not a security boundary. | +| Modbus connection options (`unit-id`, byte order) | per spec | functional knobs | not security boundaries. | +| `plc4c/`, `plc4py/`, `plc4net/` builds | **README declares these "not ready for usage" (with `plc4net` "abandoned")** *(documented: `README.md`)* | **OUT-OF-MODEL** for §8 properties; bugs reported against C / Python / .NET bindings should be triaged as code-quality / completeness, not as supported-product vulnerabilities until the maintainer reclassifies them | A finding in `plc4c/` is `OUT-OF-MODEL: unsupported-component` until the README line changes. | ### The insecure-default case (OPC UA) @@ -411,16 +523,16 @@ hypothesis (b)** and will need adjustment if the maintainer chooses ### Per-network-input trust table (wire bytes) -| Surface | Parameter | Attacker-controllable? | Driver must enforce | -|--------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Cleartext OT-protocol response (Modbus, S7, BACnet/IP, IEC-60870-104, Profinet, CANopen, KNXnet/IP, C-Bus, DF1, AB-Ethernet, ADS payload, EtherNet/IP, UMAS) | every byte of every response frame | **yes** — anyone on the OT network with reachability can spoof | **memory safety, bounded allocation, no infinite loop, no unbounded recursion** on malformed input — but **not** authenticity, integrity, or any payload-semantic guarantee *(inferred — §14 Q11)* | -| OPC UA wire frames inside the secure channel | every byte | **yes** — but signed/encrypted by the negotiated policy when policy ≠ `NONE` | as above plus: correct verification of signature and MAC under the negotiated policy; correct decryption; correct chunk reassembly *(documented + inferred — §14 Q17)* | -| OPC UA wire frames during discovery (`security-policy=NONE` phase) | every byte | **yes** | memory safety on malformed responses; **the discovery handshake itself is not authenticated** *(documented)* | -| OPC UA server certificate (presented during handshake) | full DER bytes | **yes** | when `trust-store-file` is set: X.509 chain validation per JCE rules; when not set (default): **none** *(documented: §5a)* | -| Serial-line frames | every byte | yes if the serial channel is attacker-reachable | memory safety on malformed framing *(inferred — §14 Q11)* | -| libpcap capture / replay frames | every byte | yes if the capture file is attacker-controlled | the pcap-replay transport is a **dev/test tool**; if it's running in production, the integrator has put it there | -| ETS `.knxproj` XML | as XML | yes if the file is attacker-supplied | XXE disabled *(documented)*; ZIP slip protection — *(inferred — §14 Q18)* | -| BACnet EDE file | as text | yes if the file is attacker-supplied | parser robustness *(inferred — §14 Q18)* | +| Surface | Parameter | Attacker-controllable? | Driver must enforce | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Cleartext OT-protocol response, for any driver green in the §3 item 6 matrix (Modbus, S7, IEC-60870-104, CANopen, KNXnet/IP, C-Bus *(Go)*, AB-Ethernet, BACnet/IP *(Go)*, CAN raw, Firmata, ADS payload, EtherNet/IP incl. Logix, SLMP, UMAS) | every byte of every response frame | **yes** — anyone on the OT network with reachability can spoof | **memory safety, bounded allocation, no infinite loop, no unbounded recursion** on malformed input — but **not** authenticity, integrity, or any payload-semantic guarantee *(inferred — §14 Q11)* | +| OPC UA wire frames inside the secure channel | every byte | **yes** — but signed/encrypted by the negotiated policy when policy ≠ `NONE` | as above plus: correct verification of signature and MAC under the negotiated policy; correct decryption; correct chunk reassembly *(documented + inferred — §14 Q17)* | +| OPC UA wire frames during discovery (`security-policy=NONE` phase) | every byte | **yes** | memory safety on malformed responses; **the discovery handshake itself is not authenticated** *(documented)* | +| OPC UA server certificate (presented during handshake) | full DER bytes | **yes** | when `trust-store-file` is set: X.509 chain validation per JCE rules; when not set (default): **none** *(documented: §5a)* | +| Serial-line frames | every byte | yes if the serial channel is attacker-reachable | memory safety on malformed framing *(inferred — §14 Q11)* | +| libpcap capture / replay frames | every byte | yes if the capture file is attacker-controlled | the pcap-replay transport is a **dev/test tool**; if it's running in production, the integrator has put it there | +| ETS `.knxproj` XML | as XML | yes if the file is attacker-supplied | XXE disabled *(documented)*; ZIP slip protection — *(inferred — §14 Q18)* | +| BACnet EDE file — **out of scope**: Java-only driver, not green (§3 item 6) | as text | n/a — the driver carrying this parser is not in the model | **none** — previously "parser robustness"; struck when Java BACnet/IP was declared not ready *(maintainer: cdutz, 2026-08-21)* | ### Size / shape / rate @@ -444,7 +556,7 @@ hypothesis (b)** and will need adjustment if the maintainer chooses | **Network peer with reachability to the PLC** (the OT network) | **yes — primary adversary for B2** | can read, modify, drop, replay, or inject frames on the cleartext protocols. For the OPC UA protocol with a configured trust store, the peer is bounded by the cryptographic primitives. For OPC UA with the default `PermissiveCertificateVerifier`, the peer can MITM the channel. | | **Network peer between the driver and an OPC UA discovery endpoint** | **yes** | the discovery handshake is unencrypted by spec *(documented)*; the peer can swap the advertised endpoint URL, certificate, or set of supported policies. | | **Author of a malformed-but-parseable response from a real PLC firmware** (buggy or compromised device firmware sending bytes a well-behaved firmware would not) | **yes — wire-parser robustness must hold** | causes the driver to parse adversarial bytes; in-model for memory safety, hang, unbounded allocation. | -| **Author of a malformed `.knxproj` or BACnet EDE file** | **yes for parser robustness** | causes the offline-file parser to crash, hang, or escape from its expected sandbox (XXE blocked per §5a). | +| **Author of a malformed `.knxproj` file** (the BACnet EDE parser is out of scope — Java-only driver, not green per §3 item 6) | **yes for parser robustness** | causes the offline-file parser to crash, hang, or escape from its expected sandbox (XXE blocked per §5a). | | **Embedding application itself** | **out of scope** — see §3 item 3 | trusted by construction. | | **End user of the embedding application** | **out of scope** — see §3 item 4 | PLC4X has no concept of an end user. | | **Operator of the PLC device** | **out of scope** | the physical device the driver talks to is the device; PLC4X has no opinion on its operator. | @@ -597,7 +709,7 @@ important one for an integrator.** sent at *t* can be replayed by an attacker at *t+δ*; PLC4X does not add nonces / sequence-checks of its own. Where a protocol specifies nonces, PLC4X provides those protocol-defined nonces but adds none - beyond what the protocol mandates *(maintainer — chrisdutz)*. + beyond what the protocol mandates *(maintainer — cdutz)*. - **No authentication of the embedding application's end users.** PLC4X has no concept of end users. See §3 item 4. - **No authorization over which tags an embedding application reads or @@ -605,11 +717,12 @@ important one for an integrator.** Where the target protocol has its own permission system, PLC4X **proxies** it — deferring the check to the target PLC and relaying any permission error back to the client — but provides no permission - system of its own *(maintainer — chrisdutz)*. -- **No defense against malformed `.knxproj` ZIP slip, BACnet EDE - malformed-file robustness, or path-traversal in operator-supplied - file paths.** The file paths are caller-supplied; XML XXE is the only - attack class explicitly hardened *(inferred — §14 Q18)*. + system of its own *(maintainer — cdutz)*. +- **No defense against malformed `.knxproj` ZIP slip or path-traversal + in operator-supplied file paths.** The file paths are caller-supplied; + XML XXE is the only attack class explicitly hardened *(inferred — + §14 Q18)*. BACnet EDE files are no longer named here: that parser is + Java-BACnet-only and out of scope per §3 item 6. - **No DoS protection at the API surface.** The embedding application is trusted to call the API at a reasonable rate *(inferred — §14 Q19)*. @@ -617,7 +730,7 @@ important one for an integrator.** disk on its own at runtime. (The SPI3 rewrite adds an optional Audit-Log feature that writes debug data to the filesystem — intended for transient debugging, not permanent operation, with no encryption; - enabling it in production is an operator choice *(maintainer — chrisdutz)*.) + enabling it in production is an operator choice *(maintainer — cdutz)*.) - **No constant-time comparison of authentication secrets.** Whatever the protocol provides (or doesn't) is what PLC4X passes through. - **No defense against side-channel observation of OPC UA crypto.** Per @@ -824,7 +937,10 @@ every wire-format property of every driver.** - **"Profinet IO frames are not authenticated."** Profinet's spec splits "Profinet" into multiple application protocols; the variant here does not include the optional Profinet-Security extensions. → - §3 item 1, §9. `BY-DESIGN`. + §3 item 1, §9. `BY-DESIGN`. Both Java Profinet drivers — `profinet` + and `profinet-ng` — are also not green in the matrix and so out of + scope in their own right (§3 item 6), making a report here doubly + non-actionable. - **"CANopen / SocketCAN frames are unauthenticated."** CAN is a fieldbus; the integrity property is "this frame originated on this physical bus", not cryptographic. → §3 item 1, §9. `BY-DESIGN`. @@ -834,7 +950,9 @@ every wire-format property of every driver.** §3 item 1, §9. `BY-DESIGN`. - **"C-Bus, DF1, AB-Ethernet, EtherNet/IP (classic CIP), UMAS, ADS/AMS payload traffic, Open-Protocol — all unauthenticated / - cleartext."** Per spec. → §3 item 1, §9. `BY-DESIGN`. + cleartext."** Per spec. → §3 item 1, §9. `BY-DESIGN`. The Java C-Bus + and Open-Protocol drivers are additionally not green in the matrix and + so out of scope per §3 item 6; the Go C-Bus driver is the in-model one. - **"Firmata is a serial protocol with no authentication."** Firmata is a hobbyist Arduino protocol; the security model is "the serial cable is your perimeter". → §3 item 1, §9. `BY-DESIGN`. @@ -843,7 +961,7 @@ every wire-format property of every driver.** - **"OPC UA driver accepts any server certificate."** True by default (`PermissiveCertificateVerifier`); the §10 item 3 contract requires - the operator to set `trust-store-file`. **Maintainer ruling (chrisdutz, + the operator to set `trust-store-file`. **Maintainer ruling (cdutz, §14 Q15):** this default "should be changed and reported" — it is **not** the supported posture, so a report is **`VALID`** (a gap the PMC intends to fix toward secure-by-default), not @@ -875,6 +993,29 @@ every wire-format property of every driver.** - **"Cryptographic finding in `plc4c/` / `plc4py/` / `plc4net/`."** README flags these as not-ready. → §3 item 6. `OUT-OF-MODEL: unsupported-component` pending the README line being removed. +- **"Unimplemented / stubbed / dead code in a driver — + `discoverWithHandler` returns `null`, `onRegisterConsumer` throws, a + subscription future is never completed, a connection implements no + request builder."** Check the driver matrix first. For a driver that + is not green, the stubs *are* the current state, not a bug in a + shipped feature. → §3 item 6. `OUT-OF-MODEL: unsupported-component`. +- **"Any finding — NPE, hang, resource leak, parser crash, weak + default — in a driver that is not green in the matrix."** Out of + scope by the §3 item 6 rule, whatever its technical merit. At the time + of writing that covers ctrlX, Open-Protocol, Java C-Bus, Java + BACnet/IP and both Java Profinet drivers, but **consult the + matrix rather than this sentence** — it is the authority and this list + is only illustrative. → §3 item 6. + `OUT-OF-MODEL: unsupported-component`. +- **"Vulnerability in the Java Scraper (`org.apache.plc4x.java.scraper.*`) + — unbounded thread pool, injection through a scrape job, …"** The + Scraper has been deleted from the tree and replaced by the Event Pump + (`plc4j/tools/event-pump`). There is no code left to fix. Point the + reporter at the Event Pump and close. → §3 item 11. + `OUT-OF-MODEL: removed-component`. The same applies to any other + module a scanner knows from an older release but which is absent on + `develop` — check the tree before triaging, because `README.md` and + parts of this document still name components that are gone (§14 Q33). - **"`code-generation/` mspec compiler has X."** Build-time only, not in the runtime artifact. → §3 item 6. `OUT-OF-MODEL: unsupported-component`. @@ -915,8 +1056,22 @@ Revise this document when any of the following lands: `security-policy`, `trust-store-file`, or `message-security`. - `plc4c/`, `plc4py/`, or `plc4net/` has its "not ready for usage" README flag removed — those move from §3 item 6 to in-model. +- **A cell in the driver matrix + (`website/.../protocols/index.adoc`, "Current language support for + protocols") changes to or from a green check.** This is the scope + trigger for drivers, and it takes effect *without* an edit to this + document — §3 item 6 delegates to the matrix precisely so the list + lives in one place. What does need review when a driver turns green: + its artifact should be added to `plc4j/drivers/all/pom.xml` (removed + when it turns non-green), its protocol should join the §4 B2 and §8 + untrusted-input example lists, and any §11a entry naming it should be + struck. - The boundary with `plc4x-extras` shifts (e.g. an integration is brought back into the main repo, or vice versa). +- **A component is removed from or added to `develop`.** A removal puts + it under §3 item 11 and means any §11a entry, §4 boundary or §6 knob + that named it should be struck; an addition needs placing in the §2 + component-family table before a report against it can be triaged. - A vulnerability report that cannot be cleanly routed to one of the §13 dispositions: that is evidence the model has a gap. @@ -930,8 +1085,9 @@ A report against PLC4X receives exactly one of the following. | `VALID-HARDENING` | No §8 property violated, but a §11 misuse pattern can be made harder to fall into by code change (e.g. issue a runtime warning when OPC UA runs `security-policy=NONE`). Fixed at maintainer discretion, typically no CVE. | §11 | | `OUT-OF-MODEL: trusted-input` | Requires attacker control of a §6 parameter the model marks trusted — the connection URL, the auth object, the value being written, tag-address strings (per §3 item 5 when the integrator has not opted them into untrusted-string handling). | §6 | | `OUT-OF-MODEL: adversary-not-in-scope` | Requires a §7 actor the model excludes — embedding application is hostile, side-channel observer, quantum adversary, OT-network perimeter is the integrator's problem. | §7 | -| `OUT-OF-MODEL: unsupported-component` | Lands in `plc4c/`, `plc4py/`, `plc4net/`, `tools/`, `code-generation/`, `plc4j/utils/`, `plc4j/drivers/{simulated,mock}`, `plc4x-extras` content, vendored upstream code, or repo infrastructure. | §3 item 2, §3 item 6 | -| `OUT-OF-MODEL: non-default-build` | Only manifests under a §5a knob the maintainer has ruled is dev/test. **Note:** per the maintainer (chrisdutz, §14 Q14/Q15/Q16) the OPC UA insecure defaults are moving to secure-by-default — the permissive certificate verifier in particular is a gap to fix (`VALID`), not a non-default-build exclusion. | §5a | +| `OUT-OF-MODEL: removed-component` | Names a module that no longer exists on `develop` — removed or replaced, with no successor to fix. The Java Scraper (replaced by the Event Pump) is the worked example. Not a statement about whether the report was correct. | §3 item 11 | +| `OUT-OF-MODEL: unsupported-component` | Lands in `plc4c/`, `plc4py/`, `plc4net/`, `tools/`, `code-generation/`, `plc4j/utils/`, `plc4j/drivers/{simulated,mock}`, any driver not marked with a green check in the §3 item 6 driver matrix, `plc4x-extras` content, vendored upstream code, or repo infrastructure. | §3 item 2, §3 item 6 | +| `OUT-OF-MODEL: non-default-build` | Only manifests under a §5a knob the maintainer has ruled is dev/test. **Note:** per the maintainer (cdutz, §14 Q14/Q15/Q16) the OPC UA insecure defaults are moving to secure-by-default — the permissive certificate verifier in particular is a gap to fix (`VALID`), not a non-default-build exclusion. | §5a | | `BY-DESIGN: protocol-disclaimed` | Concerns a property the **protocol** (not the library) does not provide — every "Modbus / S7 / BACnet / etc. is unauthenticated" report. | §9, §3 item 1, §11a | | `BY-DESIGN: property-disclaimed` | Concerns a §9 property the library explicitly does not provide (built-in TLS tunneling, end-user authn, DoS protection at the API). | §9 | | `KNOWN-NON-FINDING` | Matches a §11a recurring false positive. | §11a | @@ -942,7 +1098,7 @@ A report against PLC4X receives exactly one of the following. Every *(inferred)* tag in the body maps to one of these. Proposed answers are inline; please confirm, correct, or strike. -**PMC review (chrisdutz, 2026-06-04, PR-approved):** the OPC UA insecure +**PMC review (cdutz, 2026-06-04, PR-approved):** the OPC UA insecure defaults (Q14/Q15/Q16) are moving to secure-by-default — the permissive certificate verifier in particular is a gap to fix, not a supported posture — and the SPI3 rewrite adds TLS transport, hardens the parsers, @@ -1002,7 +1158,7 @@ the documentation needs to make clear that "OPC UA" with this driver does not by default carry OPC UA's spec-level cryptographic properties. -**Answered (maintainer — chrisdutz):** secure-by-default is the intended +**Answered (maintainer — cdutz):** secure-by-default is the intended posture — the SPI3 rewrite makes the insecure path explicitly opt-in and the secure path the new default; the current `NONE` default is dev/lab convenience, **not** a supported production posture. *(maps to §5a, §10, §11a, §13)* @@ -1011,7 +1167,7 @@ convenience, **not** a supported production posture. *(maps to §5a, §10, §11a single highest-priority question. When `trust-store-file` is unset, the driver accepts every server certificate. Is "OPC UA driver accepts attacker-presented certificate" `VALID` (stance (a)) or `OUT-OF-MODEL: -non-default-build` (stance (b))? **Answered (maintainer — chrisdutz):** the permissive default "should be +non-default-build` (stance (b))? **Answered (maintainer — cdutz):** the permissive default "should be changed and reported" — it is **not** the supported posture. A report that the OPC UA driver accepts an attacker-presented certificate is therefore **`VALID`** (a security gap the PMC intends to fix toward @@ -1022,7 +1178,7 @@ spec mandates discovery in cleartext; the driver follows the spec. Confirm that "OPC UA discovery handshake unauthenticated" is `BY-DESIGN: protocol-disclaimed` per §9 false-friend item 2 — not a PLC4X bug. -**Answered (maintainer — chrisdutz):** confirmed — cleartext discovery is +**Answered (maintainer — cdutz):** confirmed — cleartext discovery is per the OPC UA spec (`BY-DESIGN: protocol-disclaimed`); separately, the broader OPC UA channel is moving to secure-by-default in SPI3 (see Q14/Q15). *(maps to §3 item 1, §9, §10 item 5)* @@ -1037,7 +1193,7 @@ no upper bound is a bug, not "just OT-protocol weirdness". Are there specific drivers where this property has been deliberately weakened (e.g. generated code that is faster but not bounded)? -**Answered (maintainer — chrisdutz):** P4 is committed; in the SPI3 +**Answered (maintainer — cdutz):** P4 is committed; in the SPI3 rewrite each connection allocates a fixed-length ring-buffer (length varying per protocol), preventing the "huge fake message → huge allocation" class of issue. *(maps to §4 reachability, §8 P4, §11a)* @@ -1050,16 +1206,18 @@ only manually at version-bump time? Are there OPC UA test vectors *(maps to §8 P1, P2, P7)* **Q18.** `.knxproj` (ZIP) handling: confirm ZIP-slip protection in the -unzip step. BACnet EDE files: confirm parser robustness against -malformed files. +unzip step. ~~BACnet EDE files: confirm parser robustness against +malformed files.~~ — the EDE half is moot: EDE parsing exists only in +the Java BACnet/IP driver, which is not green in the §3 item 6 matrix, +so it is out of scope *(maintainer: cdutz, 2026-08-21)*. -**Answered (maintainer — chrisdutz):** the SPI3 rewrite addresses the +**Answered (maintainer — cdutz):** the SPI3 rewrite addresses the ETS-parser issues and hardens XML parsing. *(maps to §6, §9, §11a)* **Q19.** No API-level throttle confirmed? Proposed: yes — the embedding application is responsible. -**Answered (maintainer — chrisdutz):** confirmed — no API-level throttle; +**Answered (maintainer — cdutz):** confirmed — no API-level throttle; the embedding application is responsible. (SPI3's per-connection ring-buffer additionally bounds per-connection allocation.) *(maps to §6, §9, §10 item 8)* @@ -1085,7 +1243,7 @@ responsibility — confirm §9 / §10 item 2. Is there appetite for a Modbus-over-TLS per Schneider Electric's draft spec)? If yes, that changes §12. -**Answered (maintainer — chrisdutz):** yes — the SPI3 rewrite adds `tls` +**Answered (maintainer — cdutz):** yes — the SPI3 rewrite adds `tls` and `tls-psk` transports that can secure a `tcp` connection where the target PLC/gateway supports it. This is a §12-changing addition. *(maps to §9, §10 item 2, §12)* @@ -1163,6 +1321,50 @@ recurring false-positives strengthen the suppression list. *(meta — **Q31.** What kind of change to PLC4X should trigger a revision (proposed list in §12 — confirm or correct)? *(meta, §12)* +**Q32.** Driver scope. **Answered (cdutz, 2026-08-21):** rather +than carry a list of unfinished drivers in this document, scope is +delegated to the per-language driver matrix in +`website/.../protocols/index.adoc` — a driver is in scope for a +language iff that language's cell is a green check, and a report +against any driver that is not green is declined. Two consequences the +maintainer accepted explicitly: both Java Profinet drivers — the older +`plc4j/drivers/profinet` and the newer `plc4j/drivers/profinet-ng`, +both kept in the tree — are listed as unsupported and therefore out of +scope; and partial coverage on its own does not make a driver +unsupported, so the Java AB-Ethernet, CAN raw and Firmata drivers are +green and in scope while the Java C-Bus driver, which advertises +`canRead()` and implements nothing, is not. The Go column was audited +on the same test and every Go driver was found to declare its +capabilities honestly through `PlcConnectionMetadata`; Go AB-Ethernet, +BACnet/IP and Firmata are green with the rest. + +The **Java BACnet/IP** driver is the case that shows why the matrix, +not a code reading, is the authority. Its code passes the mechanical +test — it advertises only `canSubscribe` and implements exactly that, +the same shape as the green IEC-60870-5-104 driver — but the +maintainer's assessment is that it is an experiment that does not hold +up at scale *(maintainer: cdutz, 2026-08-21)*. It stays non-green +and therefore out of scope. Readiness is a maintainer judgement that +the matrix records; a driver can be internally consistent and still not +be something to build on. The Go BACnet/IP driver is unaffected and +stays in scope. +*(maps to §1, §2, §3 item 6, §4 B2, §8, §11a, §12, §13)* + +**Q33.** `plc4j/tools/` is unclassified, and two of its modules are +mis-filed. The directory holds `capture-replay`, `connection-cache`, +`event-pump` and `opm`. None of them appear in the §2 component-family +table, so a report against any of them has no row to land in. Two +specific problems: (a) §1 and §3 item 2 assert that Connection-Cache +and OPM live in `plc4x-extras`, but they are here, at +`plc4j/tools/connection-cache` and `plc4j/tools/opm`; (b) the +**Event Pump** (`plc4j/tools/event-pump`) is the Scraper's replacement +and connects to PLCs to collect data on a schedule — it is not obviously +"dev tooling" the way `plc4j/utils/` is, so it may be a genuine +in-model surface rather than a carve-out. Please classify all four: in +model, out as tooling, or out as extras. `README.md` also still +advertises the Scraper at line 81 and needs the same cleanup. +*(maps to §1, §2, §3 item 2, §3 item 11)* + --- ## Appendix: Existing security-policy artefact → §x back-map @@ -1182,28 +1384,31 @@ security-policy artefacts are: best **documented** sources for protocol-level security caveats. The back-map below covers the load-bearing ones. -| Source | Claim | Lands in | -|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------|-----------------------------------------------------------| -| `README.md` ("`plc4c/`, `plc4py/` not ready for usage; `plc4net/` abandoned") | scope carve-out | §3 item 6, §5a | -| `README.md` ("The Industrial IoT adapter … client-side library across multiple PLC protocols") | scope framing | §2 | -| `website/.../protocols/opcua.adoc` ("Unless explicitly disabled through configuration of `trust-store-file` all server certificates will be accepted without validation") | default-permissive verifier | §4 B2-OPCUA-PERMISSIVE, §5a, §9 false-friend item 1, §11a | -| `website/.../protocols/opcua.adoc` ("discovery phase is always conducted using `NONE` security policy" — paraphrased from `OpcuaConfiguration.java` discovery doc) | discovery unencrypted | §4 B2-OPCUA-PERMISSIVE, §9 false-friend item 2 | -| `website/.../protocols/opcua.adoc` ("`message-security` … `SIGN_ENCRYPT` … high security settings and full encryption") | secure-channel message security | §5a, §8 P1 | -| `website/.../protocols/opcua.adoc` ("There is transport level certificate which can be provided though keystore options, but there is also a X509 Certificate which can be used for authentication (currently unsupported by PLC4X)") | client-X509-auth not implemented | §11a, §14 Q14 | -| `website/.../protocols/opcua.adoc` (compatibility list: Eclipse Milo, OPC Foundation .NET, etc.) | tested reference servers | §8 P7 | -| `website/.../protocols/s7.adoc` ("PUT/GET functions") | S7 mode | §3 item 1, §11a, §14 Q23 | -| `website/.../protocols/s7.adoc` ("Siemens `LOGO` device … requires `?controller-type=LOGO`") | functional knob | §5a (non-security row) | -| `website/.../protocols/modbus.adoc` (no security section) | Modbus is unauthenticated | §3 item 1, §11a (disclaimer-by-omission) | -| `website/.../protocols/canopen.adoc` ("CANopen … address areas") | CANopen scope | §3 item 1, §11a | -| `website/.../protocols/ads.adoc` ("device-independent and fieldbus independent interface for communication between Beckhoff automation devices") | ADS scope | §3 item 1, §11a | -| `website/.../protocols/index.adoc` | per-language driver-coverage matrix | §2 component table | -| `website/asciidoc/.../security.adoc` ("For more information about reporting vulnerabilities, see the Apache Security Team page") | reporting channel | §1 reporting cross-reference | -| `website/asciidoc/.../developers/maturity.adoc` (QU20, QU30) | maturity posture | §1 reporting cross-reference | -| `plc4j/drivers/opcua/.../security/PermissiveCertificateVerifier.java` (no-op `checkCertificateTrusted`) | implements the default | §4 B2-OPCUA-PERMISSIVE, §5a | -| `plc4j/drivers/opcua/.../security/SecurityPolicy.java` (enum of `NONE`, `Basic128Rsa15`, `Basic256`, `Basic256Sha256`, `Aes128_Sha256_RsaOaep`, `Aes256_Sha256_RsaPss`) | supported policies | §5a, §8 P1, P7, §11a, §14 Q25 | -| `plc4j/drivers/opcua/.../config/OpcuaConfiguration.java` (`@ConfigurationParameter` set incl. `trust-store-file`, `discovery`, `username`, `password`, `key-store-file`, `key-store-password`, `security-policy=NONE`, `message-security=SIGN_ENCRYPT`, `channel-lifetime=3600000`, `session-timeout=120000`, `negotiation-timeout=60000`, `request-timeout=30000`) | knob inventory | §5a, §6, §10 | -| `plc4j/drivers/knxnetip/.../ets/EtsParser.java` lines 65–71 (XXE/DTD/schema disabled, `FEATURE_SECURE_PROCESSING`) | hardened XML parser | §5 (filesystem-KNX), §8 P6, §11a code-base | -| `plc4j/drivers/ads/.../AdsProtocolLogic.java` lines 130–145 (`PlcUsernamePasswordAuthentication` instance check; `setupAmsRoute(...)` call) | ADS credential forwarding | §4 B2-ADS, §5a | -| `plc4j/api/.../authentication/PlcAuthentication.java`, `PlcUsernamePasswordAuthentication.java`, `PlcCertificateAuthentication.java` | auth type-system | §6, §9 false-friend | -| `plc4j/api/.../PlcConnection.java`, `PlcDriverManager.java` | public API surface | §2, §4 B1, §6 | -| `plc4j/transports/{tcp,udp,serial,raw-socket,pcap-replay,socketcan,can,virtualcan,test}` (no listening-socket entries) | client-only transports | §2, §3 item 2, §10 item 9 | +| Source | Claim | Lands in | +|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|-----------------------------------------------------------| +| `README.md` ("`plc4c/`, `plc4py/` not ready for usage; `plc4net/` abandoned") | scope carve-out | §3 item 6, §5a | +| `README.md` ("The Industrial IoT adapter … client-side library across multiple PLC protocols") | scope framing | §2 | +| `website/.../protocols/opcua.adoc` ("Unless explicitly disabled through configuration of `trust-store-file` all server certificates will be accepted without validation") | default-permissive verifier | §4 B2-OPCUA-PERMISSIVE, §5a, §9 false-friend item 1, §11a | +| `website/.../protocols/opcua.adoc` ("discovery phase is always conducted using `NONE` security policy" — paraphrased from `OpcuaConfiguration.java` discovery doc) | discovery unencrypted | §4 B2-OPCUA-PERMISSIVE, §9 false-friend item 2 | +| `website/.../protocols/opcua.adoc` ("`message-security` … `SIGN_ENCRYPT` … high security settings and full encryption") | secure-channel message security | §5a, §8 P1 | +| `website/.../protocols/opcua.adoc` ("There is transport level certificate which can be provided though keystore options, but there is also a X509 Certificate which can be used for authentication (currently unsupported by PLC4X)") | client-X509-auth not implemented | §11a, §14 Q14 | +| `website/.../protocols/opcua.adoc` (compatibility list: Eclipse Milo, OPC Foundation .NET, etc.) | tested reference servers | §8 P7 | +| `website/.../protocols/s7.adoc` ("PUT/GET functions") | S7 mode | §3 item 1, §11a, §14 Q23 | +| `website/.../protocols/s7.adoc` ("Siemens `LOGO` device … requires `?controller-type=LOGO`") | functional knob | §5a (non-security row) | +| `website/.../protocols/modbus.adoc` (no security section) | Modbus is unauthenticated | §3 item 1, §11a (disclaimer-by-omission) | +| `website/.../protocols/canopen.adoc` ("CANopen … address areas") | CANopen scope | §3 item 1, §11a | +| `website/.../protocols/ads.adoc` ("device-independent and fieldbus independent interface for communication between Beckhoff automation devices") | ADS scope | §3 item 1, §11a | +| `website/.../protocols/index.adoc` | per-language driver-coverage matrix | §2 component table | +| `website/.../protocols/index.adoc` ("Current language support for protocols" table + its "Scope of support" note: green check = supported and in the threat model; anything else = unsupported, security reports declined) | **normative** driver-scope rule | §1, §2, §3 item 6, §4 B2, §8, §11a, §12, §13, §14 Q32 | +| `plc4j/drivers/all/pom.xml` (bundles exactly the Java-green drivers: ab-eth, ads, can, canopen, eip, firmata, iec-60870, knxnetip, modbus, opcua, plc4x, s7, simulated, slmp, umas) | the matrix rule, enforced by the build | §3 item 6 | +| `website/asciidoc/.../security.adoc` ("For more information about reporting vulnerabilities, see the Apache Security Team page") | reporting channel | §1 reporting cross-reference | +| `website/asciidoc/.../developers/maturity.adoc` (QU20, QU30) | maturity posture | §1 reporting cross-reference | +| `plc4j/drivers/opcua/.../security/PermissiveCertificateVerifier.java` (no-op `checkCertificateTrusted`) | implements the default | §4 B2-OPCUA-PERMISSIVE, §5a | +| `plc4j/drivers/opcua/.../security/SecurityPolicy.java` (enum of `NONE`, `Basic128Rsa15`, `Basic256`, `Basic256Sha256`, `Aes128_Sha256_RsaOaep`, `Aes256_Sha256_RsaPss`) | supported policies | §5a, §8 P1, P7, §11a, §14 Q25 | +| `plc4j/drivers/opcua/.../config/OpcuaConfiguration.java` (`@ConfigurationParameter` set incl. `trust-store-file`, `discovery`, `username`, `password`, `key-store-file`, `key-store-password`, `security-policy=NONE`, `message-security=SIGN_ENCRYPT`, `channel-lifetime=3600000`, `session-timeout=120000`, `negotiation-timeout=60000`, `request-timeout=30000`) | knob inventory | §5a, §6, §10 | +| `plc4j/drivers/knxnetip/.../ets/EtsParser.java` lines 65–71 (XXE/DTD/schema disabled, `FEATURE_SECURE_PROCESSING`) | hardened XML parser | §5 (filesystem-KNX), §8 P6, §11a code-base | +| `plc4j/drivers/ads/.../AdsProtocolLogic.java` lines 130–145 (`PlcUsernamePasswordAuthentication` instance check; `setupAmsRoute(...)` call) | ADS credential forwarding | §4 B2-ADS, §5a | +| `plc4j/api/.../authentication/PlcAuthentication.java`, `PlcUsernamePasswordAuthentication.java`, `PlcCertificateAuthentication.java` | auth type-system | §6, §9 false-friend | +| `plc4j/api/.../PlcConnection.java`, `PlcDriverManager.java` | public API surface | §2, §4 B1, §6 | +| Commit `83023b0` ("Implemented a new component for fetching data from remote PLCs (replacement for the scraper)") + absence of any `*scraper*` path on `develop` | Scraper removed, Event Pump is its successor | §1, §3 item 2, §3 item 11, §11a, §13 | +| `plc4j/transports/{tcp,udp,serial,raw-socket,pcap-replay,socketcan,can,virtualcan,test}` (no listening-socket entries) | client-only transports | §2, §3 item 2, §10 item 9 | diff --git a/plc4j/drivers/all/pom.xml b/plc4j/drivers/all/pom.xml index 15f74a4e73..08ada01939 100644 --- a/plc4j/drivers/all/pom.xml +++ b/plc4j/drivers/all/pom.xml @@ -75,7 +75,17 @@ - + org.apache.plc4x plc4j-driver-ab-eth @@ -88,18 +98,6 @@ 1.0.0-SNAPSHOT runtime - - org.apache.plc4x - plc4j-driver-bacnet - 1.0.0-SNAPSHOT - runtime - - - org.apache.plc4x - plc4j-driver-c-bus - 1.0.0-SNAPSHOT - runtime - org.apache.plc4x plc4j-driver-can @@ -136,12 +134,6 @@ 1.0.0-SNAPSHOT runtime - - org.apache.plc4x - plc4j-driver-open-protocol - 1.0.0-SNAPSHOT - runtime - org.apache.plc4x plc4j-driver-modbus @@ -160,12 +152,6 @@ 1.0.0-SNAPSHOT runtime - - org.apache.plc4x - plc4j-driver-profinet-ng - 1.0.0-SNAPSHOT - runtime - org.apache.plc4x plc4j-driver-s7 diff --git a/website/asciidoc/modules/users/pages/protocols/index.adoc b/website/asciidoc/modules/users/pages/protocols/index.adoc index 464f9a8fe2..a536a4728f 100644 --- a/website/asciidoc/modules/users/pages/protocols/index.adoc +++ b/website/asciidoc/modules/users/pages/protocols/index.adoc @@ -22,14 +22,17 @@ The tables below list the drivers that actually ship with each language implemen A protocol only appears as supported where a driver exists and is registered with that implementation's driver manager - a protocol specification on its own is not enough. +The first table is also the project's *definition of which drivers are supported*: see +<> below before relying on a driver or reporting a security issue against one. + |=== |Protocol | C | C# | Go | Java | Python |AB-Ethernet |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] -|icon:exclamation[role="yellow"] +|icon:check[role="green"] +|icon:check[role="green"] |icon:times[role="red"] |ADS /AMS @@ -42,15 +45,15 @@ implementation's driver manager - a protocol specification on its own is not eno |BACnet/IP |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] -|icon:exclamation[role="yellow"] +|icon:check[role="green"] +|icon:times[role="red"] |icon:times[role="red"] |CAN (raw) |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] +|icon:check[role="green"] |icon:times[role="red"] |CANopen @@ -64,14 +67,14 @@ implementation's driver manager - a protocol specification on its own is not eno |icon:times[role="red"] |icon:times[role="red"] |icon:check[role="green"] -|icon:exclamation[role="yellow"] +|icon:times[role="red"] |icon:times[role="red"] |ctrlX |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] +|icon:times[role="red"] |icon:times[role="red"] |DF1 @@ -98,8 +101,8 @@ implementation's driver manager - a protocol specification on its own is not eno |Firmata |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] -|icon:exclamation[role="yellow"] +|icon:check[role="green"] +|icon:check[role="green"] |icon:times[role="red"] |IEC-60870 @@ -111,7 +114,7 @@ implementation's driver manager - a protocol specification on its own is not eno |KNXnet/IP |icon:times[role="red"] -|icon:exclamation[role="yellow"] +|icon:times[role="red"] |icon:check[role="green"] |icon:check[role="green"] |icon:times[role="red"] @@ -134,7 +137,7 @@ implementation's driver manager - a protocol specification on its own is not eno |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] +|icon:times[role="red"] |icon:times[role="red"] |PLC4X (Proxy-Protocol) @@ -144,11 +147,18 @@ implementation's driver manager - a protocol specification on its own is not eno |icon:check[role="green"] |icon:times[role="red"] -|Profinet +|Profinet (`profinet`) +|icon:times[role="red"] +|icon:times[role="red"] +|icon:times[role="red"] +|icon:times[role="red"] +|icon:times[role="red"] + +|Profinet (`profinet-ng`) +|icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] -|icon:exclamation[role="yellow"] |icon:times[role="red"] |S7 @@ -184,40 +194,99 @@ implementation's driver manager - a protocol specification on its own is not eno |=== |Legend: -- icon:check[role="green"] Implemented and usable -- icon:exclamation[role="yellow"] Partially implemented - see the notes below -- icon:times[role="red"] Not implemented +- icon:check[role="green"] Supported - ready to be used. Not every supported driver implements +every operation: some protocols have no meaningful read, or the driver covers a deliberate subset. +What it does implement works. See <> for the per-operation breakdown and +<> for the scope of each. +- icon:times[role="red"] Not usable, and not supported. Either no driver exists for that language, +or one exists but is unfinished, only partially implemented, or judged not ready to build on. See +<> and <> +for which is which in each case. -|=== +This table has exactly two states on purpose. From an application's point of view "there is no +driver" and "there is a driver you should not build on" have the same consequence, so they get the +same marking; the sections below are where the difference is explained. -Notes on the partially implemented drivers: +|=== -* *AB-Ethernet* reads and offers polled subscriptions in both Java and Go; neither writes. -* *BACnet/IP* passively receives broadcast traffic and exposes it as subscriptions. It does not read or write properties. -* *CAN (raw)* (Java) writes and subscribes; there is no read support. -* *CBus* (Java) is a skeleton that connects but implements no operations. The Go driver is the complete one. -* *ctrlX* (Java) answers pings; that is the only operation it completes. Discovery is offered by -the driver, but `CtrlXPlcDiscoverer.discoverWithHandler` is an unimplemented TODO that returns -`null` instead of a future, so a discovery request fails rather than finding anything. Its read, -write and subscribe builders are not implemented either, and although the connection carries -browse code, browsing does not work yet. -* *Firmata* writes and subscribes in both Java and Go; neither reads. A board is driven and -monitored, never polled. +[#scope-of-support] +== Scope of support + +The table above is the authoritative statement of what this project supports, per language: + +* A driver marked icon:check[role="green"] in a language's column is *ready to be used*. It is a +supported part of the release and is in scope of the project's security threat model +(`THREAT-MODEL.md`). +* icon:times[role="red"] - anything that is not a green check - means the driver is *not declared +ready*. It is not supported, and it is +*out of scope of the threat model*: a vulnerability report against a driver that is not marked with +a green check will be declined, citing this table. Findings in such a driver are treated as +code-quality issues rather than as vulnerabilities. + +This is decided *per language*: the same protocol can be supported in one language and not in +another. C-Bus is the standing example - the Go driver is green and supported, the Java driver is an +unfinished skeleton and is not. + +This rule deliberately does not depend on the second table on this page (<>), +which records which operations a driver implements in plc4j and is descriptive only. + +For plc4j the rule is also enforced by the build: `plc4j-driver-all` bundles exactly the drivers +that are green in the Java column, and nothing else. A driver that is not green has to be depended +on explicitly, by its own artifact - it will not arrive on your classpath through the meta package. + +The way to move a driver into scope is to finish it and change its cell to a green check - a change +to this table changes the threat model's coverage, with no edit to `THREAT-MODEL.md` needed. + +[#notes-on-driver-coverage] +== Notes on driver coverage + +What the drivers that are not icon:check[role="green"] above do and do not do, plus the scope of the +green ones that cover only part of the operation set. A green driver that appears here is supported +- the entry describes what it is for, not a defect. Where a protocol has drivers in more than one +language, they are separate implementations and can land on opposite sides of the line: + +* *AB-Ethernet* reads and offers polled subscriptions, and both drivers are supported. In Java +`onRead` is a complete implementation (sequential single-flight reads with a transaction counter, +request timeout and per-tag response codes) and subscriptions come from +`PollingSubscriptionConnectionBase`; Go mirrors it exactly, with `DefaultPollingSubscriber` as the +counterpart. Neither writes, and neither advertises the capability, so a write request is refused +rather than silently dropped - DF1 does define write commands, so this is an unimplemented feature +rather than a protocol limit. The Go reader decodes the `Integer`, `Word`, `Dword` and `Singlebit` +file types and answers `UNSUPPORTED` for anything else, which is one better than plc4j, where the +same case returns OK with a null value. +* *BACnet/IP* has two drivers that are not the same driver, and only the Go one is supported. *Go* +does `ReadProperty` (with segmentation and complex-ack decoding), `WriteProperty`, and real COV +subscriptions via `SubscribeCOV` with a lifetime. *Java* is an experiment and is not usable: it is +passive, receiving broadcast traffic and exposing it as subscriptions, and it does not read or write +properties. Its code is internally consistent - it advertises only `canSubscribe` and implements +exactly that - but it does not hold up at scale, which is why it is marked icon:times[role="red"] +rather than supported. Reading the code is not enough to tell; readiness is a maintainer +judgement, and this table is where that judgement is recorded. +* *CAN (raw)* (Java) writes and subscribes, and is supported. Writes for one node id are packed into +a single frame and serialized per tag with individual response codes; subscriptions register a +consumer per node and every inbound frame is dispatched as a `PlcSubscriptionEvent`. There is no +read, and there cannot meaningfully be one: a raw CAN bus is broadcast, with no request/response +exchange to address. +* *Firmata* writes and subscribes, and both drivers are supported. Writes are built into Firmata +messages and shipped (the protocol has no write acknowledgement, so the response is OK once the +bytes are out); a subscription sets the pin modes and enables reporting, and pin updates are +dispatched as subscription events. Neither driver reads - a board is driven and monitored, never +polled, which is how Firmata works. * *IEC 60870-5-104* subscribes only, in both Java and Go, because the protocol is push driven: the RTU sends unsolicited ASDUs once the session is started, so there is nothing to read or write. The Go driver keeps the IEC quality descriptors alongside each value, so a reading the RTU marked invalid, blocked, substituted or not-topical does not arrive looking like a good one. -* *KNXnet/IP* (C#) is the only driver in plc4net and has not kept pace with the other implementations. -* *Open-Protocol* (Java) is a skeleton that connects but implements no operations. -* *Profinet* (Java) discovers devices and browses the submodules of a connected device; neither of -the two Java drivers has read or write support. Only the newer `profinet-ng` driver is part of -`plc4j-driver-all`; the older `profinet` driver is still in the code base but is not bundled. In -`profinet-ng` a subscription request completes the PROFINET connect handshake, but the subscription -response future is deliberately left incomplete and `onRegisterConsumer` throws -`UnsupportedOperationException`, so no cyclic data ever reaches an application. The unbundled -`profinet` driver does implement that path - it completes the subscription response, registers -consumers per subscription handle and dispatches a `PlcSubscriptionEvent` for every incoming cyclic -frame - so the subscribe entry below, which describes what ships, does not apply to it. +* *KNXnet/IP* (C#) is the only driver in plc4net and has not kept pace with the other +implementations. plc4net is flagged "not ready for usage - abandoned" in the README, so the whole +implementation is unsupported - the C# column is marked not usable throughout for that reason, not +because of anything specific to this driver. The Go and Java KNXnet/IP drivers are unaffected and +are supported. +* *OPC-UA* and *CBus* (Go) both implement `Subscribe` fully but leave `Unsubscribe` returning +"Not Implemented" - an active subscription ends when the connection closes. Everything else in both +drivers works, which is why they are supported. +* *CBus* (Go) additionally carries `TODO: implement me` gaps for several CAL command types in its +tag handler and for some data types in its value handler. The mainstream lighting/status paths - +read, write, browse and subscribe - are implemented. * *SLMP* addresses the word devices `D`, `W` and `R` only, in both Java and Go. That is the scope the driver was written to rather than a port gap - the bit devices and the wider MELSEC command set are not implemented in either language. @@ -231,6 +300,58 @@ message model for it, but no language implementation has a driver: there is noth DF1 connection. DF1 command messages also appear inside the AB-Ethernet model, which is where that protocol is actually used today. +[#unfinished-drivers] +== Unfinished drivers + +The drivers below are in the code base but are *not usable*: each one connects, and then completes +nothing an application could be built on - at most a connectivity ping, a discovery scan or a +browse, never a read, a write or a delivered subscription. They are marked icon:times[role="red"] in +the table above, the same as a protocol with no driver at all - from an application's point of view +the two are equivalent, and this section is what tells them apart. All of them are Java-only - no other language ships a driver for these protocols. + +* *CBus* (Java) is a shell. It connects and starts a receive loop, but `getTagHandler()` returns +`null` - no tag syntax is defined, so nothing can be addressed - and the incoming-message handler +only writes a trace log, so received frames are discarded. No read, write, subscribe or browse +builder exists anywhere in its 376 lines, yet `CBusDriver.canRead()` returns `true`, so the driver +advertises a capability it does not have. *C-Bus is effectively a Go-only protocol*: the Go driver +is a complete ~25,000-line implementation with read, write, browse and subscribe, and is supported. +* *ctrlX* (Java) answers pings; that is the only operation it completes. Discovery is offered by +the driver, but `CtrlXPlcDiscoverer.discoverWithHandler` is an unimplemented TODO that returns +`null` instead of a future, so a discovery request fails rather than finding anything. Its read, +write and subscribe builders are not implemented either, and although the connection carries +browse code, browsing does not work yet. +* *Open-Protocol* (Java) is a skeleton that connects but implements no operations. +* *Profinet* - there are *two* Java drivers, `profinet` and `profinet-ng`, and *neither is +supported*. Both discover devices and browse the submodules of a connected device, and neither has +read or write support. ++ +-- +** `profinet-ng` is the newer one. A subscription request completes the PROFINET connect handshake, +but the subscription response future is deliberately left incomplete and `onRegisterConsumer` throws +`UnsupportedOperationException`, so no cyclic data ever reaches an application. +** `profinet` is the older one. It does implement the cyclic path that `profinet-ng` does not - it +completes the subscription response, registers consumers per subscription handle and dispatches a +`PlcSubscriptionEvent` for every incoming frame - but it is superseded and still has no read or +write, and it is not supported either. +-- + +None of these are bundled in `plc4j-driver-all` - it carries only the drivers that are green in the +Java column - so an application picks one up only by depending on its artifact deliberately. + +NOTE: The Go column has now been audited to the same depth as the Java one. Every Go driver +declares its capabilities through `PlcConnectionMetadata`, and in each case the declaration matches +what the code implements - `simulated` is the clearest illustration: it hands out a subscription +request builder but deliberately reports `ProvidesSubscribing: false`, because the `Subscriber` +behind it is a stub, and the code says so in a comment. No Go driver was found advertising a +capability it does not have, which is the failure that puts the Java C-Bus driver out of support. +The C# column is not usable throughout, because plc4net is abandoned. + +These drivers are unsupported and out of scope of the threat model under the general rule in +<> - not because of a carve-out specific to them, but simply because they are not +marked with a green check. The same applies to every other non-green driver: partial implementation +is not readiness either. + +[#features] == Features The table below lists which operations each driver actually implements. It describes *plc4j*, @@ -281,7 +402,7 @@ metadata inconsistently, so this table follows the code rather than the metadata |icon:check[role="green"] |icon:times[role="red"] -|CBus +|CBus (unsupported) |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] @@ -289,7 +410,7 @@ metadata inconsistently, so this table follows the code rather than the metadata |icon:times[role="red"] |icon:times[role="red"] -|ctrlX +|ctrlX (unsupported) |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] @@ -361,7 +482,7 @@ metadata inconsistently, so this table follows the code rather than the metadata |icon:check[role="green"] |icon:times[role="red"] -|Open-Protocol (Torque-Tools) +|Open-Protocol (Torque-Tools, unsupported) |icon:times[role="red"] |icon:times[role="red"] |icon:times[role="red"] @@ -377,7 +498,7 @@ metadata inconsistently, so this table follows the code rather than the metadata |icon:times[role="red"] |icon:times[role="red"] -|Profinet (`profinet-ng`) +|Profinet (`profinet-ng`, unsupported) |icon:check[role="green"] |icon:check[role="green"] |icon:times[role="red"] @@ -385,6 +506,14 @@ metadata inconsistently, so this table follows the code rather than the metadata |icon:times[role="red"] |icon:times[role="red"] +|Profinet (`profinet`, unsupported, not bundled) +|icon:check[role="green"] +|icon:check[role="green"] +|icon:times[role="red"] +|icon:times[role="red"] +|icon:check[role="green"] +|icon:times[role="red"] + |S7 |icon:check[role="green"] |icon:check[role="green"]