Skip to content

Security Scanning

DeltaFi runs vulnerability and misconfiguration scans against built images, source-tree dependencies, infrastructure-as-code, and the source tree itself. All scans are exposed as Gradle tasks under the Security tasks group and aggregated by a single root task — securityScan — that applies per-tool severity gates and emits a markdown + JSON summary report.

Running scans

The Security tasks group is visible from ./gradlew tasks:

TaskScopeTool
:<project>:grypeOne project's Docker imagegrype
:<project>:trivyOne project's Docker imagetrivy
:grypeReportAll built images, combined summarygrype
:trivyReportAll built images, combined summarytrivy
:grypeFullReportEvery installed DeltaFi image (after install)grype
:trivyFullReportEvery installed DeltaFi image (after install)trivy
:scanSourceRepo source tree (declared dependencies)trivy fs
:scanConfigDockerfiles, Helm charts, k8s manifestsCheckov
:scanSecretsRepo source tree (hardcoded secrets)trivy
:scanReferencedImagesExternally-referenced container images (e.g. deltafi/* from the TUI orchestrator)trivy
:<project>:sbomOne project's Docker imagesyft
:collectSbomsEvery docker-producing projectsyft
:securityScanAggregates everything, applies the gate

Common entry points:

bash
./gradlew securityScan                # full sweep + gate (fails the build on findings)
./gradlew securityScan -PfailOn=NONE  # full sweep, never fail (report-only mode)
./gradlew :deltafi-core:grype         # quick grype scan of one image
./gradlew scanSecrets                 # repo-wide secret scan

The aggregated summary is written to build/reports/security/summary.md (human-friendly) and summary.json (machine-readable).

Configuring the gate

The securityScan extension at the project root controls the per-tool severity threshold, the ignoreUnfixed policy, and where suppressions/reports live. Defaults are wired into the security-scan precompiled script plugin; override in the root build.gradle only when needed:

groovy
securityScan {
    failOn {
        trivyImage  = 'HIGH'      // image CVEs
        grypeImage  = 'CRITICAL'  // grype is noisier; tighter threshold
        trivyFs     = 'HIGH'      // source-dep CVEs
        checkov     = 'HIGH'      // IaC/Dockerfile/Helm/k8s misconfig
        trivySecret = 'LOW'       // any secret fails
    }
    ignoreUnfixed = true          // exclude CVEs without an upstream fix from the gate (still reported)
    sarif = false                 // when true, every scan also writes a SARIF file alongside the JSON (~doubles scan time)
    suppressionsDir = file('.security')
    reportDir = layout.buildDirectory.dir('reports/security').get().asFile
    sbomDir = layout.buildDirectory.dir('sboms').get().asFile
    // Auto-populated from tui/internal/orchestration/orchestrator.go when present.
    // Override or extend to scan additional externally-pulled images.
    referencedImages = [
        'deltafi/grafana:12.4.5-0',
        'deltafi/timescaledb:2.19.3-pg16-10',
        // ...
    ]
}

The -PfailOn=<SEVERITY|NONE> CLI override applies to every tool simultaneously for one-off runs (CI dry runs, local triage). It bypasses the per-tool config without editing the build.

Gate semantics

  • failOn = 'HIGH' fails the build on HIGH and CRITICAL findings. Lowest-severity-that-fails, not exact-match.
  • failOn = 'NONE' never fails — the report is still produced.
  • Findings filtered out by .security/ suppression files are excluded before the gate evaluates.
  • ignoreUnfixed = true excludes vulnerabilities with no available fix from gate evaluation — they still appear in the summary so they're visible. Config and secret findings always participate (there is no "fix version" concept).

Suppressing findings (.security/)

Place suppression files in the .security/ directory at the repo root. Each tool reads its native format; the build wires the right --config/--ignorefile flag for you.

.security/
├── trivyignore     # trivy: one CVE/secret ID per line, '#' for comments
├── grype.yaml      # grype: ignore list, see grype --help for full syntax
└── checkov.yaml    # checkov: --config-file with skip-check / skip-path / etc.

Examples:

.security/trivyignore:

# Pinned for $REASON, tracked in $TICKET
CVE-2024-12345

.security/grype.yaml:

yaml
ignore:
  - vulnerability: CVE-2024-12345
  - vulnerability: CVE-2024-67890
    package:
      name: some-package

A suppression entry should always have an inline justification (ticket, expiration date, or reason) so the next reviewer knows whether it's still warranted.

Downstream plugins

External DeltaFi plugins using the published org.deltafi.plugin-convention automatically inherit:

  • org.deltafi.docker-scangrype and trivy tasks against the plugin's image
  • org.deltafi.security-scansecurityScan aggregator + gate + summary report

Plugin authors configure securityScan { failOn { ... } } in their own build.gradle and drop suppression files into their own .security/ directory. The source/config/secret scans (scanSource, scanConfig, scanSecrets) are monorepo-only.

Output files

The aggregator writes:

  • build/reports/security/summary.md — Markdown report with the gate verdict and a per-scan finding table
  • build/reports/security/summary.json — Same data as JSON for CI ingestion

Each per-scan task also writes its raw output under build/:

  • build/grype-results/<project>-grype-results.json
  • build/trivy-results/<project>-trivy-results.json
  • build/scan-results/source/trivy-fs.json
  • build/scan-results/config/checkov.json
  • build/scan-results/secrets/trivy-secrets.json
  • build/trivy-results/referenced-<image>-trivy-results.json — one per externally-referenced image scanned by scanReferencedImages
  • build/sboms/<project>.cdx.json — CycloneDX SBOM (from sbom/collectSboms)

When securityScan { sarif = true } is set, each scan also writes a .sarif file alongside its .json output in the same directory. SARIF is consumable by GitHub Code Scanning, IDE plugins (e.g. SARIF Viewer for IntelliJ/VS Code), and most security CI dashboards.

Tool fallback

If a scan tool isn't on PATH locally, tasks pull a Docker image and run the tool from there transparently — no manual install required. Defaults are the upstream tags:

ToolNative binaryDefault container fallback
grypegrypeanchore/grype:latest
trivytrivyaquasec/trivy:latest
syftsyftanchore/syft:latest
checkovcheckovbridgecrew/checkov:latest

Override any entry to pin to a specific version or point at a mirrored image (e.g. for offline / air-gapped builds, or to lock CI on a specific scanner version):

groovy
securityScan {
    toolImages.grype = 'deltafi/grype:0.71.0-1'
    toolImages.trivy = 'deltafi/trivy:0.71.0-1'
}

Plugin authors override per-project in their own build.gradle. Installing the native binary is faster (no per-run docker pull) but otherwise equivalent.

Adding a new image to coverage

securityScan, grypeReport, and trivyReport auto-discover any subproject that applies one of the docker convention plugins (docker-java or docker-plain). Apply the right plugin and the project's image will be included in the next scan run.

Troubleshooting

  • null executable error: Update to the current gradle-plugin version — older builds shipped a bug where ToolRunner.getExecutable() resolved against the Gradle ExecSpec delegate. Fixed by capturing the value into a local before the exec closure.
  • Trivy "context canceled" / "FATAL" during scanSource: A subdirectory got into trouble during analysis. Add it to securityScan { skipDirs += 'subdir-name' }. The default skip list already includes build, .gradle, node_modules, m2, .git, dist, out, .vitepress.
  • scanConfig reports many findings: Checkov pre-renders Helm charts before scanning, which keeps the misconfig count much lower than per-template scanners. Triage recurring rule IDs via .security/checkov.yaml (skip-check) rather than narrowing configPaths. If checkov is not on PATH the task falls back to the bridgecrew/checkov:latest image automatically.

Contact US