Skip to content

Security Findings: Who Does What

Several tools touch vulnerability and secret findings. Each one has one job, so a new scanner, dashboard or alert has an obvious place to land and nothing is counted, stored or announced twice.

flowchart LR
    subgraph produce["Produce"]
        ci["CI scan / secret-scan<br/>(default branch)"]
        rt["Runtime scan<br/>(every running image)"]
        kb["kube-bench<br/>(CIS, per node)"]
        dast["DAST: ZAP, Nuclei<br/>(CI image, live edge)"]
    end
    glue["Windmill<br/>(stateless glue)"]
    dd[("DefectDojo<br/>the record")]
    vm["VictoriaMetrics<br/>numbers"]
    graf["Grafana<br/>supply-chain dashboard"]
    am["Alertmanager<br/>record integrity"]
    bs["Backstage<br/>per-entity card"]
    ci --> glue
    rt --> glue
    kb --> glue
    dast --> glue
    glue -->|re-import| dd
    dd -->|counts| glue
    glue -->|metrics| vm
    vm --> graf
    vm --> am
    graf -->|drill-down| dd
    bs -->|read-only proxy| dd
Tool Its job Not its job
CI scan / secret-scan jobs Produce Grype and gitleaks reports for every image the estate builds Deciding what is accepted
Runtime scan (f/security/pod_image_cve_scan) Scan every third-party image the cluster runs, a rotating batch four times a day Images the estate builds (their CI scan is the record) and platform images
kube-bench Produce the CIS benchmark report of each node —
DAST: CI dast-baseline job (ci-templates /dast.yml) ZAP's passive baseline against the image the pipeline just built, run as a service The edge in front of the app (proxy, WAF, ingress headers)
DAST: scheduled edge scan (fleet dast/, daily) ZAP's passive baseline and Nuclei's TLS checks against each live public host, through the ingress, the WAF and the SSO Active attacks: those target the Services behind the WAF
DefectDojo The record: deduplication, lifecycle (open → mitigated), risk acceptances with an owner and an expiry, SLA dates, KEV flags Paging anyone
Windmill Stateless glue: re-imports every report (f/security/defectdojo_grype_sync, daily) and reads the record back as numbers (f/security/defectdojo_metrics, hourly) Holding state of its own
VictoriaMetrics The numbers, written only by defectdojo_metrics A second copy of the findings
Grafana (Security / Supply chain) The estate-wide view; every DefectDojo panel links into the record Triage
Alertmanager Record integrity only: a feed went stale, the metrics cannot be read, an automatic risk acceptance was not renewed, a human risk-acceptance decision is due "A CVE exists" — that is news, not an outage
Pushover digest (grype curator) CVE news: one daily message with what is new and fixable —
Backstage The per-entity lens: what the entity's CI scan found, and what the images its pods run carry, with links into DefectDojo and Grafana Its own copy of the numbers
OpenObserve Events (logs, traces) Findings
GitLab Code, CI, merge requests; every import carries the commit, ref and job that produced it Tracking the findings

The rules behind the map

  • The default branch is the record. The CI feeds and both ignore-list curators read only pipelines of the project's default branch. A newer scan of an unmerged merge request never replaces it, so the record describes what ships and does not flap between a branch and main.
  • One naming rule for running images. The runtime scan records each image under its repository (product type mdapi-runtime). The Backstage catalog sync applies the same rule to the pods of each entity's namespace and writes the result into the defectdojo.org/runtime-products annotation, so the card names exactly the products the scan records.
  • Won't-fix lives in DefectDojo. A finding with no upstream fix is held by a risk acceptance with an expiry, renewed while it stays unfixable; a human decision gets an owner and a due date that Alertmanager watches.
  • Only passive scans cross the WAF. The edge scan runs from inside the cluster, and the WAF alerting trusts internal sources: every request the WAF blocks from there raises a false-positive alert. The edge scan therefore spiders and reads (ZAP baseline) and checks TLS handshakes (Nuclei ssl), which the WAF never blocks. Attack payloads target the Services directly, behind the WAF. The CI scan sees the app alone, the edge scan sees what a visitor meets: each one covers what the other cannot.
  • Every import is traceable. Each test carries commit_hash, branch_tag and build_id (the CI job URL); secret findings link to the exact line of the commit that was scanned.

See Supply Chain Security for the scanners themselves and CIS Kubernetes Benchmark for kube-bench.