The CI check job runs cargo fmt --all --check; the new runtime modules and the
orchestrator wiring needed reformatting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the provision-and-test loop's dynamic coverage (sub-tasks 3 + 4):
- provision_and_test now returns a ProvisionOutcome { ics findings, DAST run }.
After the Modbus probe it runs a bounded, best-effort DAST scan against the
provisioned web endpoint (independently timed out so it can't consume the whole
instance lifetime), and the orchestrator persists the DAST scan run + findings
linked to the scan run. Kept as a plain data return so the whole run is
portable to a remote execution backend. On the OpenPLC substrate the web
endpoint is OpenPLC's own UI (fidelity caveat documented); the CODESYS-runtime
follow-up raises this to a real WebVisu.
- ICS Modbus probe now enumerates the exposed process surface (read-only): a Read
Coils and a Read Holding Registers of the first block. Coils and holding
registers are read/write process points, so an exposed block is an
unauthenticated *write* surface — reported as `ics-modbus-exposed-points`
(High). Read-only to detect (we never write), so it is safe on the live probe
too, not just the provisioned instance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dynamic PLC testing without reaching the customer's device: when a PLC/SPS
target ships control logic but no reachable live URL, instantiate that logic
ourselves on a throwaway OpenPLC container in-cluster, load + start it, probe
the provisioned Modbus endpoint, and tear it down. No customer network access,
sandboxed, and reproducible.
This is the phase-1 foundation of epic #183 (OpenPLC substrate). It covers:
- provision: ephemeral container lifecycle (docker CLI). Resource-capped
(memory/cpus/pids), hardened (no-new-privileges), labelled, joined to the
agent's own network with no host port exposure, and swept by a stale reaper
for anything a crashed run leaks. The `docker` argv is built by pure functions
so it is unit-tested without a daemon.
- openplc: drives the OpenPLC web UI to load a program — login → upload →
save → compile (MatIEC) → start_plc (which opens Modbus/TCP 502).
- runtime::provision_and_test: composes them under a hard deadline with
guaranteed teardown on every path (success / error / timeout), then runs the
existing ICS probe against the provisioned endpoint. extract_program picks the
best loadable program (complete ST > largest ST > PLCopen XML).
- orchestrator: for a PlcSps target with control logic and no live URL, run
provision-and-test after the static PLC scan. Gated by PlcRuntimeConfig
(PLC_RUNTIME_ENABLED, default off — needs Docker access in the agent).
DAST-against-WebVisu and CODESYS-runtime fidelity are follow-ups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each deploy job now builds the per-service image, pushes to the
private registry as :latest and :sha, then triggers an HMAC-signed
orca redeploy webhook. Coolify webhooks are no longer used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>