feat(pipeline): PLC/SPS control-logic security scanner (IEC 61131-3) #162

Merged
sharang merged 3 commits from feat/plc-control-logic-scanner into main 2026-07-16 08:31:44 +00:00
Owner

Implements ScanType::PlcControlLogic — the missing scanner for PlcSps targets (they classified but ran nothing). This is the core of the SPS onboarding demo.

New pipeline::plc

  • Real IEC 61131-3 Structured Text front end — lexer + recursive-descent parser → AST (POUs, typed VAR sections, statements, expressions), tolerant recovery.
  • PLCopen XML extractor — pulls each ST POU's interface vars + <ST> body and reconstructs ST, so .st files and PLCopen projects share one path.
  • 8 semantic, guard-aware rules (CWE + remediation each):
    • hardcoded credential (CWE-798), default/weak password (CWE-1393)
    • safety-interlock / watchdog bypass (CWE-1384)
    • array indexed by unvalidated input (CWE-129)
    • division without a zero-guard (CWE-369) — suppressed when an enclosing IF d <> 0 proves non-zero (the payoff of a real parser vs regex)
    • insecure comm: auth/encryption disabled + cleartext OT ports (CWE-319)
    • unstructured JMP (CWE-691)
  • PlcControlLogicScanner (Scanner impl) → Findings (fingerprint, file, line, severity).

Wiring

run_target_pipeline: when the plan includes PlcControlLogic, run_plc_scan ingests the PlcProject artifact, analyzes, and persists findings.

Fixtures + tests

examples/plc-demo/ — a vulnerable pump-station .st + a PLCopen conveyor.xml. 5 tests: parser, all-rules-fire, guarded-clean-is-quiet, end-to-end tree scan.

Adds roxmltree.

Follow-ups

  • A public OpenPLC/CODESYS sample as a 2nd fixture (in progress).
  • Demo on the deployment: onboard a PlcSps target with a PLC project → scan → findings.

Local: fmt + agent/mcp clippy clean; PLC tests 5/5.

Implements **`ScanType::PlcControlLogic`** — the missing scanner for `PlcSps` targets (they classified but ran nothing). This is the core of the SPS onboarding demo. ## New `pipeline::plc` - **Real IEC 61131-3 Structured Text front end** — lexer + recursive-descent parser → AST (POUs, typed VAR sections, statements, expressions), tolerant recovery. - **PLCopen XML extractor** — pulls each ST POU's interface vars + `<ST>` body and reconstructs ST, so `.st` files and PLCopen projects share one path. - **8 semantic, guard-aware rules** (CWE + remediation each): - hardcoded credential (CWE-798), default/weak password (CWE-1393) - safety-interlock / watchdog bypass (CWE-1384) - array indexed by unvalidated input (CWE-129) - **division without a zero-guard** (CWE-369) — *suppressed* when an enclosing `IF d <> 0` proves non-zero (the payoff of a real parser vs regex) - insecure comm: auth/encryption disabled + cleartext OT ports (CWE-319) - unstructured JMP (CWE-691) - `PlcControlLogicScanner` (Scanner impl) → `Finding`s (fingerprint, file, line, severity). ## Wiring `run_target_pipeline`: when the plan includes `PlcControlLogic`, `run_plc_scan` ingests the `PlcProject` artifact, analyzes, and persists findings. ## Fixtures + tests `examples/plc-demo/` — a vulnerable pump-station `.st` + a PLCopen `conveyor.xml`. **5 tests**: parser, all-rules-fire, guarded-clean-is-quiet, end-to-end tree scan. Adds `roxmltree`. ## Follow-ups - A public OpenPLC/CODESYS sample as a 2nd fixture (in progress). - Demo on the deployment: onboard a PlcSps target with a PLC project → scan → findings. Local: fmt + agent/mcp clippy clean; PLC tests 5/5.
sharang added 1 commit 2026-07-16 07:52:59 +00:00
feat(pipeline): PLC/SPS control-logic security scanner (IEC 61131-3)
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
fcd49ecdf7
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>
sharang added 1 commit 2026-07-16 08:20:57 +00:00
fix(plc): drop redundant watchdog clause in safety-bypass rule
CI / Check (pull_request) Has been cancelled
CI / Detect Changes (pull_request) Has been cancelled
CI / Deploy Agent (pull_request) Has been cancelled
CI / Deploy Dashboard (pull_request) Has been cancelled
CI / Deploy Docs (pull_request) Has been cancelled
CI / Deploy MCP (pull_request) Has been cancelled
5e983d699f
CI clippy (rust 1.94.0, overly_complex_bool_expr) flagged the disabling
check as a logic bug: the `watchdog && matches!(value, Int(0))` term is
fully subsumed by the preceding `matches!(value, Int(0))`. Simplify to
`Bool(false) || Int(0)` — behavior is unchanged (a safety/watchdog signal
driven to FALSE or 0 is still a bypass), and `watchdog` stays used in the
outer guard. All 5 PLC tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sharang added 1 commit 2026-07-16 08:25:54 +00:00
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
f8861419cb
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>
sharang merged commit 6d02b138c6 into main 2026-07-16 08:31:44 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sharang/compliance-scanner-agent#162