# PLC / SPS Projects (CODESYS)

Prüfwerk analyzes industrial control logic (IEC 61131-3) for PLC/SPS targets such
as CODESYS projects. A single PLC/SPS target is treated as a **composite device**:
the control application *and* the device it runs on.

| What you provide | What Prüfwerk does |
| --- | --- |
| PLC project (PLCopen XML / ST, or a `.projectarchive`) | **Control-logic SAST** — semantic security rules over ST **and** graphical FBD/LD |
| A `.projectarchive` | **Control-app SBOM** — the referenced CODESYS libraries + the runtime version, matched against known CVEs |
| A device firmware image | Firmware SBOM / CVE (opt-in) |
| A reachable endpoint (WebVisu, OPC UA) | DAST / pentest (opt-in) |

## Anatomy: a soft PLC is a SoC + Linux + runtime

A CODESYS controller is **not** a monolithic appliance like a classic Siemens
S7. It is **PC-based ("soft") control** — commodity silicon running a
general-purpose Linux, with a **software PLC runtime** as just another process:

| Classic PLC (e.g. Siemens S7) | Soft PLC (CODESYS-on-Yocto, OpenPLC-on-Raspbian) |
| --- | --- |
| Proprietary hardware + firmware | Commodity SoC (x86 / ARM) |
| Proprietary OS | General-purpose Linux (a **Yocto** image, or Raspbian) |
| Proprietary runtime | Software runtime (**CODESYS Control**, or OpenPLC) |
| STEP7 / TIA project | IEC 61131-3 control app (ST / LD / FBD / SFC) |

Because of this, the device is built along **two independent tracks**, by
different people, on different timelines, and shipped separately. It also
inherits the **entire Linux / IT attack surface on top of** the OT / control
one — which is exactly why a PLC/SPS target is treated as a **composite**:
Prüfwerk ingests one artifact per layer and scans each with the right pipeline.

```mermaid
flowchart TB
    subgraph TA["Track A · Device platform — built by the hardware OEM / vendor"]
        direction LR
        A1["Yocto / OpenEmbedded<br/>BSP + RT kernel"] --> A2["Bake in the CODESYS<br/>Control for Linux runtime"] --> A3["bitbake → device image<br/>.wic / .tar + manifest"]
    end
    subgraph TB2["Track B · Control application — built by the machine builder / customer"]
        direction LR
        B1["CODESYS IDE<br/>ST / LD / FBD / SFC + WebVisu"] --> B2["Reference CODESYS +<br/>vendor libraries"] --> B3["Compile → download<br/>to device (gateway 11740)"]
    end

    A3 --> DEV(["Running soft-PLC device<br/>SoC + Linux + runtime + control app<br/>Modbus · OPC UA · EtherNet/IP · WebVisu"])
    B3 --> DEV

    subgraph CERT["What Prüfwerk scans — one layer per artifact"]
        direction LR
        S1["Firmware layer<br/>FirmwareStatic · SBOM · CVE"]
        S2["Control-logic layer<br/>PLC SAST — ST + FBD/LD"]
        S3["Control-app SBOM<br/>libraries + runtime → CVE"]
        S4["Running layer<br/>ICS probe · DAST (WebVisu)"]
    end

    A3 -. firmware image .-> S1
    B1 -. PLCopen XML / ST via git .-> S2
    B2 -. projectarchive (zip) .-> S3
    DEV -. live URL / provisioned .-> S4

    classDef yocto fill:#fde68a,stroke:#b45309,color:#111
    classDef codesys fill:#bfdbfe,stroke:#1d4ed8,color:#111
    classDef dev fill:#e9d5ff,stroke:#7e22ce,color:#111
    classDef cert fill:#bbf7d0,stroke:#15803d,color:#111
    class A1,A2,A3 yocto
    class B1,B2,B3 codesys
    class DEV dev
    class S1,S2,S3,S4 cert
```

