Skip to content

Configuration reference

Complete reference for the ringzero { } extension added by the tech.ringzero.sca plugin. All properties use Gradle’s lazy Property/ Provider types, so they’re compatible with the configuration cache and can be wired to providers. ringZero { } is an alias for the same extension.

PropertyTypeDefaultDescription
apiKeyProperty<String>Scoped API key (rz_key_…) with sca:upload for uploads; add scans:read only when tooling reads scan status — see Authentication & permissions. Resolution order: this property → RINGZERO_API_KEY env var → ringzero.apiKey Gradle property. See secure provisioning.
apiUrlProperty<String>https://api.ringzero.tech/v1API base URL override. Only change this if RingZero support tells you to (e.g. a regional endpoint). Also settable via RINGZERO_API_URL.
projectProperty<String>name of the project applying the pluginProject id (prj_6h2m) or slug. Pin it explicitly to keep scan history stable across renames. A new slug creates the project on first upload; if the key carries a project restriction, a slug outside that list fails with 422 unknown_project — restrict CI keys to their project so a leaked key can’t create org resources.
suppressionsFileRegularFilePropertyringzero-suppressions.yml in the project applying the pluginThe suppressions file, parsed and uploaded inside every snapshot. The default is used only when the file exists; explicitly setting a path that doesn’t exist is a configuration error.
reportsDirectoryDirectoryPropertythat project’s build/ringzeroWhere the tasks write snapshot.json, scan.json, and the readable module attribution report dependencies.md. Each module has a separate directory by default. summary.json and findings.json arrive with ringzeroCheck.

Module metadata is also configured at the top level:

PropertyTypeDefaultDescription
teamNameProperty<String>unsetTeam label for this module, emitted as modules[].teamName. Whitespace is trimmed; blank values are omitted. No implicit parent or environment fallback.
attributesMapProperty<String, String>empty mapCustom module fields such as service, domain, costCenter, and customValue, emitted as modules[].attributes. Keys must be nonblank and values must be strings. Keys are sorted for deterministic output. No implicit inheritance.

See module teams and custom attributes for Kotlin and Groovy examples and how metadata joins to dependency attribution.

There is no organization setting: the API key is org-scoped, so the server derives the organization from the key alone — nothing to configure, and a key can never write into another org.

Controls what goes into the dependency snapshot.

Each project applying the plugin has independent settings. Aggregate scans use module-local selection and filters for projects that apply the plugin, and the aggregate owner’s settings for projects that do not. Local settings keep their own defaults; see multi-project builds.

PropertyTypeDefaultDescription
includeSubprojectsProperty<Boolean>true on root, false on subprojectsWhether this project’s scan also includes its descendants. This scopes the task; an ancestor’s aggregate still considers each module independently.
configurationsListProperty<String>["runtimeClasspath"]Gradle configurations to resolve. Accepts exact names or globs ("*RuntimeClasspath"), optionally path-qualified per subproject (":benchmark:runtimeClasspath"; unqualified entries apply to every project). A name matching nothing in a given project is skipped silently (not every subproject has every configuration); an entry matching nothing in any project logs a warning.
excludedConfigurationsListProperty<String>[]Configurations to leave out, same name/glob/path-qualification rules as configurations. The selection is configurations minus excludedConfigurationsexclusion always wins. The motivating pattern is variant-heavy builds: configurations = ["*RuntimeClasspath"] with excludedConfigurations = ["debug*", "*AndroidTest*"].
excludedModulesListProperty<String>[]Glob patterns matched against group:name coordinates ("com.acme.internal:*", "*:fastjson"). Matched dependencies are removed from this project’s contribution before upload. Transitives reachable only through an excluded dependency are pruned with it. A shared dependency remains in an aggregate if another project’s filters include it. Use this for out-of-scope artifacts; to silence a known finding on a package you still want inventoried, use a suppression instead.
includeTransitiveProperty<Boolean>trueInclude transitive dependencies. Disabling this is almost never what you want — most vulnerable packages arrive transitively — but it’s available for diagnostics.
manifestsListProperty<String>auto-detectedPaths relative to the root project directory, including in module-local scans. Detected per included project: build.gradle(.kts) and an existing gradle.lockfile. Module overrides replace that module’s detected paths in an aggregate; a nonempty override on the aggregate owner replaces the entire list.
excludeProjectsListProperty<String>[]Exact project paths (e.g. ":samples", ":docs") whose configurations and manifests are omitted from this task’s scope. A module can also exclude its own path to omit itself from an ancestor’s aggregate. Dependencies may still be included through another scanned project’s resolved graph.

Glob syntax (everywhere globs are accepted): * matches any run of characters within a segment. There is no ** or ?.

Policy evaluated by ringzeroCheck after the scan completes. Without a failOn block, ringzeroCheck still waits for completion and prints the summary — it just never fails the build.

