Relic Scan
The worker catalogs every scannable file with Syft, such as package archives and Docker image
manifests, and matches the catalog against Grype's vulnerability database. Without either
binary, Relic Scan is off. The server counts it as on while a worker with both binaries
reported its scan queue in the last 20 minutes. When it is off, a file waiting for a scan
shows unavailable instead of queued, and Home, Administration → System and
relic security database status say that Relic Scan is off. The settings are RELIC_SCAN_SYFT_PATH, RELIC_SCAN_GRYPE_PATH
and RELIC_SCAN_VULNERABILITY_DATABASE_DIR; see the
configuration reference.
What Relic Scan scans
The worker scans these files, by name:
- Package archives:
.jar,.war,.ear,.tgz,.tar.gz,.tar.bz2,.tar.xz,.txz,.whl,.egg,.zip,.nupkg,.crateand.gem. - OS packages:
.deb,.rpmand.apk. - Docker image manifests (
manifest.json) and model files (.pkl,.pickle,.joblib,.dill,.pt,.pth,.bin,.ckpt,.npy). - Configuration files (
.yaml,.yml,.tf,.tfvars,.env,.dockerfile,Dockerfile,Containerfile) and lock files (package-lock.json,npm-shrinkwrap.json,yarn.lock,Gemfile.lock,Cargo.lock,composer.lock,poetry.lock,Pipfile.lock,requirements*.txt,go.mod), for secrets, misconfigurations and the packages they name.
Grype matches an OS package only against its distribution's data. Relic takes the release from
the package version (el8, fc39, amzn2, +deb11u2, ubuntu0.22.04). For a Debian package
whose version names none, Relic takes it from the deb.distribution the package is published
under: buster, bullseye, bookworm and trixie (Debian 10 to 13), bionic, focal,
jammy and noble (Ubuntu 18.04 to 24.04), with a suite suffix such as -security ignored
and the oldest release taken when there are several. An Alpine package takes it from a vX.Y
folder in its path. When none names one, as for sid, the scan is partial and its reason
says that the package's OS vulnerabilities were not matched.
Quarantine and Also block artifacts without a successful scan hold only package archives,
OS packages, image manifests and model files. Other files, configuration files included, are
served before they are scanned. For such a file, GET /api/v1/repositories/<repo>/download-checks/<path> answers rule not_held, and
relic_downloads_not_held_total counts these downloads.
In the UI
- An artifact's Security tab shows its findings (for a Docker tag, the image's), with CVSS scores and where each vulnerable package was found.
- The SBOM tab lists every component with its licenses. Filter by license or to license
policy violations; a filter for one license matches
MIT OR Apache-2.0. Hover over a violation to see its policy, rule and watch. - Security → Vulnerabilities lists every finding, filtered by severity, repository, package type, fixable, known exploited or malicious.
Malicious packages
Every scan also reports the packages named in
OpenSSF's malicious-packages data (MAL-
advisories). The worker reads them from OSV every six hours, for npm, PyPI, Maven, Go, NuGet,
crates.io and RubyGems, and checks earlier scans again when a new advisory arrives. A
malicious package is a critical finding marked Malicious.
A policy rule set to Matches: Malicious packages (malicious: true) matches only these.
With Block downloads, a remote repository stops serving a package as soon as it is reported.
Reachability (Go)
The worker runs govulncheck on every Go executable a scan finds, alone, in an archive or in an image. A Go finding is then:
- Reachable: the executable calls a vulnerable function. The finding shows the call.
- Not reachable: the executable contains the module, but no code calls the vulnerable functions.
- Unknown: every other finding, including other languages, Go module sources and binaries without build information.
A security rule can skip unreachable findings: Skip Go vulnerabilities no code calls
(ignoreUnreachable: true). Unknown findings still count.
govulncheck reads the Go vulnerability database, which the worker downloads with Grype's from
RELIC_SCAN_GO_VULNERABILITY_DATABASE_URL. When it changes, the worker checks the stored
symbol summaries of scanned executables again, without downloading the artifacts.
Operational risk
Every scan also reports operational risks, apart from vulnerabilities:
| Risk | Severity | Source |
|---|---|---|
| End of life: the component, or an image's operating system, is past its release cycle's end of life | high | endoflife.date, read daily |
| End of life within 90 days | low | the same |
| Deprecated by its maintainers, or unmaintained (no source activity for 90 days, OpenSSF Scorecard Maintained) | medium | deps.dev, only when RELIC_DEPS_DEV_URL is set, since each check sends it a package name and version |
Security → Operational risk lists them across repositories. When the end-of-life data changes, Relic assesses earlier scans again.
- Matches: End of life (
endOfLife: true) with a minimum severity of high matches cycles past end of life; with low, also those ending within 90 days. - Matches: Unmaintained (
unmaintained: true) matches deprecated and unmaintained packages. - To accept a risk, ignore its package URL, such as
pkg:pypi/django@4.2.11, orpkg:generic/alpine@3.17.4for an image's operating system.
List the end-of-life components in one repository:
relic security risks --kind end_of_life --repository libs-local
Quarantine
Quarantine on a remote repository holds the files it caches until Relic Scan has scanned
them and found them not malicious. Only package archives, OS packages, image manifests and
model files are held (What Relic Scan scans). A held file answers 403 with the reason and cannot be
copied or moved. A clean scan releases it, as does a later match that withdraws its malicious
finding.
Security → Quarantine lists held files: awaiting scan, scan failed, scan skipped (over 2 GiB), malicious or rejected. An administrator can release a file, to serve it whatever its scan says, or reject it, to never serve it.
List the held files:
relic security quarantine list
Release one file:
relic security quarantine release maven-remote/org/acme/lib/1.0/lib-1.0.jar --reason "reviewed"
A released file is still checked against the watches covering its repository.
Conditions
A security rule can have a CEL condition, with these variables:
| About | Variables |
|---|---|
| The finding or issue | id, cves, severity, cvss, fixable, malicious, package_name, version, purl, analyzer, rule, file, reachable (reachable, not_reachable or unknown), secret_live |
| Operational risks | eol, unmaintained |
| Where it was found | repository, path, build |
With another criterion, such as a minimum severity, the condition narrows it; alone, the condition decides.
rules:
- name: critical-outside-our-code
minSeverity: critical
condition: '!purl.startsWith("pkg:maven/com.example/")'
failBuild: true
- name: secrets-outside-tests
condition: 'analyzer == "secrets" && !file.endsWith("_test.go")'
blockDownload: true
Saving a policy fails when a condition is not valid CEL, names an unknown variable, or does not return true or false.
Model files
Relic Scan reads, without loading, the pickle streams of .pkl, .pickle, .joblib, .dill, PyTorch files (.pt,
.pth, .bin, .ckpt, zip and legacy format) and NumPy object arrays (.npy), and reports
their imports as model issues. A model file goes to this analyzer alone: Syft, Grype, Gitleaks
and Trivy do not scan it, so it has no SBOM or vulnerability findings.
| Rule | Severity | When |
|---|---|---|
pickle-unsafe-import | critical | an import that runs code or reaches the system: os.system, subprocess, builtins.eval, exec, socket, … |
pickle-dynamic-import | high, or medium for an unreadable stream | an import whose name the stream computes, or a stream Relic cannot read |
pickle-unknown-import | medium | an import outside the libraries models use (PyTorch, NumPy, scikit-learn, …) |
Policies treat these like other issues: a rule with a minimum severity of critical blocks a
malicious model. Prefer safetensors, which holds no code.
Secrets and misconfigurations
| Kind | Found by | What |
|---|---|---|
| Secrets | gitleaks | keys, tokens and passwords in files, also inside archives. Relic records the rule, file and line, never the secret. |
| Misconfigurations | Trivy, with its built-in checks | Helm charts, Kubernetes manifests, Terraform modules, Dockerfiles and image configuration (a privileged container, a missing resource limit, an image running as root, …) |
Each analyzer is off when its binary (RELIC_SCAN_GITLEAKS_PATH, RELIC_SCAN_TRIVY_PATH) is
missing. The issues appear under Secrets, misconfigurations, licenses and model code in
files on an artifact's Security tab, and in relic security report <repository>/<path>.
A policy rule matches them with Matches: Secrets or Misconfigurations (secrets: true,
misconfigurations: true).
Secret validation
With RELIC_SECRET_VALIDATION_ENABLED=true, the worker asks each secret's provider whether it
still accepts it. It is off by default, because it sends each secret to its provider.
| Provider | Secrets (gitleaks rules) | Request |
|---|---|---|
| GitHub | personal, fine-grained, OAuth and app tokens | GET https://api.github.com/user |
| GitLab | personal access tokens | GET https://gitlab.com/api/v4/personal_access_tokens/self |
| Slack | bot and user tokens | POST https://slack.com/api/auth.test |
| npm | access tokens | GET https://registry.npmjs.org/-/whoami |
| Stripe | secret and restricted keys | GET https://api.stripe.com/v1/account |
The issue records the outcome:
- Live: the provider accepts the secret. The issue becomes critical.
- Inactive: Slack, npm or Stripe refuses it.
- Not confirmed (
unknown): no provider answers for the rule (AWS keys, private keys, generic passwords), or a GitHub or GitLab refusal, which a self-hosted server's token also gets. - Check failed (
error): the provider did not answer.
Relic contacts only these hosts, never stores the secret, and audits each check
(secret.validated). Checks are half a second apart, at most 50
per scan. RELIC_SECRET_VALIDATION_PROVIDERS limits the providers, for example github,npm.
Matches: Live secrets (liveSecrets: true) matches live secrets only.
Deep license scanning
To find licenses in file text, such as a vendored GPL source file, install the
ScanCode toolkit beside the worker and
set RELIC_SCAN_SCANCODE_PATH to its scancode binary. It is off by default, and slow. Relic
lists each license with its file and line, and license policies treat it like a package's own.
SBOMs
Export SBOM on the SBOM tab downloads the components as CycloneDX 1.6, SPDX 2.3 or Syft JSON.
Export an item's SBOM as SPDX:
relic security sbom export libs-local/app/app-1.4.0.jar --format spdx --file app.spdx.json
A build can upload its own SBOM (CycloneDX, SPDX or Syft JSON). It replaces the SBOM of the
item's content, and Relic matches it again. Until the match finishes, the scan is queued, so
relic security report does not report a clean result early. Every file with that content shares the SBOM, so
this needs deploy permission on each of them, or an administrator.
Import an SBOM for an item:
relic security sbom import libs-local/app/app-1.4.0.jar build/bom.cdx.json
The Relic API routes are GET and
PUT /api/v1/sbom/<repo>/<path>.
VEX
Relic exchanges ignore rules as OpenVEX and CycloneDX VEX documents.
- Export VEX on Security → Ignore rules, or
relic security vex export, writes anot_affectedstatement for each vulnerability a rule ignores in packages: OpenVEX, or CycloneDX 1.6 with--format cyclonedx. - With the Go vulnerabilities no code calls (
--unreachable) adds the unreachable Go vulnerabilities, per scanned file, with the justificationvulnerable_code_not_in_execute_path(CycloneDX:code_not_reachable). - Import VEX, or
relic security vex import, turnsnot_affectedandfixedstatements (CycloneDX:not_affected,resolved) into ignore rules on the packages they name, by package URL or, in CycloneDX, bybom-refor BOM-Link. The rule notes the document, its author and the importing user. A statement whose rule is already stored creates no second one.
Import skips affected and under investigation (CycloneDX: exploitable, in triage, false positive). Export skips rules VEX cannot express: without a vulnerability or a package, limited to licenses, watches, policies, paths or builds, or expiring (waivers included). Both report how many they skipped.
Package lookups
A package does not always carry its license: a Go module in an executable has none, and a jar often leaves it to a parent POM. Lookups fetch what is missing. They are off by default because each one sends a package's name and version to the URL you set.
Set a URL for each ecosystem you want looked up:
| Variable | Lookup | Requests |
|---|---|---|
RELIC_SCAN_MAVEN_URL | POMs and parent POMs, for licenses and versions | <url>/<group path>/<artifact>/<version>/<artifact>-<version>.pom |
RELIC_SCAN_PYPI_URL | Python licenses, for packages in requirements and lock files | <url>/<name>/<version>/json |
RELIC_SCAN_NPM_URL | npm licenses, for packages in lock files | <url>/<name>/<version> |
RELIC_SCAN_GO_PROXY_URL | Go licenses | <url>/<module>/@v/<version>.zip |
- Point
RELIC_SCAN_MAVEN_URLat a mirror: Maven Central can refuse Syft's requests. - A URL takes no credentials, so the mirror must allow anonymous reads. Relic refuses to start with credentials in one.
- The scanners do not read the worker's proxy settings, and they trust the system's CA certificates only. The mirror must be directly reachable with a certificate those roots accept.
- A lookup that fails leaves that license unknown, and the scan continues.
- Lookups run when Relic makes an SBOM. SBOMs already stored keep the licenses they have.
Without Internet access
The worker downloads the vulnerability database at start and every 12 hours. A database that
fails its checksum is deleted and downloaded again; scans wait meanwhile. With
RELIC_SCAN_VULNERABILITY_DATABASE_URL=off, scans wait until you import one. A scan whose
match against the database fails three times is recorded as failed, with Grype's error, and
the worker scans it again once a database download or import succeeds. Offline, either:
- copy Grype's
latest.jsonand the archive it names, for example into a generic repository, and pointRELIC_SCAN_VULNERABILITY_DATABASE_URLat the copiedlatest.json; or - set it to
offand import databases by hand.
Package lookups are off unless you set them; point them at mirrors inside the network.
On a machine with Internet access, set the database base URL:
base=https://grype.anchore.io/databases/v6
Download the latest database archive:
curl -fsSLo grype-db.tar.zst "$base/$(curl -fsS $base/latest.json | jq -r .path)"
Inside the network, import it as an administrator:
relic security database import grype-db.tar.zst
Check the database status:
relic security database status
Import vulnerability database on Administration → System does the same.
The server refuses a file that does not start like an archive Grype imports (.tar.zst,
.tar.gz, .tar.xz or .tar), and refuses any import while Relic Scan is off. When the
worker fails to install an archive, the status command and Administration → System show
the error until a later import succeeds.
For Go reachability, copy https://vuln.go.dev/vulndb.zip to a generic repository and point
RELIC_SCAN_GO_VULNERABILITY_DATABASE_URL at the copy.
For end-of-life data, point RELIC_END_OF_LIFE_URL at a copy, or set it to off and import
it. On a machine with Internet access, download it:
curl -fsSLo eol.json https://endoflife.date/api/v1/products/full
Inside the network, import it as an administrator:
relic security end-of-life import eol.json
Check what the server holds:
relic security end-of-life status
Policies and watches
- A policy defines violations: a minimum severity, or licenses. It can fail builds and block downloads.
- A watch applies policies to local and remote repositories, or builds. A download through a virtual repository is checked against the member that stores the file, so a watch cannot name a virtual repository, or one that does not exist.
- A rule that blocks downloads can also block content without a successful scan
(
blockUnscanned, Also block artifacts without a successful scan): not scanned yet, failed, or skipped (over 2 GiB). A skipped file stays blocked while the rule covers it. The rule applies to package archives, OS packages, image manifests and model files only.
License policies
A license rule bans or allows SPDX licenses. Relic evaluates each component's SPDX expression:
| Component license | Banned GPL-3.0-only | Allowed MIT |
|---|---|---|
MIT OR GPL-3.0-only | no violation: MIT can be chosen | no violation |
MIT AND GPL-3.0-only | violation | violation: GPL-3.0-only is required |
GPL-3.0-or-later | violation: a ban covers every version form | violation |
- A license not mapped to SPDX is compared by name, ignoring case.
- A component without a license is allowed, unless the rule turns off Allow components without a known license.
- License policies apply to scanned artifacts. Build scans (
relic build scan) report only vulnerabilities.
Policies as code
A preview evaluates relic.build/v1 policies against stored findings, without saving them,
and lists the files they would raise violations on.
apiVersion: relic.build/v1
kind: Policy
metadata: {name: no-critical}
spec:
kind: security
rules:
- {name: critical, minSeverity: critical, failBuild: true, blockDownload: true}
Preview the policies:
relic security policy preview -f policies.yaml
Apply them:
relic apply -f policies.yaml
Decisions
Security → Decisions lists every blocking decision: blocked downloads, builds a scan fails, and promotions a gate refuses. Each names the subject, policy, watch, rule, reason, requester and count.
List the blocked downloads:
relic security decisions --kind download
Ignore rules
An ignore rule stops a finding from counting as a violation. A license rule covers every
expression naming the license: ignoring GPL-3.0-only also covers MIT AND GPL-3.0-only.
Waivers
Any signed-in user can request a waiver: an ignore rule for a package, vulnerabilities or licenses, with a reason. Administrators approve it for 1 to 365 days, creating an ignore rule that expires then, or reject it with a note. Security → Waivers lists your waivers; administrators and viewers see everyone's.
Request a waiver:
relic security waiver request --package pkg:npm/lodash@4.17.15 --vulnerability CVE-2020-8203 --reason "the vulnerable function is not reachable"
List the pending waivers, 50 per page, newest first:
relic security waiver list --status pending
List the next page:
relic security waiver list --status pending --page 2
Approve one for 30 days:
relic security waiver approve 0192f7a0-5d1e-7c3a-9b2f-6e4d8a1c0b7e --days 30 --note "until the upgrade"
CLI
relic security audit scans a project's dependencies, relic security scan files, images
and SBOMs, and relic build scan a published build, all with the server's database and
policies. See Scanning and auditing, and
CVE intelligence to rank findings by risk.