::: tip Where Yocto fits
Yocto is **Track A** — the *build system* for the device platform. It produces
the Linux image and bakes in the CODESYS runtime, so it is the **firmware
layer**, entirely separate from the control application. Hand it to Prüfwerk as
its own **firmware image** artifact (scanned by the firmware pipeline, not the
PLC pipeline). The device OS need not be Yocto — Raspbian/Debian/Buildroot, or
even an RTOS / bare-metal, are all possible — but Yocto is the common,
product-grade industrial choice.
:::

## Two ways to deliver the project

You can either **upload** the project when onboarding, or point Prüfwerk at a
**git repository** (recommended — every scan is just a `git pull`, no re-upload).

### Option A — Upload

On the onboarding wizard, choose target type **PLC / SPS**, then attach a **PLC
project** artifact and pick its format:

- **PLCopen XML** (`.xml`) — export from CODESYS via *Project → Export PLCopenXML*.
- **Structured Text** (`.st`) — a raw ST file.
- **Project archive** (`.projectarchive`) — *File → Project Archive → Save/Send
  Archive…* with **"Referenced libraries"** ticked. This is the only form that
  also yields the **library + runtime SBOM**.

### Option B — Git repository (recommended)

Attach a **Git repository** artifact to the PLC/SPS target. Prüfwerk clones it and
runs the control-logic scan over the exported source in the repo.

## Best-case git repository layout

Because the binary `.project` does not diff or merge in git, commit **textual
exports** for review-friendly SAST, and include the **`.projectarchive`** so the
library/runtime SBOM is available too:

```text
my-plc-project/
├── src/
│   ├── PLC_PRG.xml          # PLCopen XML export (ST or FBD/LD) — one per POU
│   ├── PumpController.xml
│   ├── SafetyInterlock.xml
│   └── GVL.xml              # global variable lists, also as PLCopen XML
├── PumpStation.projectarchive   # optional but recommended → library + runtime SBOM
└── README.md
```

**Guidelines**

- **Export to PLCopen XML** (`Project → Export PLCopenXML`), one file per POU, and
  commit those. ST, **and graphical FBD/LD**, are both analyzed.
- Alternatively commit raw `.st` / `.exp` / `.scl` files — also analyzed.
- **Do not** commit only the binary `.project` — it cannot be parsed (and does not
  diff). If you want the library SBOM, commit the **`.projectarchive`** as well.
- CODESYS's built-in Git integration, which stores an exported representation,
  works too — as long as the committed form is PLCopen XML / textual.

::: tip What unlocks what
- **Control-logic SAST** needs textual source in the repo (PLCopen XML or `.st`).
- **Library + runtime SBOM** needs a **`.projectarchive`** — PLCopen XML exports do
  **not** carry the referenced libraries.
:::

## What the scanner finds

The control-logic rules are CWE-mapped and include: hardcoded credentials
(CWE-798), default/weak passwords (CWE-1393), safety interlock / watchdog bypass
(CWE-1384), unchecked array indexing (CWE-129), division-by-zero (CWE-369,
guard-aware), cleartext/insecure communication (CWE-319), insecure protocol ports
(CWE-319, e.g. Modbus 502, FTP 21, Telnet 23), and unstructured jumps (CWE-691).

The **SBOM** view lists the CODESYS libraries (`pkg:codesys/<name>@<version>`) and
the runtime; matching runtime components (e.g. the `Cmp*` / `3SLicense` libraries)
surface real CODESYS advisories as CVE alerts.

## Dynamic testing — ICS protocol probe

Beyond the static analysis, Prüfwerk can **probe the running device** over
industrial protocols. Attach a **Live URL** artifact (the device host / WebVisu
URL) to the PLC/SPS target and enable the **ICS Probe** scan.

The probe is **read-only** — it never writes to the live process. It currently
speaks **Modbus/TCP** (port 502): it confirms whether the device answers
unauthenticated Modbus requests and reads its device identity (vendor / product /
revision). Because Modbus/TCP has no authentication or encryption in the protocol,
a reachable endpoint that answers is reported as an exposed control interface
(CWE-306). OPC UA and EtherNet/IP probes are planned.

::: warning
The ICS probe connects to the live device. It is **opt-in** (off by default) and
should only be run against targets you are authorized to test. It performs reads
only, never writes.
:::