PropertyTypeDefaultDescription
severityProperty<String>unset (never fail)Minimum severity that fails the build: "critical", "high", "medium", or "low". A value of "high" fails on high and critical.
cvssProperty<Double>unsetFail on any finding with a CVSS base score at or above this value (0.0–10.0).
fixableOnlyProperty<Boolean>falseOnly findings with a non-null fixedVersion trip the gate — gate on what the team can action today. Unfixable findings are still reported, they just don’t fail the build.
reachableOnlyProperty<Boolean>falseOnly findings marked reachable trip the gate. Fail-safe rule: findings with no reachability data still gate — unknown ≠ safe.
gracePeriodProperty<Duration>unsetFindings whose advisory was published within the period (e.g. PT168H = 7 days, using the finding’s publishedAt) warn instead of fail — a CVE published overnight shouldn’t redden every pipeline before anyone can triage. Pair with expiring suppressions for the long tail.
unresolvableProperty<Boolean>falseWhat ringzeroCheck does when it can’t get an answer: poll timeout (upload.timeout), network loss mid-poll, or scan status: failed. false = warn and pass (fail open), true = fail the build (fail closed) — typical only in release pipelines. This governs ringzeroCheck only: an upload that exhausts its retries always fails ringzeroScan (there is no scan to check), and a deliberate --offline build runs report-only, never an unresolvable event.

How the thresholds combine: each finding is tested against severity and cvss independently — either trips the gate. A finding with no CVSS score (some advisories ship without a vector) is evaluated against severity only; setting only cvss never consults severity labels. fixableOnly and reachableOnly are filters on top: a finding must pass a threshold and every enabled filter to fail the build.

Network behavior for the snapshot upload and status polling.

PropertyTypeDefaultDescription
timeoutProperty<Duration>PT5MOverall budget for the upload (all attempts and backoff); once ringzeroCheck ships, also its wait-for-completion budget.
connectTimeoutProperty<Duration>PT10STCP/TLS connect timeout per request.
retriesProperty<Int>3Retry count for the upload request (exponential backoff with jitter; retries I/O errors, 5xx, and 429, honoring Retry-After). Other 4xx responses never retry.
pollIntervalProperty<Duration>PT5SInterval between GET /v1/scans/{scanId} polls — reserved for the upcoming ringzeroCheck, unused in v0.1.0.

Uploads are idempotent: the plugin derives a key from the snapshot or multipart bundle as an Idempotency-Key, so a timeout-then-retry (or two CI jobs racing on the same commit) converges on one scan instead of creating duplicates.

The timeout covers the entire multipart upload, including retries. For large builds, adjust it to the measured upload time.

The plugin uses the build JVM’s standard proxy settings — the same ones Gradle itself uses — set in gradle.properties:

gradle.properties
systemProp.https.proxyHost=proxy.internal
systemProp.https.proxyPort=3128
systemProp.http.nonProxyHosts=*.internal

There is deliberately no RingZero-specific proxy DSL: ops teams configure the JVM proxy once and every plugin honors it. (The sidecar, which doesn’t run on a JVM, has its own proxy config.)

Suppress a known, triaged finding without losing sight of it. Suppressions live in a committed YAML file — not the DSL — because they’re governance artifacts: the reason and expiry belong in code review, and the same file format works for direct API integrations.

ringzero-suppressions.yml
suppressions:
- cve: CVE-2025-4410 # required — CVE or GHSA id
package: "com.alibaba:fastjson" # optional glob; omitted = this CVE on any package
reason: "Not exploitable: we never deserialize untrusted input" # required
expires: 2026-10-01 # optional ISO date; omitted = no expiry

The plugin parses the file itself (it has zero runtime dependencies, so no YAML library) and accepts exactly this restricted subset of YAML — nothing outside it is ever silently skipped; it’s a configuration error naming the line:

  • Blank lines and # comments are fine anywhere; # comments may also trail a value. A trailing comment must be separated from the value by a space (value # comment) — a # glued to the value or preceded only by a tab is part of the value, not a comment.
  • The first significant line must be suppressions: (or suppressions: [] for an explicit empty list; an empty file also means no suppressions).
  • Each entry is a - key: value block. The only keys are cve, package, reason, and expires; each at most once per entry, in any order.
  • Values are plain scalars or single-/double-quoted strings. No escape sequences, multi-line scalars (|/>), flow collections ([…]/{…}), anchors/aliases, nesting, or tabs in indentation.
  • expires must be an ISO yyyy-MM-dd date.

