test(plc): add realistic OpenPLC-style traffic-light sample
CI / Check (pull_request) Successful in 5m20s
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

Second demo fixture (public-sample shape) to complement the all-rules
pump_station.st: a timed pedestrian-crossing state machine adapted from
the OpenPLC traffic-light example, extended with a SCADA/Modbus uplink and
a maintenance override. Mostly sound control logic with three planted,
field-realistic defects (hardcoded SCADA password, cleartext Modbus master,
maintenance mode that drops the pedestrian safety permit).

The regression test asserts the scanner surfaces those defects while staying
quiet on the guarded duty-cycle division and the JMP-free CASE machine —
demonstrating low false positives on real-world-shaped code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-07-16 10:25:31 +02:00
co-authored by Claude Opus 4.8
parent 5e983d699f
commit f8861419cb
2 changed files with 136 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
(*
* Pedestrian-crossing traffic-light controller.
*
* Structure adapted from the classic OpenPLC "traffic light" example
* (github.com/thiagoralves/OpenPLC_v3 examples) a timed state machine
* driving vehicle + pedestrian lamps, extended with a SCADA/Modbus link
* and a maintenance override so it reads like a real deployed program.
*
* The control logic itself is sound; the security-relevant defects are the
* kind that slip into field code under deadline: a hardcoded SCADA password,
* a cleartext Modbus/TCP master, and a maintenance mode that drops the
* pedestrian safety permit. Everything else should stay quiet.
*)
PROGRAM TrafficLight
VAR
State : INT := 0; (* 0 GreenVeh, 1 Amber, 2 RedVeh/WalkPed, 3 FlashPed *)
Tmr : TON;
StateElapsed : TIME;
CycleMs : DINT := 0;
(* Lamp outputs *)
VehGreen : BOOL := FALSE;
VehAmber : BOOL := FALSE;
VehRed : BOOL := FALSE;
PedWalk : BOOL := FALSE;
PedStop : BOOL := TRUE;
(* Pedestrian safety permit must be TRUE before the WALK phase asserts *)
PedPermit : BOOL := TRUE;
PedButton : BOOL := FALSE;
(* SCADA / remote monitoring *)
ScadaUser : STRING := 'operator';
ScadaPassword : STRING := 'Tr@ffic2019'; (* hardcoded SCADA credential *)
ModbusReady : BOOL := FALSE;
(* Maintenance override *)
MaintMode : BOOL := FALSE;
LampCount : INT := 5;
DutyPct : INT;
END_VAR
(* ---- SCADA uplink: publish state to the control room over Modbus/TCP ---- *)
IF NOT ModbusReady THEN
Modbus_TCP_Master(IP := '10.20.0.5', PORT := 502, AUTH := FALSE, USER := ScadaUser, PASS := ScadaPassword);
ModbusReady := TRUE;
END_IF;
(* ---- Duty-cycle for the flashing pedestrian lamp (guarded division) ---- *)
IF LampCount <> 0 THEN
DutyPct := (CycleMs * 100) / LampCount;
END_IF;
(* ---- Maintenance override: flash amber, hand control to the technician ---- *)
IF MaintMode THEN
VehGreen := FALSE;
VehRed := FALSE;
VehAmber := NOT VehAmber;
PedPermit := FALSE; (* drops the pedestrian safety permit in code *)
PedWalk := FALSE;
PedStop := TRUE;
ELSE
(* ---- Normal timed state machine ---- *)
Tmr(IN := TRUE, PT := T#5s);
StateElapsed := Tmr.ET;
CASE State OF
0: (* vehicles go, pedestrians stop *)
VehGreen := TRUE; VehAmber := FALSE; VehRed := FALSE;
PedWalk := FALSE; PedStop := TRUE;
IF PedButton AND Tmr.Q THEN
State := 1; Tmr(IN := FALSE);
END_IF;
1: (* amber transition *)
VehGreen := FALSE; VehAmber := TRUE;
IF Tmr.Q THEN State := 2; Tmr(IN := FALSE); END_IF;
2: (* vehicles stop, pedestrians walk only if permitted *)
VehAmber := FALSE; VehRed := TRUE;
IF PedPermit THEN
PedWalk := TRUE; PedStop := FALSE;
END_IF;
IF Tmr.Q THEN State := 3; Tmr(IN := FALSE); END_IF;
3: (* flashing don't-walk before returning to green *)
PedWalk := NOT PedWalk;
IF Tmr.Q THEN
State := 0; PedButton := FALSE; Tmr(IN := FALSE);
END_IF;
ELSE
State := 0;
END_CASE;
END_IF;
END_PROGRAM