Files
compliance-scanner-agent/examples/plc-demo/pump_station.st
T
Sharang ParnerkarandClaude Opus 4.8 fcd49ecdf7
CI / Check (pull_request) Failing after 2m31s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
feat(pipeline): PLC/SPS control-logic security scanner (IEC 61131-3)
Implements ScanType::PlcControlLogic — the missing piece for PlcSps targets,
which previously classified but ran no scan.

New `pipeline::plc`:
- A real IEC 61131-3 Structured Text front end: lexer + recursive-descent parser
  → AST (POUs, typed VAR sections, statements, expressions). Tolerant recovery so
  odd constructs never sink a file.
- PLCopen XML extractor: pulls each ST POU's interface vars + `<ST>` body and
  reconstructs equivalent ST, so raw `.st` files and PLCopen projects share one
  analysis path.
- Eight semantic, guard-aware rules over the AST → findings: hardcoded
  credentials, default/weak passwords, safety-interlock/watchdog bypass, array
  indexed by unvalidated input, division without a zero-guard (suppressed when an
  enclosing `IF <d> <> 0` proves it), insecure comm (auth/encryption disabled),
  and cleartext OT protocol ports, plus unstructured JMP. Each carries CWE +
  remediation.
- `PlcControlLogicScanner` (Scanner impl) walks the project tree and emits
  `Finding`s (dedup fingerprint, file, line, severity).

Wired into `run_target_pipeline`: when the scan plan includes PlcControlLogic,
`run_plc_scan` ingests the PlcProject artifact, analyzes it, and persists the
findings (findings_count handled by run_target).

Demo fixtures under examples/plc-demo/ (a vulnerable pump-station `.st` + a
PLCopen `conveyor.xml`). Tests: parser, all-rules-fire, guarded-clean-is-quiet,
and an end-to-end tree scan — 5 passing.

Adds `roxmltree` (read-only XML) for PLCopen parsing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:52:42 +02:00

58 lines
1.7 KiB
Smalltalk

(*
* Demo PLC program pump-station control (IEC 61131-3 Structured Text).
*
* Deliberately vulnerable, for the compliance-scanner PLC control-logic demo.
* Each issue below is flagged by pipeline::plc::rules.
*)
FUNCTION_BLOCK PumpStationCtrl
VAR_INPUT
OperatorCmd : INT; (* HMI command index untrusted *)
FlowSetpoint : REAL;
END_VAR
VAR_OUTPUT
PumpSpeed : REAL;
Fault : BOOL;
END_VAR
VAR
HmiPassword : STRING := 'admin123'; (* hardcoded + default credential *)
ApiKey : STRING := 'sk_live_9c1f2a'; (* hardcoded secret *)
PumpProfiles : ARRAY[0..7] OF REAL;
Safety_Enable : BOOL := TRUE;
Watchdog_Kick : INT := 1;
MeasuredFlow : REAL;
ScaleFactor : REAL;
i : INT;
END_VAR
(* Operator can index the profile table with an unvalidated command. *)
PumpSpeed := PumpProfiles[OperatorCmd];
(* Divisor is a live process value that can read zero on a stopped line. *)
ScaleFactor := FlowSetpoint / MeasuredFlow;
(* Safety interlock disabled straight from application logic. *)
IF OperatorCmd = 99 THEN
Safety_Enable := FALSE;
Watchdog_Kick := 0;
END_IF;
(* Unauthenticated Modbus/TCP link on the cleartext OT port. *)
Modbus_TCP_Connect(IP := '10.10.5.20', PORT := 502, AUTH := FALSE, PASSWORD := 'plc');
(* Unstructured jump around the fault handler. *)
IF MeasuredFlow > 1000.0 THEN
JMP trip;
END_IF;
(* A correctly guarded division must NOT be flagged. *)
IF ScaleFactor <> 0.0 THEN
PumpSpeed := PumpSpeed / ScaleFactor;
END_IF;
RETURN;
trip:
Fault := TRUE;
PumpSpeed := 0.0;
END_FUNCTION_BLOCK