Semantics:

  • Suppressions are uploaded with the snapshot by ringzeroScan and applied server-side: matching findings are marked suppressed, excluded from the scan’s severity counts — and therefore from failOn gating — but remain visible in the app and the API.
  • reason is required. The plugin fails the build on an entry without one — the API never even sees a malformed file.
  • An expired entry produces a build warning and is ignored when a new scan is processed. --rerun attempts another upload; unchanged content may reuse the previous scan during the 24-hour idempotency window. The expired entry is still uploaded, keeping the bytes independent of the date.
  • Whether an entry matched anything is a server-side question — see the scan’s suppressed count in the app or the API; per-finding console output arrives with ringzeroCheck Coming soon.

Contrast with scan.excludedModules: exclusion removes a module from the snapshot entirely (no inventory, no findings, invisible); suppression keeps the dependency and the finding visible but un-gated. Prefer suppression for anything you may need to explain later.

TaskInputsOutputs (under reportsDirectory, default build/ringzero/)
ringzeroScanFiltered dependency graph files and attribution from module collection tasks; manifest paths; the suppressions file; project/apiUrl; upload and offline modesnapshot.json (full snapshot or bundle index); dependencies.md (report or report index); scan.json{ "id": "scn_7g8h", "url": "…" } after an upload, { "uploaded": false, "reason": "no-api-key" | "offline" } in report-only mode
ringzeroCheck Coming soonscan.json; the failOn { } policyBuild success/failure; console summary; summary.json — always written, pass or fail: { scanId, url, status, findings: {…}, suppressed, policy: { violated, threshold, violations } }; findings.json (raw findings array) when the key has findings:read

ringzeroScan also takes module team labels and custom attributes as inputs, and preserves them in the local artifacts and uploaded scan. For large builds, snapshot.json and dependencies.md index the multipart bundle instead of containing the full graph and report. Archive the whole report directory; see report formats.

summary.json exists so CI can annotate PRs without scraping console output.

ringzeroScan is up-to-date when the resolved dependency graph (after exclusions), module metadata, the suppressions file’s content, project, apiUrl, and whether an upload would happen are all unchanged since the last run — the task then skips the upload and reuses the previous scan id. Editing, creating, or deleting the suppressions file therefore re-executes the task and re-uploads. Consequences worth knowing:

  • Advisory matching happens when a scan is processed. Stored scans are not continuously re-matched; an unchanged task does not request fresh analysis.
  • Providing an API key after a report-only run counts as a change: the next build re-executes and uploads instead of reporting up-to-date.
  • Force the root scan task to run again with ./gradlew :ringzeroScan --rerun. Use a module-qualified task path for an independent scan. An identical snapshot can still reuse its previous scan within the API’s 24-hour idempotency window, including multipart uploads.

The tasks are never build-cacheable: an upload is a side effect. Local up-to-date checking can still skip an unchanged scan and reuse its outputs.

On Gradle 8.1 or newer, enable the configuration cache for aggregate and module-local scans:

Terminal window
./gradlew :ringzeroScan --configuration-cache --configuration-cache-problems=fail
./gradlew :core:ringzeroScan :api:ringzeroScan --parallel --configuration-cache

Or enable it for the build in gradle.properties:

org.gradle.configuration-cache=true

The first run stores the configured tasks; subsequent runs with the same task selection reuse them. Internal collection tasks resolve each graph in its owning project and write filtered fragments under that project’s build/ringzero/graphs/. The scan task merges these files into its final snapshot. Dependency graph providers stay lazy and tasks use declared inputs instead of accessing Gradle projects during execution, following Gradle’s configuration cache requirements. Changes to module build scripts and scan settings invalidate the cached configuration. Up-to-date checking still decides whether each scan executes; --rerun-tasks forces execution and an upload attempt while allowing configuration cache reuse. Other plugins in the build must also support the configuration cache.

Two situations put ringzeroScan in the same report-only mode: Gradle’s --offline flag, and no API key resolving from any source. In both, the task still resolves the graph and writes local reports (a full snapshot or an indexed large-scan bundle), skips the upload, logs which mode it is in, and succeeds — a missing key is never an error. scan.json records the outcome as { "uploaded": false, "reason": "no-api-key" | "offline" }.

Deliberate offline is never a failOn.unresolvable failure — that property governs unexpected unreachability only.

For every setting with more than one source, the plugin resolves in this order (first match wins):

PrioritySourceExample
1ringzero { } DSLapiUrl = "https://api.eu.ringzero.tech/v1"
2Environment variableRINGZERO_API_URL=…
3Gradle propertyringzero.apiUrl=… in ~/.gradle/gradle.properties

This applies to upload identity and connection properties (apiKey, apiUrl, project), which all have RINGZERO_* env and ringzero.* Gradle-property forms. List-valued and block settings (scan { }, failOn { }, upload { }, suppressionsFile, reportsDirectory) are DSL-only. Module metadata (teamName, attributes) is also DSL-only; it can be wired explicitly to a provider when an environment or Gradle property is desired.