This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new 15072d2a63 chore(security): Add draft project security threat-model
document (#2575)
15072d2a63 is described below
commit 15072d2a6308a4faf54e2a7e4f3c7333af804a9f
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Jun 8 07:51:42 2026 +0200
chore(security): Add draft project security threat-model document (#2575)
* Add draft project security threat-model document
Adds a draft project-level security threat-model document
(draft-THREAT-MODEL.md) at repo root, improving discoverability
for automated security scanners running against this repository.
The file follows the rubric format used by several other ASF
projects piloting security-model discoverability.
The "draft-" prefix signals this is a proposal for the PMC to
review, correct, or reject — not a finalised maintainer-blessed
model. Every claim carries a provenance tag (documented /
inferred / maintainer) so reviewers can see where each claim
originates; §14 collects open questions for the maintainers.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* Revise threat model per chrisdutz PR review (OPC UA secure-by-default
ruling + SPI3 notes)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
draft-THREAT-MODEL.md | 1228 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1228 insertions(+)
diff --git a/draft-THREAT-MODEL.md b/draft-THREAT-MODEL.md
new file mode 100644
index 0000000000..99d61429bf
--- /dev/null
+++ b/draft-THREAT-MODEL.md
@@ -0,0 +1,1228 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+# Apache PLC4X Security Threat Model (draft)
+
+## §1 Header
+
+- **Project**: Apache PLC4X (`apache/plc4x`) — a multi-language library of
+ client-side protocol drivers for industrial / OT (operational-technology)
+ PLC protocols (Siemens S7, Modbus, EtherNet/IP, OPC UA, ADS/AMS, KNXnet/IP,
+ BACnet/IP, IEC-60870-5-104, CANopen, C-Bus, Firmata, Profinet, UMAS, etc.)
+ *(documented: `README.md`,
`website/asciidoc/modules/users/pages/protocols/index.adoc`)*.
+- **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`).
+ 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.
+- **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
+ triaged against the model as it stood at *N*, not at HEAD.
+- **Date**: 2026-05-30.
+- **Authors**: ASF Security team draft, awaiting PLC4X PMC review.
+- **Status**: draft — under maintainer review.
+- **Reporting cross-reference**: vulnerabilities that may violate a §8
+ property should be reported to the Apache Security Team
+ (`[email protected]`) per
+ `website/asciidoc/modules/users/pages/security.adoc`; reports that fall
+ under §3, §9, or §11a will be closed by PLC4X triagers citing this
+ document.
+- **Provenance legend** —
+ *(documented)* = drawn from in-repo docs, the website asciidoc tree, or
+ on-the-wire protocol specifications, with citation;
+ *(maintainer)* = stated by a PLC4X maintainer (PMC chair Chris Dutz or
+ another committer) in response to this draft;
+ *(inferred)* = synthesized by the producer from code structure, the
+ protocol's published specification, or general OT-protocol domain
+ knowledge, awaiting PMC ratification — every *(inferred)* tag has a
+ matching §14 question.
+- **Draft confidence**: 50 documented / 3 maintainer / 45 inferred.
+
+**About the project.** PLC4X provides a uniform client-side API
+(`PlcConnection` in Java; analogous in Go, C, Python) on top of a fleet of
+**protocol drivers**, each implementing one specific PLC wire protocol.
+An embedding application opens a connection by URL
+(e.g. `s7://10.0.0.5:102?remote-rack=0&remote-slot=1`,
+`modbus-tcp://10.0.0.5:502`,
+`opcua:tcp://10.0.0.5:12686?discovery=true&security-policy=Basic256Sha256`),
+optionally supplies a `PlcAuthentication`, and then issues `read` /
+`write` / `subscribe` / `browse` calls against tag-address strings whose
+syntax is **protocol-specific** (S7 area+offset, Modbus coil/register
+address, OPC UA `ns=...;s=...` node identifier, …). The library is a
+**library** — there is no daemon, no listening socket on the embedding
+side, no per-end-user identity. The "session" is the PLC connection.
+
+PLC4X's domain is **industrial network protocols**. Most of these protocols
+were standardized in the 1980s–2000s before OT-network security was a
+design concern: Modbus, S7 (PUT/GET), BACnet/IP, IEC-60870-5-104,
+Profinet, CANopen, KNXnet/IP, C-Bus, DF1, AB-Ethernet, ADS/AMS, EtherNet/IP,
+and the historical UMAS dialect of Modicon all transit cleartext on the
+wire with **no authentication, no integrity, and no confidentiality** —
+that is the protocol as standardized, not a PLC4X choice. OPC UA, the
+youngest of the supported set, is the lone exception: it carries a full
+TLS-like security stack (certificates, signed/encrypted message
+exchange, security policies) and the OPC UA driver here actually
+implements that stack. This asymmetry shapes essentially every claim in
+the rest of the document.
+
+## §2 Scope and intended use
+
+### Intended use
+
+- **Embedded-library access to PLC devices over their native protocols**,
+ from a host application (SCADA gateway, MES, historian, custom
+ industrial-IoT bridge, OPC-UA-server adapter, Calcite/Camel/NiFi
+ integration). Read tags, write tags, subscribe to changes, browse
+ device address space *(documented: `README.md` — "The Industrial IoT
+ adapter"; `website/asciidoc/modules/users/pages/protocols/index.adoc`)*.
+- **Single-PLC, request/response style traffic** initiated by the embedding
+ application against a known device endpoint
+ *(documented: per-protocol pages;
`plc4j/api/src/main/java/.../PlcConnection.java`)*.
+
+### Deployment shape
+
+PLC4X is **not** a network service. It is an **in-process library** built
+into a host application (Java, Go, C, or Python process). The host
+application — not PLC4X — is the thing exposed to its own users and to
+the wider IT network. PLC4X opens **outbound** connections to PLC
+endpoints; nothing in the main repo listens *(documented: `README.md`,
+the `plc4j/transports/` inventory which is exclusively client-side —
+`tcp`, `udp`, `serial`, `raw-socket`, `pcap-replay`, `socketcan`, `can`,
+`virtualcan`; no listener transport)*.
+
+The library lives where the **OT network meets the IT network**: in
+typical deployments, the host process runs on a Linux/Windows VM or
+container with network reachability into the plant-floor / fieldbus
+segment, and surfaces data northbound by some other mechanism the host
+application chose. PLC4X has no opinion on the northbound side.
+
+### Caller roles
+
+Because PLC4X is a library, there is no "client / operator / peer" role
+split of the kind a network service would have. The roles are:
+
+| Role | Trust level | Notes |
+| --- | --- | --- |
+| **Embedding application** | trusted — sole API caller | Decides which driver
to load, supplies the connection URL, the `PlcAuthentication` (when the
underlying protocol supports it), and every tag-address string. PLC4X has no
notion of an "end user" within the embedding process *(documented:
`plc4j/api/.../PlcConnection.java`, `PlcDriverManager.java`)*. |
+| **Remote PLC device** | **untrusted on the wire** | Industrial controllers
in the field; the device's firmware, the wire bytes it emits, and any responses
to PLC4X requests are all treated as untrusted input crossing a trust boundary
into the library's parser *(inferred — §14 Q1)*. |
+| **OPC UA server** | partially-authenticated peer | When OPC UA is the
protocol, the server identity is bound to a certificate that PLC4X can verify
against a trust store; that is the only protocol in the supported set where
peer identity is cryptographic *(documented:
`website/asciidoc/.../protocols/opcua.adoc`;
`plc4j/drivers/opcua/.../security/`)*. |
+| **PLC4X server / OPC UA server (the embedding side of `plc4x-extras`)** |
**out of scope** — see §3 *(maintainer)* | |
+
+### Component-family table
+
+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.getConnectionManager().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)* |
+
+A finding is in-model only if it lands in a row marked "yes" — see §4
+for the per-component reachability test.
+
+## §3 Out of scope (explicit non-goals)
+
+PLC4X is **not**, and does not aim to be, the following — reports requiring
+any of these will be closed with the cited disposition.
+
+1. **A defender for the OT protocols themselves.** Modbus, S7 (PUT/GET),
+ BACnet/IP, IEC-60870-5-104, Profinet, CANopen, KNXnet/IP, C-Bus, DF1,
+ AB-Ethernet, ADS/AMS (unless the operator has configured the AMS-route
+ credential flow), EtherNet/IP, and UMAS as standardized **do not
+ authenticate the peer, do not protect message integrity, and do not
+ encrypt** *(documented per protocol: `website/.../protocols/*.adoc`;
+ inferred from the protocol specifications themselves — §14 Q3)*. A
+ report of the shape "the wire payload is unauthenticated cleartext" or
+ "anyone on the OT network can spoof a Modbus master" describes the
+ **protocol**, not the library; PLC4X cannot add what the protocol
+ does not have. → `BY-DESIGN: protocol-disclaimed` (§9). The integrator
+ is responsible for the OT-network perimeter (§10). See §11a for the
+ per-protocol enumeration.
+2. **A server / responder for any of the supported protocols.** PLC4X is a
+ **client-side** library: it speaks the protocols *to* PLCs, not as a
+ 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`.
+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
+ embedding application could express via the public API is by
+ definition authorized; "a malicious embedding application writes to
+ the wrong coil" is not a PLC4X vulnerability *(inferred — §14 Q4)*.
+ → `OUT-OF-MODEL: trusted-input`.
+4. **A defender for end users of the embedding application.** PLC4X has
+ no concept of an "end user". Whether the embedding application
+ authenticates its own users, what authorization it enforces over
+ which tags they may read or write, and how it sanitizes user-supplied
+ tag-address strings before passing them to `parseTagAddress` is the
+ embedding application's responsibility *(inferred — §14 Q4)*. →
+ `OUT-OF-MODEL: trusted-input`.
+5. **A sandbox for tag-address strings sourced from untrusted users.**
+ Tag-address syntax is protocol-specific and rich (S7
+ `%DB1.DBX0.0:BOOL`, OPC UA `ns=2;s=...`, Modbus
`holding-register:40001:INT`,
+ …). If the embedding application accepts user-supplied tag strings,
+ it is responsible for vetting them — the parser does not provide a
+ security boundary against attacker-supplied tag strings (it provides a
+ correctness boundary against malformed strings) *(inferred —
+ §14 Q5)*. → `OUT-OF-MODEL: trusted-input` for syntactic-validation
+ reports, `VALID` for parser memory-corruption / DoS findings reachable
+ from caller-supplied strings.
+6. **Code that ships in the repository but is not part of the supported
+ product.** `plc4c/` and `plc4py/` are flagged "not ready for usage" in
+ `README.md`; `plc4net/` is "not ready for usage - abandoned"; the
+ `tools/`, `code-generation/`, `plc4j/utils/`,
`plc4j/drivers/{simulated,mock}`,
+ `images/`, `media/`, `website/`, `licenses/`, `src/`, top-level
+ `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`.
+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
+ parser flaws, BACnet stack vulnerabilities on the device side, OPC UA
+ server bugs) are upstream to the PLC vendor. The physical
+ consequences of a write — that a coil turning on opens a relief
+ valve, that a setpoint change spoils a batch — are downstream to
+ the integrator's process design *(inferred — §14 Q6)*. →
+ `OUT-OF-MODEL: adversary-not-in-scope` for device bugs;
+ `OUT-OF-MODEL: trusted-input` for process-control consequences of an
+ API-authorized write.
+8. **OT-network segmentation, hardening, and intrusion detection.**
+ PLC4X assumes the embedding application is deployed *somewhere with
+ network reachability to the PLC*; whether that path is firewalled,
+ air-gapped, monitored by an IDS, or routed over a hostile carrier is
+ the integrator's deployment posture *(inferred — §14 Q7)*. →
+ `OUT-OF-MODEL: adversary-not-in-scope`.
+9. **TLS / VPN / IPSec tunnels that the integrator may layer underneath.**
+ Most cleartext OT protocols are operated in production behind some
+ form of point-to-point tunnel (a VPN concentrator at the OT/IT
+ boundary, an IPSec link to a remote pumping station, a TLS-tunneling
+ proxy in front of a Modbus device). PLC4X has **no built-in TLS or
+ tunneling layer** for the cleartext protocols *(inferred from
+ transport inventory — §14 Q8)*. Reports of the shape "Modbus-TCP
+ should run over TLS" describe an integrator deployment choice; the
+ driver does not block such a deployment but does not provide it
+ either. → `BY-DESIGN: property-disclaimed`.
+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.
+
+## §4 Trust boundaries and data flow
+
+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) |
+
+### Reachability preconditions per family
+
+For each family in §2, a finding is in-model only if it is reachable as
+follows.
+
+- **Per-protocol driver, generated `readwrite/` parser code, OPC UA
+ security subsystem**: reachable from **bytes the remote PLC (or
+ attacker-on-the-OT-network) returns to the driver over the
+ configured transport**. The parser/decoder must not corrupt host memory,
+ must not infinite-loop, must not allocate unboundedly, and must not
+ cause the host process to crash on adversarial input
+ *(inferred — §14 Q11)*. **This is the single most important reachability
+ boundary in the document** — most realistic PLC4X-side vulnerabilities
+ will be wire-parser bugs.
+- **Public API (`plc4j/api`), SPI (`plc4j/spi`)**: reachable from the
+ embedding application's calls. Memory-safety / correctness issues
+ here are valid only if the embedding application's call sequence is
+ documented (per the Javadoc / the public Java API).
+- **Tag-address parser** (per-driver `*TagHandler`, `parseTagAddress`):
+ reachable from the **tag-address string** passed by the embedding
+ application. PLC4X does not assume the string itself is
+ attacker-controlled — see §3 item 5 — but a parser bug that
+ corrupts memory or hangs the parser is still `VALID-HARDENING` (and
+ `VALID` if the parser is memory-corrupting and the embedding
+ application *does* pass user-controlled strings) *(inferred —
+ §14 Q5)*.
+- **Transport layer**: reachable from bytes the OS delivers via the
+ socket / serial port / libpcap capture file. Memory safety on
+ malformed framing is in model *(inferred — §14 Q11)*.
+- **OPC UA security subsystem** (B2-OPCUA): reachable from
+ certificate chains, security tokens, signed handshake messages
+ produced by the remote endpoint. **Findings here are weighted more
+ heavily than equivalent findings in a cleartext-protocol driver**:
+ the OPC UA driver is the one place in the repository that
+ *promises* a cryptographic property, so a break of that property
+ is a §8 P1 violation, not a §11a non-finding.
+- **Generated code (mspec)**: identical to the driver that consumes it
+ — generated parsers run inside the driver. The mspec compiler
+ itself is build-time and out of model per §3 item 6.
+- **`plc4go/`, `plc4c/`, `plc4py/`**: per-language; the C and Python
+ bindings are flagged "not ready for usage" in `README.md` — see
+ §3 item 6 and §5a.
+
+## §5 Assumptions about the environment
+
+- **Operating system**: Linux primary (Ubuntu / RHEL family),
+ Windows / macOS supported for Java; ARM-and-x86 supported.
+ Java 21 minimum, tested up to Java 24 *(documented: `README.md`)*.
+- **Java runtime**: HotSpot or OpenJ9; the build requires
+ `--add-exports jdk.compiler/...` for code generation but the
+ runtime artifact does not *(documented: `README.md`)*.
+- **Process model**: in-process library. PLC4X loads as a JAR (or Go
+ module, or Python wheel) into the embedding application; it
+ registers protocol drivers via the Java SPI mechanism (or
+ language-equivalent). No long-lived daemons of its own *(documented:
+ `plc4j/api/.../PlcDriverManager.java`)*.
+- **Network**: outbound TCP / UDP / raw-socket / SocketCAN / serial as
+ required by the selected driver. Ports per the protocol:
+ 502/Modbus-TCP, 102/S7, 47808/BACnet, 44818+2222/EtherNet-IP, 4840
+ + dynamic/OPC UA, 2404/IEC-60870-104, 3671/KNXnet/IP, etc.
+ *(documented per protocol page)*. **The OT network is assumed
+ to be operated as a defended perimeter by the integrator** —
+ see §10 item 1.
+- **Memory**: the JVM (or Go GC, or C `malloc`, or CPython allocator)
+ provides the underlying memory model. PLC4X writes Java / Go / C /
+ Python code at the language's normal trust level.
+- **Concurrency**: each `PlcConnection` is a Netty channel; calls
+ through `read`/`write`/`subscribe`/`browse` return
+ `CompletableFuture`s. Thread safety is per the documented Java API
+ contract *(inferred — §14 Q12)*.
+- **Cryptography (OPC UA only)**: the JCE provider shipped with the
+ JVM (OpenJDK / Oracle / IBM) provides AES, RSA, RSASSA-PSS,
+ HMAC-SHA1/256, X.509 parsing
+ *(documented: `plc4j/drivers/opcua/.../security/SecurityPolicy.java`)*.
+- **Filesystem (OPC UA only)**: a keystore and truststore are loaded
+ from operator-configured paths
+ *(documented: `plc4j/drivers/opcua/.../config/OpcuaConfiguration.java`,
+ `plc4j/drivers/opcua/.../context/OpcuaDriverContext.java`)*.
+- **Filesystem (KNX only)**: `.knxproj` ETS files are parsed via
+ XML; XXE / external-DTD / external-schema are explicitly
+ disabled *(documented: `plc4j/drivers/knxnetip/.../ets/EtsParser.java`
+ lines 65–71 — `factory.setFeature(DISALLOW_DOCTYPE_DECL, true)` +
+ `FEATURE_SECURE_PROCESSING` + empty `ACCESS_EXTERNAL_DTD/SCHEMA`)*.
+- **Filesystem (BACnet/IP only)**: EDE (Engineering Data Exchange)
+ files describing the target devices are loaded from
+ operator-configured paths *(documented:
+ `plc4j/drivers/bacnet/.../configuration/BacNetIpConfiguration.java`)*.
+- **What PLC4X does NOT do to its host** (negative claims, awaiting
+ maintainer ratification — these are predominantly *(inferred)*,
+ see §14 Q13):
+ - **does not** install signal handlers besides those Netty / JVM
+ install for it *(inferred — §14 Q13)*;
+ - **does not** spawn child processes at runtime *(inferred —
+ §14 Q13)*;
+ - **does not** open listening sockets — all sockets are outbound
+ *(inferred from transport inventory — §14 Q13)*;
+ - **does not** read environment variables for security-sensitive
+ behavior beyond standard JVM/JNI conventions *(inferred —
+ §14 Q13)*;
+ - **does not** mutate process-wide global state (allocator, locale,
+ `System.setSecurityManager`, etc.) at runtime *(inferred —
+ §14 Q13)*;
+ - the `raw-socket` transport **does** require `CAP_NET_RAW` (Linux)
+ / Administrator (Windows) / root-equivalent privileges when
+ used; this is a privileged transport with an inherent host-level
+ cost the embedding application takes on by choosing it
+ *(inferred — §14 Q10)*.
+
+## §5a Build-time and configuration variants
+
+PLC4X exposes **per-driver configuration knobs** rather than a
+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 documen [...]
+| 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. |
+
+### The insecure-default case (OPC UA)
+
+The OPC UA driver's **defaults are dev/lab-grade, not production-grade**:
+`security-policy=NONE`, `trust-store-file` unset, `discovery=true` over an
+unencrypted channel. Each of these defaults voids a §8 property the
+driver could otherwise provide. The §14 Q14, Q15, Q16 questions ask
+the maintainer to choose, per knob, between:
+
+- **(a) "the default is the supported posture"** — in which case a report
+ of "OPC UA driver accepts any server cert" is `VALID` against PLC4X,
+ and the documentation needs to make clearer what the trade-off is,
+ or
+- **(b) "the default is dev/test convenience; operator MUST configure
+ the production knobs per §10"** — in which case the report is
+ `OUT-OF-MODEL: non-default-build` and the §10 contract makes the
+ required knobs unambiguous.
+
+Both stances are defensible; the maintainer ruling determines which §13
+disposition applies. **The text of §10 items 4–6 is written on
+hypothesis (b)** and will need adjustment if the maintainer chooses
+(a).
+
+## §6 Assumptions about inputs
+
+### Per-entry-point trust table (Java public API)
+
+| Entry point | Parameter | Attacker-controllable? | Caller must enforce |
+| --- | --- | --- | --- |
+| `PlcDriverManager.getConnection(url, [auth])` | `url` | **no** — trusted
caller string | only well-formed URLs reach this method; do not let end-users
construct them |
+| `PlcDriverManager.getConnection(url, auth)` | `auth` (`PlcAuthentication`) |
**no** — trusted caller object | credentials in the auth object are forwarded
to the protocol (cleartext, except inside an OPC UA secure channel) |
+| `connection.parseTagAddress(addr)` | `addr` | **conditionally** — trusted by
default; if the embedding application sources tag strings from end users, the
embedding application is responsible *(see §3 item 5; §14 Q5)* | tag-address
syntax validation; do not pass un-vetted untrusted strings |
+| `readRequestBuilder.addTagAddress(name, addr)` | `addr` | conditionally —
same as above | as above |
+| `writeRequestBuilder.addTagAddress(name, addr, value)` | `value` | **no** —
trusted caller value | application-level authorization over which tags may be
written |
+| `subscriptionRequestBuilder.add*(addr, ...)` | `addr` | conditionally — same
as above | as above |
+
+### 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)* |
+
+### Size / shape / rate
+
+- PLC4X drivers **stream individual request/response frames**; there is
+ no documented per-call cap on response size beyond what the protocol
+ itself bounds (Modbus PDUs are spec-bounded, S7 PDU size is
+ negotiated, OPC UA chunk size is negotiated) *(documented: per
+ protocol page; `S7Configuration.pduSize`,
+ `OpcuaConfiguration.limits.encoding.{send,receive}-buffer-size`)*.
+- **PLC4X provides no per-call rate limit or backpressure on the API
+ side** — if the embedding application calls `read` in a hot loop, the
+ driver will issue as many requests as the underlying transport allows
+ *(inferred — §14 Q19)*.
+
+## §7 Adversary model
+
+### Actors
+
+| Actor | In scope? | Capabilities granted |
+| --- | --- | --- |
+| **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). |
+| **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. |
+| **Author of a tag-address string** | conditionally in scope — see §3 item 5
| the embedding application decides whether to expose this surface. |
+| **Side-channel observer** (cache timing, branch prediction, network timing
of OPC UA crypto operations) | **out of scope** *(inferred — §14 Q20)* |
standard JCE side-channel posture; no PLC4X-level constant-time guarantee. |
+| **Quantum adversary** | **out of scope** | the OPC UA security policies in
§5a use classical RSA / AES / SHA. |
+
+### Authenticated-but-Byzantine peer
+
+PLC4X is a client-side library; there is no internal "cluster
+membership" to be Byzantine within. The closest analogue is **a PLC
+device whose firmware has been compromised and which is sending crafted
+responses while passing whatever authentication the protocol provides**.
+For cleartext protocols this is no different from any other attacker
+on the wire (§3 item 1); for OPC UA, this is the "compromised but
+properly-certified server" case — the driver still trusts the server's
+*claims* about data, even if the channel is cryptographically sound
+*(inferred — §14 Q21)*. A finding requiring a compromised PLC to
+**produce wrong measurement values** is `OUT-OF-MODEL: trusted-input`
+(the device is the source of truth for its own data); a finding
+requiring a compromised PLC to **crash or corrupt the driver via
+malformed wire bytes** is in-model for memory safety.
+
+## §8 Security properties the project provides
+
+For each property: condition, violation symptom, severity tier,
+provenance. There are fewer §8 properties than for a typical
+threat-model target precisely because PLC4X is a client-side library
+sitting on top of OT protocols that themselves provide little — most
+of the security work is delegated to the integrator (§10).
+
+### P1 — OPC UA secure-channel confidentiality and integrity, when configured
+
+- **Condition**: `security-policy` is set to one of `Basic128Rsa15`,
+ `Basic256`, `Basic256Sha256`, `Aes128_Sha256_RsaOaep`, or
+ `Aes256_Sha256_RsaPss` *(documented:
+ `plc4j/drivers/opcua/.../security/SecurityPolicy.java`)*, AND
+ `message-security` is `SIGN` or `SIGN_ENCRYPT` *(documented)*, AND
+ `trust-store-file` is set to a trust store containing the expected
+ server certificate (or its issuer) *(documented:
`website/.../protocols/opcua.adoc`)*.
+- **Violation symptom**: wire bytes between the driver and the server
+ that an on-path attacker can decrypt (without the configured key) or
+ modify without the driver detecting the modification.
+- **Severity**: **security-critical**, `VALID` per §13.
+- *(documented + inferred — §14 Q17)*: the implementation of the
+ policies is in repo; the property is documented by reference to the
+ OPC UA spec, but no PLC4X-specific test or formal-verification artifact
+ is cited.
+
+### P2 — OPC UA server-certificate authentication, when a trust store is
configured
+
+- **Condition**: `trust-store-file` is set; the trust store contains
+ the expected certificate or a chain root *(documented:
+ `website/.../protocols/opcua.adoc`)*.
+- **Violation symptom**: the OPC UA driver completes a handshake with a
+ server presenting a certificate that does not chain to a trust-store
+ root, and proceeds to exchange application-layer messages.
+- **Severity**: **security-critical**, `VALID` per §13.
+- *(documented)*.
+
+### P3 — Memory safety of the Java public API and Java SPI on well-formed
embedding-application calls
+
+- **Condition**: the embedding application observes the documented
+ Java contract (Javadoc): correct types, non-`null` where required,
+ `close()` semantics observed.
+- **Violation symptom**: `NullPointerException` / `IllegalStateException`
+ in PLC4X internals from a documented-correct API call (not a JVM
+ crash — Java is GC'd); for the Go bindings,
+ panic from an `unsafe`-free call site; for `plc4c`, native crash.
+- **Severity**: **correctness-only** for non-driver code; **security-critical**
+ when a driver path is reachable from B2 (network input). See P4.
+- *(inferred — §14 Q22)*.
+
+### P4 — Memory safety, bounded allocation, and bounded loop / recursion in
the per-driver wire parsers, on adversarial wire bytes
+
+- **Condition**: the parser is reachable via B2 (network input) from
+ a remote PLC or an attacker on the OT network with reachability;
+ the host JVM / Go runtime / C `malloc` follows its documented
+ semantics.
+- **Violation symptom**: in Java — JVM heap exhaustion driven by a
+ bounded-input expansion factor, parser hang on crafted input,
+ `StackOverflowError` from unbounded recursion on a nested wire
+ structure; in Go — `goroutine` hang, unbounded allocation; in C
+ — heap / stack corruption, OOB read/write, use-after-free.
+- **Severity**: **security-critical** (the OT network is the §7 primary
+ adversary), `VALID` per §13.
+- *(inferred — §14 Q11)*. **This is the most reachable §8 property
+ for the scan target, and the most likely site of real findings.**
+
+### P5 — Tag-address parser correctness and bounded resource use, on
caller-supplied address strings
+
+- **Condition**: the embedding application calls
+ `parseTagAddress` / `addTagAddress` per the documented per-driver
+ syntax.
+- **Violation symptom**: parser crash, hang, or unbounded allocation
+ on a syntactically-malformed address; cross-driver interference
+ (a tag-address string for driver A causing observable state change
+ in driver B).
+- **Severity**: **`VALID-HARDENING`** by default (the address string is
+ trusted per §6); **`VALID`** when the parser is reachable from
+ unbounded user-supplied strings AND the embedding application's
+ documented use of the API includes that case.
+- *(inferred — §14 Q5)*.
+
+### P6 — XXE / external-DTD / external-schema disabled in the KNX `.knxproj`
parser
+
+- **Condition**: an integrator loads a `.knxproj` ETS file through the
+ KNX driver.
+- **Violation symptom**: an XML processor in `EtsParser` resolves an
+ external entity, external DTD, or external schema, allowing
+ file-disclosure / SSRF from the `.knxproj`.
+- **Severity**: **security-critical**, `VALID` per §13.
+- *(documented: `EtsParser.java` lines 65–71)*. This is the one
+ obvious "we hardened this" property in the repo and we cite it
+ explicitly.
+
+### P7 — OPC UA secure-channel implementation tracks the OPC UA spec's
security-policy URIs
+
+- **Condition**: an OPC UA security policy ≠ `NONE` is selected.
+- **Violation symptom**: a policy-spec-compliant OPC UA server rejects
+ the driver's handshake; or the driver's handshake completes with a
+ policy weaker than the URI implies; or the driver picks an
+ algorithm not in the policy's algorithm-set.
+- **Severity**: **security-critical**, `VALID` per §13.
+- *(documented + inferred — §14 Q17)*. Compatibility against Eclipse
+ Milo and OPC Foundation .NET is asserted in the docs
+ *(`website/.../protocols/opcua.adoc`)*; a property break against
+ those reference servers is a §8 P7 violation.
+
+## §9 Security properties the project does *not* provide
+
+These are the false-friend properties — features that *look like* a
+security property but are not one — and the well-known classes of
+attack the library cannot defend against. **This section is the most
+important one for an integrator.**
+
+### General disclaimers
+
+- **No authenticity, integrity, or confidentiality on Modbus, S7, BACnet/IP,
+ IEC-60870-5-104, Profinet, CANopen, KNXnet/IP, C-Bus, DF1,
+ AB-Ethernet, EtherNet/IP, UMAS, or ADS/AMS wire traffic.** PLC4X
+ implements the protocol as standardized; the protocols themselves
+ do not have those properties *(documented per protocol; inferred —
+ §14 Q3)*. → §3 item 1, §11a.
+- **No built-in transport-layer tunneling.** PLC4X does not transparently
+ wrap a cleartext protocol in TLS, VPN, IPSec, or Wireguard. If the
+ integrator wants encrypted-on-the-wire Modbus, they must run Modbus
+ inside an integrator-provided tunnel *(inferred — §14 Q8)*.
+- **No replay-protection on cleartext protocols.** A request the driver
+ 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)*.
+- **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
+ writes.** The embedding application owns that decision. See §3 items 3, 4.
+ 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)*.
+- **No DoS protection at the API surface.** The embedding application
+ is trusted to call the API at a reasonable rate
+ *(inferred — §14 Q19)*.
+- **No data-at-rest encryption.** PLC4X does not persist anything to
+ 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)*.)
+- **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
+ JCE / underlying-OpenSSL posture; no PLC4X-level mitigations
+ *(inferred — §14 Q20)*.
+- **No defender stance against a compromised PLC firmware sending
+ spec-compliant but semantically wrong data.** Garbage-in / garbage-out
+ per §7 / §3 item 7.
+
+### False-friend properties (call out separately)
+
+- **"OPC UA is encrypted" — only with non-default configuration.** A
+ reader who knows OPC UA has "Secure: encryption, authentication, and
+ auditing" in its spec slogan
+ *(`website/.../protocols/opcua.adoc` line 264)* would assume the
+ PLC4X OPC UA driver provides those properties by default. **It does
+ not.** Defaults are `security-policy=NONE` (no encryption), no
+ trust store (no server-identity validation), discovery over an
+ unencrypted channel. All three must be flipped from default for the
+ cryptographic story to mean what an OPC UA reader expects.
+ *(inferred — §14 Q14, Q15, Q16)*
+- **OPC UA discovery is unencrypted by spec.** The OPC UA *spec*
+ conducts discovery over `security-policy=NONE`. An attacker on the
+ path during discovery can rewrite advertised endpoints, server
+ certificates, and supported policies before the driver chooses
+ what to honor. This is not a PLC4X bug; this is OPC UA *(inferred —
+ §14 Q16)*. But a
+ scanner that reports "discovery handshake is unauthenticated" against
+ PLC4X is reporting on the protocol, not on the driver
+ *(documented)*.
+- **Modbus / S7 PUT-GET / EtherNet-IP / ADS / etc. running over TCP
+ port `:N` is not "secure" because TCP is.** The transport-level
+ encryption is absent; the protocol-level encryption is absent. The
+ protocol-spec authentication is either absent (Modbus) or weak (S7
+ PUT/GET *(inferred — §14 Q23)*) or device-specific (ADS/AMS route
+ setup).
+- **The `simulated://`, `mock://` drivers are dev-tools, not sandboxes.**
+ They exist to let an embedding application be unit-tested without a
+ real PLC; they are not isolation boundaries.
+- **The `pcap-replay` transport is a developer / regression-test tool.**
+ It replays captured frames; it is not designed for production use
+ and a finding against it that depends on an attacker controlling the
+ pcap file is `OUT-OF-MODEL: unsupported-component`.
+- **`plc4c/`, `plc4py/`, `plc4net/` are README-flagged "not ready for
+ usage".** A finding against them is `OUT-OF-MODEL:
+ unsupported-component` until the README line changes (§5a).
+- **`PlcUsernamePasswordAuthentication` on ADS / OPC UA carries
+ credentials on the wire as the protocol dictates.** It is not a
+ PLC4X-level security primitive — it is a typed wrapper around two
+ strings that the driver forwards to the protocol's authentication
+ step. Whether it is then encrypted (OPC UA with a secure channel) or
+ cleartext (ADS payload, OPC UA with `security-policy=NONE`) depends
+ on the rest of the configuration.
+- **`PlcCertificateAuthentication`** wraps an in-memory KeyStore for
+ certificate-based authentication; the security properties of the
+ surrounding TLS / OPC UA channel still apply.
+
+### Well-known attack classes against this category of project that PLC4X does
not defend against
+
+- **MITM on cleartext OT protocols.** Out of model per §3 item 1.
+- **Replay of OT commands** (a recorded "open valve" frame replayed
+ later). Out of model per §3 item 1.
+- **Spoofing of a Modbus master / S7 PG / BACnet master.** Out of model
+ per §3 item 1.
+- **Compression / decoding bombs in `.knxproj` ZIP files.** ZIP-slip
+ in the unzip step is *(inferred — §14 Q18)*.
+- **XML billion-laughs in `.knxproj`.** Mitigated by
+ `FEATURE_SECURE_PROCESSING` *(documented: `EtsParser.java`)*. →
+ `KNOWN-NON-FINDING`.
+- **OPC UA discovery-channel MITM.** Out of model per §9
+ false-friend item 2.
+- **OPC UA server-certificate substitution when `trust-store-file` is
+ unset (the default).** **Pending maintainer ruling** (§14 Q15).
+- **DoS via unbounded-tag-list `read` / `subscribe` requests.** Out of
+ model per §9 "No DoS protection at the API surface".
+- **Memory corruption in the per-protocol wire parser on a crafted
+ response from an attacker-controlled or compromised PLC.** This **is**
+ in model — see §8 P4. The most likely category of real findings.
+
+## §10 Downstream responsibilities
+
+The embedding application / integrator deploying PLC4X in production
+**must**:
+
+1. **Treat the OT network as the security perimeter for every cleartext
+ protocol.** Operate Modbus, S7, BACnet/IP, IEC-60870-104, Profinet,
+ CANopen, KNXnet/IP, C-Bus, DF1, AB-Ethernet, EtherNet/IP, UMAS,
+ and ADS/AMS payload traffic on an OT VLAN, behind a firewall, with
+ no untrusted-network egress, ideally with monitoring. PLC4X
+ provides no transport security for these protocols, by design
+ (§9). *(integrator)*
+2. **If a cleartext protocol must traverse a hostile path, deploy an
+ external tunnel** (site-to-site VPN, IPSec, stunnel/TLS-wrapping
+ proxy, or operate the cleartext protocol exclusively inside a
+ point-to-point cellular-modem channel). PLC4X does not bundle
+ such a tunnel. *(integrator)*
+3. **For OPC UA in production: set `security-policy` to
+ `Basic256Sha256` or stronger AND `message-security` to
+ `SIGN_ENCRYPT` AND configure a `trust-store-file` that contains
+ only the expected server certificate or its issuing CA.** The
+ default of "`NONE`, no trust store" is **not** the production
+ posture *(inferred — §14 Q14, Q15)*.
+4. **For OPC UA where mutual authentication is required: provision a
+ client certificate and load it via `key-store-file` /
+ `key-store-password`.** The auto-generated self-signed certificate
+ the driver falls back to is not recoverable across restarts and
+ cannot satisfy a peer that requires a stable client identity
+ *(documented: `website/.../protocols/opcua.adoc`)*.
+5. **For OPC UA where discovery is over a hostile path: disable
+ `discovery` and configure the endpoint explicitly via
+ `endpoint-host`, `endpoint-port`.** The discovery handshake is
+ unauthenticated by OPC UA spec *(documented)*.
+6. **Vet tag-address strings before passing them to PLC4X if they are
+ sourced from end users of the embedding application.** PLC4X does
+ not provide a security boundary against attacker-supplied
+ tag-address strings (§3 item 5). *(integrator)*
+7. **Authorize which tags may be read or written by which end user
+ inside the embedding application.** PLC4X has no end-user
+ identity. *(integrator)*
+8. **Bound the request rate / queue depth on the embedding-application
+ side if DoS resistance against a misbehaving caller is needed.**
+ PLC4X has no API-level throttle *(inferred — §14 Q19)*. *(integrator)*
+9. **Run the embedding process with the minimum host privileges the
+ chosen transport requires.** Most transports need only a
+ non-privileged user; the `raw-socket` transport requires
+ `CAP_NET_RAW` (or Administrator) — grant it to the embedding
+ process specifically rather than running as `root`. *(integrator)*
+10. **Treat the `simulated://`, `mock://`, `pcap-replay` drivers as
+ dev / test fixtures, not as production endpoints.** *(integrator)*
+11. **Do not deploy `plc4c/`, `plc4py/`, or `plc4net/` artifacts in
+ production until the README line ("not ready for usage") is
+ removed.** *(integrator; documented: `README.md`)*
+12. **Trust the PLC firmware only as far as the integrator independently
+ trusts the device.** The semantic content of values returned by the
+ PLC is **device-side trusted-input** per §3 item 7 — but a buggy
+ firmware sending malformed wire bytes is still in-model for
+ parser robustness (§8 P4). *(integrator)*
+
+## §11 Known misuse patterns
+
+- **Exposing the embedding-application process directly to an
+ untrusted network and trusting the OT-network protocol's "auth" to
+ hold.** Modbus, S7 PUT/GET, etc., do not have authentication. The
+ OT-network perimeter (§10 item 1) is the *only* control.
+- **Running OPC UA with `security-policy=NONE` in production.** The
+ default. → §9 false-friend item 1.
+- **Running OPC UA with a configured security policy but no
+ `trust-store-file`.** The driver uses `PermissiveCertificateVerifier`
+ by default — every server certificate is accepted. An MITM gets the
+ encryption key. → §5a.
+- **Treating `discovery=true` as part of the security boundary.**
+ Discovery is over `security-policy=NONE` by OPC UA spec. → §9
+ false-friend.
+- **Using `simulated://` or `mock://` in production code paths.** They
+ are dev tools.
+- **Passing end-user-supplied tag-address strings to `parseTagAddress`
+ without vetting.** A parser bug becomes a user-reachable bug.
+- **Deploying the `plc4c/` or `plc4py/` artifact in production.** The
+ README flags them.
+- **Relying on `PlcCertificateAuthentication` without TLS / OPC UA
+ secure channel underneath.** The KeyStore exists; the credential is
+ there; whether anything on the wire is actually encrypted depends on
+ the surrounding configuration.
+- **Running with `--add-exports jdk.compiler/...` flags in the
+ production JVM.** Those flags are for the **build**, not the
+ runtime artifact *(documented: `README.md`)*; granting them at
+ runtime exposes JDK internals unnecessarily.
+- **Bundling the embedding application into a single host on an OT
+ segment and assuming the host is "behind the firewall, so it's
+ fine".** PLC4X does no integrity verification on its own outbound
+ traffic. A compromised host with PLC4X loaded can write arbitrary
+ values to any reachable PLC.
+
+## §11a Known non-findings (recurring false positives)
+
+This section is the highest-leverage input for automated agentic
+security scans. Each entry: tool symptom, why it is safe under the
+model, the § that licenses the call. **PLC4X's §11a is unusually
+long because the project's core domain is unauthenticated OT
+protocols, and a scanner unaware of that fact will report essentially
+every wire-format property of every driver.**
+
+### Protocol-by-design non-findings
+
+- **"Modbus has no authentication on the wire."** Modbus does not
+ define authentication. → §3 item 1, §9. `BY-DESIGN:
+ protocol-disclaimed`.
+- **"Modbus is sent in cleartext."** Modbus does not define
+ encryption. → §3 item 1, §9. `BY-DESIGN: protocol-disclaimed`.
+- **"S7 PUT/GET sends commands without authenticating the master."**
+ PUT/GET is the spec's "client/server data block access" mode; it
+ does not authenticate the master. The S7 driver here speaks PUT/GET
+ by design *(documented: `website/.../protocols/s7.adoc` —
+ "S7-300/S7-400 controllers, as well as basic reading and writing
+ functions for the S7-1200 and S7-1500 devices (PUT/GET
+ functions)")*. → §3 item 1, §9. `BY-DESIGN: protocol-disclaimed`.
+- **"BACnet/IP has no authentication, integrity, or confidentiality."**
+ Classic BACnet/IP (the version implemented here) defines none of
+ those properties. (BACnet/SC, the secure variant, is a different
+ protocol and is not in PLC4X.) → §3 item 1, §9. `BY-DESIGN`.
+- **"IEC-60870-5-104 is cleartext and unauthenticated."** Spec.
+ (IEC-62351 is the secure overlay and is not in PLC4X.) → §3 item 1,
+ §9. `BY-DESIGN`.
+- **"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`.
+- **"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`.
+- **"KNXnet/IP / KNX Tunnelling has no authentication."** Classic
+ KNXnet/IP does not. (KNX Secure is the secure overlay and is
+ *(inferred — §14 Q24)* not implemented in this driver. Confirm.) →
+ §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`.
+- **"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`.
+
+### Driver-default non-findings (pending maintainer ruling)
+
+- **"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,
+ §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
+ `OUT-OF-MODEL: non-default-build`.
+- **"OPC UA driver does discovery in cleartext."** True by spec; not a
+ driver bug. → §9 false-friend item 2.
+- **"OPC UA driver auto-generates a self-signed client certificate."**
+ Documented behavior when `key-store-file` is unset
+ *(`website/.../protocols/opcua.adoc`)*. The auto-generated cert is
+ intended as a "get-it-working" convenience; production should
+ provision an explicit one. → §10 item 4. `OUT-OF-MODEL:
+ non-default-build` pending §14 Q14.
+- **"OPC UA driver uses `Basic128Rsa15`/`Basic256` policies which are
+ deprecated by the OPC Foundation."** Yes — the driver supports them
+ *(documented: `SecurityPolicy.java`)* because some PLC firmware
+ still only supports them. The operator is responsible for choosing
+ `Basic256Sha256` or stronger per §10 item 3 *(inferred — §14 Q25)*.
+ → `OUT-OF-MODEL: non-default-build`.
+
+### Code-base non-findings
+
+- **"XXE / external-DTD / external-schema processing in the KNX
+ parser."** Disabled *(documented: `EtsParser.java` lines 65–71)*.
+ → `KNOWN-NON-FINDING`.
+- **"Hardcoded password / token in `plc4j/utils/test-utils/`,
+ `tests/`, `testdata/`, `simulated://`, `mock://` drivers."** These
+ are test infrastructure. → §3 item 6. `OUT-OF-MODEL:
+ unsupported-component`.
+- **"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.
+- **"`code-generation/` mspec compiler has X."** Build-time only,
+ not in the runtime artifact. → §3 item 6. `OUT-OF-MODEL:
+ unsupported-component`.
+- **"`Dockerfile`, `docker-compose.yaml`, `Jenkinsfile` has X."**
+ Repo infrastructure. → §3 item 6.
+- **"OPC-UA Server / PLC4X Server / Calcite / Camel / Kafka-Connect /
+ NiFi adapter has X."** Out of this repo (in `plc4x-extras`). →
+ §3 item 2. `OUT-OF-MODEL: unsupported-component`.
+- **"Vendored Eclipse Milo / Netty / Jackson / commons-* has CVE-Y."**
+ Report upstream; PLC4X picks up fixes via dependency-version bumps.
+ `OUT-OF-MODEL: unsupported-component` *(inferred — §14 Q26)*.
+- **"SQL injection / XSS / SSRF in the website asciidoc tree."**
+ Documentation, not the deployed library. → §3 item 6.
+
+### Integrator-deployment non-findings
+
+- **"PLC4X runs as root."** PLC4X does not require root; the embedding
+ application chose the UID. (See §10 item 9 for `CAP_NET_RAW`.)
+ `OUT-OF-MODEL: adversary-not-in-scope`.
+- **"PLC4X is reachable from the internet."** PLC4X does not bind
+ listening sockets; "reachable" means the embedding application is
+ reachable. `OUT-OF-MODEL: adversary-not-in-scope`.
+- **"PLC4X writes to a Modbus coil that controls a physical relief
+ valve."** The integrator authorized the API call; the physical
+ consequence is integrator-side. → §3 item 7. `OUT-OF-MODEL:
+ trusted-input`.
+
+## §12 Conditions that would change this model
+
+Revise this document when any of the following lands:
+
+- A new protocol driver gains a built-in transport-security layer
+ (e.g. Modbus-Security, OPC UA Pub-Sub with Security, KNX Secure,
+ BACnet/SC). Changes §3 item 1, §9, §11a per-protocol.
+- A new transport gains a listening-socket capability (would convert
+ PLC4X from a pure client to a client/server).
+- A change in the default value of any §5a knob — especially OPC UA
+ `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.
+- The boundary with `plc4x-extras` shifts (e.g. an integration is
+ brought back into the main repo, or vice versa).
+- A vulnerability report that cannot be cleanly routed to one of the
+ §13 dispositions: that is evidence the model has a gap.
+
+## §13 Triage dispositions
+
+A report against PLC4X receives exactly one of the following.
+
+| Disposition | Meaning | Licensed by |
+| --- | --- | --- |
+| `VALID` | Violates a §8 property via an in-scope §7 adversary using an
in-scope §6 input — typically a wire-parser memory-safety break (P4), an OPC UA
secure-channel break (P1/P2/P7), or KNX-XML XXE bypass (P6). | §8, §6, §7 |
+| `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 |
+| `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 |
+| `MODEL-GAP` | Cannot be cleanly routed to any of the above — triggers §12
model revision. | §12 |
+
+## §14 Open questions for the maintainers
+
+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
+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,
+and bounds per-connection allocation (Q8/Q11/Q18/Q19). Answers folded
+below and into the body as *(maintainer)*.
+
+### Wave 1 — scope, intended use, the OPC UA defaults
+
+**Q1.** Confirm that "remote PLC bytes on the wire are untrusted input
+crossing into the parser" is the right framing for §2 / §4 — i.e.,
+the driver's job is to be memory-safe and bounded-resource against any
+byte sequence a remote PLC (or attacker on the OT net) could send,
+even though the protocol itself provides no authentication.
+*(maps to §2, §4 B2, §7, §8 P4)*
+
+**Q2.** Confirm the unsupported-component list: `plc4j/utils/`,
+`plc4j/drivers/{simulated,mock}`, `code-generation/`, `tools/`,
+`images/`, `media/`, `website/`, `licenses/`, top-level
+`Dockerfile`/`docker-compose.yaml`/`Jenkinsfile`. Anything to add or
+remove? *(maps to §3 item 6)*
+
+**Q3.** Confirm the per-protocol enumeration of "no auth / no
+integrity / no confidentiality" in §3 item 1 and §11a. The
+proposed list: Modbus (all variants), S7 (PUT/GET), BACnet/IP
+(classic), IEC-60870-5-104, Profinet (the variant implemented),
+CANopen, KNXnet/IP (classic), C-Bus, DF1, AB-Ethernet, EtherNet/IP
+(classic CIP), UMAS, ADS/AMS payload, Open-Protocol, Firmata. Have we
+missed any, or wrongly tarred one that does have some authentication
+the driver enforces? *(maps to §3 item 1, §11a)*
+
+**Q4.** Confirm "the embedding application is trusted, end users of
+the embedding application are out of scope" *(proposed: yes)*. Are
+there any embedded-product / OEM contexts where PLC4X considers the
+embedding application's end users in scope? *(maps to §3 items 3, 4)*
+
+**Q5.** Tag-address strings: confirm the default stance is "trusted
+caller input — embedding application owns vetting". Is there a
+PLC4X-side memory-safety / hang / unbounded-allocation guarantee
+on tag-address strings? Proposed: `VALID-HARDENING` for parser
+robustness, `VALID` only if the embedding application's documented
+contract includes user-supplied strings. *(maps to §3 item 5, §8 P5)*
+
+**Q6.** Confirm out-of-scope status of PLC-device firmware bugs and
+physical-process consequences. *(maps to §3 item 7)*
+
+**Q7.** OT-network perimeter as integrator responsibility — confirm
+the §10 item 1 framing. *(maps to §3 item 8, §10)*
+
+### Wave 2 — OPC UA defaults (highest-priority maintainer ruling)
+
+**Q14.** OPC UA `security-policy=NONE` default. Is "OPC UA used with
+`security-policy=NONE` in production" a `VALID` report against PLC4X
+(stance (a) — default is supported posture) or `OUT-OF-MODEL:
+non-default-build` (stance (b) — operator must flip per §10 item 3)?
+**Proposed: (b)** — the default is convenience for dev/lab. If (a),
+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
+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)*
+
+**Q15.** OPC UA default `PermissiveCertificateVerifier` — the
+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
+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
+secure-by-default), not `OUT-OF-MODEL: non-default-build`. *(maps to §5a, §10
item 3, §11a, §13)*
+
+**Q16.** OPC UA `discovery=true` over `security-policy=NONE`. The
+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
+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)*
+
+### Wave 3 — wire-parser robustness, the most likely site of real findings
+
+**Q11.** Confirm §8 P4 (memory safety, bounded allocation, bounded
+loop / recursion in per-driver wire parsers on adversarial wire bytes)
+is a property PLC4X has actually committed to — i.e., a Modbus parser
+that allocates `O(N)` memory in response to a 1-byte length field with
+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
+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)*
+
+**Q17.** OPC UA secure-channel implementation correctness — §8 P1 /
+P2 / P7. Are these properties tested against the cited reference
+servers (Eclipse Milo, OPC Foundation .NET) on every release, or
+only manually at version-bump time? Are there OPC UA test vectors
+(spec-published handshake traces) the driver is regressed against?
+*(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.
+
+**Answered (maintainer — chrisdutz):** 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;
+the embedding application is responsible. (SPI3's per-connection
+ring-buffer additionally bounds per-connection allocation.) *(maps to §6, §9,
§10 item 8)*
+
+**Q20.** Side-channel posture for OPC UA crypto: out of scope
+*(proposed)*. *(maps to §7, §9)*
+
+**Q21.** Compromised PLC firmware sending crafted but
+spec-compliant responses — confirm the split: wrong **measurement
+values** are `OUT-OF-MODEL: trusted-input` (the device is the source
+of truth), but wrong **wire framing** is in-model for parser
+robustness. *(maps to §3 item 7, §7, §8 P4)*
+
+**Q22.** §8 P3 (Java/Go/C-binding API memory safety): is the right
+framing "Java public API is memory-safe by virtue of being Java,
+unless a JNI path is reached", or do you make a stronger claim?
+*(maps to §8 P3)*
+
+### Wave 4 — environment, false-friends, edges
+
+**Q8.** External tunneling (VPN / IPSec / TLS-wrap) is integrator's
+responsibility — confirm §9 / §10 item 2. Is there appetite for a
+"TLS-wrap transport" that would terminate at PLC4X (e.g.
+Modbus-over-TLS per Schneider Electric's draft spec)? If yes, that
+changes §12.
+
+**Answered (maintainer — chrisdutz):** 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)*
+
+**Q9.** ADS authentication: when `PlcUsernamePasswordAuthentication`
+is supplied, the credential drives an AMS-route registration
+*(`AdsProtocolLogic` lines 130–145)*. Confirm that this is forwarded
+in whatever transport mode TwinCAT runs (typically cleartext); PLC4X
+does not encrypt it. *(maps to §4 B2-ADS, §5a)*
+
+**Q10.** `raw-socket` transport requires `CAP_NET_RAW` — confirm
+this is the only transport with elevated host-privilege needs. Any
+others? *(maps to §3, §10 item 9)*
+
+**Q12.** Thread-safety of `PlcConnection`: confirm the documented
+contract (proposed: connections are not safe for concurrent use by
+multiple threads without the embedding application's own
+synchronization; the `CompletableFuture` model returns to the
+caller's thread). *(maps to §5)*
+
+**Q13.** Confirm the "what PLC4X does NOT do to its host" inventory
+in §5: no signal handlers (besides Netty / JVM), no child-process
+spawn at runtime, no listening sockets, no security-sensitive env-var
+reads, no global-state mutation. Any exceptions? *(maps to §5)*
+
+**Q23.** S7 PUT/GET vs S7 protected modes: the docs reference
+"S7-Plus" as a future target. The current driver speaks PUT/GET
+exclusively *(documented: `website/.../protocols/s7.adoc`)*. Confirm
+that "S7 password protection on the PLC" (the device-side `PG` /
+`OS` access password) is **out-of-band** to the driver — the driver
+will speak PUT/GET to whatever device responds. *(maps to §3 item 1,
+§9, §11a)*
+
+**Q24.** KNX Secure: is the KNX driver implementing KNX Secure (the
+cryptographic overlay on KNXnet/IP), or only classic KNXnet/IP?
+Proposed: only classic. *(maps to §11a)*
+
+**Q25.** OPC UA legacy policies (`Basic128Rsa15`, `Basic256`):
+deprecated by the OPC Foundation but supported here for PLC-firmware
+compat. Confirm the stance: supported for connectivity reasons,
+operator chooses `Basic256Sha256`-and-up per §10 item 3.
+*(maps to §5a, §10 item 3, §11a)*
+
+**Q26.** Vendored / dependency CVEs (Netty, Jackson, Eclipse Milo
+crypto, commons-*) — confirm the policy is "report upstream, PLC4X
+picks up the fix via the next dependency bump". *(maps to §3 item 6,
+§11a)*
+
+### Wave 5 — meta
+
+**Q27.** Should this document live at `docs/threat-model.md` /
+`website/asciidoc/modules/users/pages/threat-model.adoc`, or somewhere
+else? *(meta)*
+
+**Q28.** Is there an existing threat-model document (Confluence wiki,
+internal note) that this should reconcile against rather than
+supersede? The website `security.adoc` page is process-only (where
+to report); the `developers/maturity.adoc` page references the
+security process but has no threat-model content. *(meta — §3.1a
+of the rubric)*
+
+**Q29.** `plc4x-extras` boundary: confirm that this document is
+strictly for the main `apache/plc4x` repo and that a separate model
+will be produced for `plc4x-extras` when that repo is brought into
+the program. *(maps to §1, §3 item 2)*
+
+**Q30.** §11a is the highest-leverage section for automated triage
+and is heavily protocol-disclaimer-shaped here. Could the PMC
+contribute 3–5 patterns observed in inbound reports / mailing-list
+threads that recur — e.g. "scanner reports unauthenticated Modbus,
+we close every time"; "scanner reports plaintext password in
+OPC-UA-over-`security-policy=NONE`, we close every time"? Concrete
+recurring false-positives strengthen the suppression list. *(meta —
+§11a)*
+
+**Q31.** What kind of change to PLC4X should trigger a revision
+(proposed list in §12 — confirm or correct)? *(meta, §12)*
+
+---
+
+## Appendix: Existing security-policy artefact → §x back-map
+
+PLC4X does not currently ship an in-repo `SECURITY.md`. The de facto
+security-policy artefacts are:
+
+- `website/asciidoc/modules/users/pages/security.adoc` — the **process-only**
+ page, which says (verbatim): "For more information about reporting
+ vulnerabilities, see the Apache Security Team page. … No vulnerabilities
+ have been reported." No threat-model content; everything maps to the §1
+ reporting cross-reference.
+- `website/asciidoc/modules/developers/pages/maturity.adoc` — restates the
+ process posture (QU20, QU30); no threat-model content.
+- Per-protocol pages under
+ `website/asciidoc/modules/users/pages/protocols/*.adoc` — these are the
+ 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 |