fix(matrix): DAST needs an http(s) endpoint; don't offer/run it on modbus://
CI / Check (pull_request) Successful in 6m2s
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

Demo C surfaced this: a PlcSps target whose only live URL is an industrial
endpoint (modbus://plc-sim:502) still marked DAST applicable, so it could be
enabled and then failed at reconnaissance — DAST is an HTTP crawler and the
endpoint speaks raw Modbus.

- Add ArtifactRequirement::HttpUrl (a live URL with an http(s) scheme) and
  point the three DAST rules (WebApp/BackendService, EmbeddedLinuxYocto,
  PlcSps) at it. The ICS probe keeps RunningUrl — it works off host:port of
  any scheme. A modbus:// endpoint now blocks DAST with a clear reason
  ("no http(s) live URL — DAST needs a web endpoint") so the wizard no longer
  offers it as an opt-in.
- Gate the PLC-path DAST trigger on plan.has(Dast), not just plc||ics, so a
  DAST target left over from an earlier run can't re-trigger against a
  non-web device.

Tests: PlcSps + modbus:// offers ICS probe but blocks DAST; PlcSps + http
WebVisu offers both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-07-16 23:22:17 +02:00
co-authored by Claude Opus 4.8
parent cd65fa345c
commit 7bba3518d1
2 changed files with 81 additions and 11 deletions
+11 -5
View File
@@ -409,11 +409,17 @@ impl PipelineOrchestrator {
new_count += self.run_ics_probe(target, &target_id, scan_run_id).await?;
}
if plc || ics {
// PLC/SPS device: also DAST against a WebVisu / exposed endpoint. The
// control-logic scan already consumed the code artifact, so the SAST
// pipeline is not re-run.
self.update_phase(scan_run_id, "dast_scanning").await;
self.maybe_trigger_dast(&target_id, scan_run_id).await;
// PLC/SPS device: also DAST against a WebVisu / exposed endpoint, but
// only when DAST is actually planned — a device reachable only over an
// industrial protocol (e.g. modbus://) has no web surface to crawl, and
// running DAST there just fails at reconnaissance. Gating here (not only
// at provisioning) also stops a DAST target left over from an earlier
// run from re-triggering. The control-logic scan already consumed the
// code artifact, so the SAST pipeline is not re-run.
if plan.has(ScanType::Dast) {
self.update_phase(scan_run_id, "dast_scanning").await;
self.maybe_trigger_dast(&target_id, scan_run_id).await;
}
return Ok(new_count);
}