Install
Security

Relic Scan

Scan stored artifacts with Syft and Grype, and enforce policies, watches and ignore rules.

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, .crate and .gem.
  • OS packages: .deb, .rpm and .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:

RiskSeveritySource
End of life: the component, or an image's operating system, is past its release cycle's end of lifehighendoflife.date, read daily
End of life within 90 dayslowthe same
Deprecated by its maintainers, or unmaintained (no source activity for 90 days, OpenSSF Scorecard Maintained)mediumdeps.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, or pkg:generic/alpine@3.17.4 for 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:

AboutVariables
The finding or issueid, cves, severity, cvss, fixable, malicious, package_name, version, purl, analyzer, rule, file, reachable (reachable, not_reachable or unknown), secret_live
Operational riskseol, unmaintained
Where it was foundrepository, 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.

RuleSeverityWhen
pickle-unsafe-importcriticalan import that runs code or reaches the system: os.system, subprocess, builtins.eval, exec, socket, …
pickle-dynamic-importhigh, or medium for an unreadable streaman import whose name the stream computes, or a stream Relic cannot read
pickle-unknown-importmediuman 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

KindFound byWhat
Secretsgitleakskeys, tokens and passwords in files, also inside archives. Relic records the rule, file and line, never the secret.
MisconfigurationsTrivy, with its built-in checksHelm 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.

ProviderSecrets (gitleaks rules)Request
GitHubpersonal, fine-grained, OAuth and app tokensGET https://api.github.com/user
GitLabpersonal access tokensGET https://gitlab.com/api/v4/personal_access_tokens/self
Slackbot and user tokensPOST https://slack.com/api/auth.test
npmaccess tokensGET https://registry.npmjs.org/-/whoami
Stripesecret and restricted keysGET 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 a not_affected statement 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 justification vulnerable_code_not_in_execute_path (CycloneDX: code_not_reachable).
  • Import VEX, or relic security vex import, turns not_affected and fixed statements (CycloneDX: not_affected, resolved) into ignore rules on the packages they name, by package URL or, in CycloneDX, by bom-ref or 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:

VariableLookupRequests
RELIC_SCAN_MAVEN_URLPOMs and parent POMs, for licenses and versions<url>/<group path>/<artifact>/<version>/<artifact>-<version>.pom
RELIC_SCAN_PYPI_URLPython licenses, for packages in requirements and lock files<url>/<name>/<version>/json
RELIC_SCAN_NPM_URLnpm licenses, for packages in lock files<url>/<name>/<version>
RELIC_SCAN_GO_PROXY_URLGo licenses<url>/<module>/@v/<version>.zip
  • Point RELIC_SCAN_MAVEN_URL at 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.json and the archive it names, for example into a generic repository, and point RELIC_SCAN_VULNERABILITY_DATABASE_URL at the copied latest.json; or
  • set it to off and 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 licenseBanned GPL-3.0-onlyAllowed MIT
MIT OR GPL-3.0-onlyno violation: MIT can be chosenno violation
MIT AND GPL-3.0-onlyviolationviolation: GPL-3.0-only is required
GPL-3.0-or-laterviolation: a ban covers every version formviolation
  • 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.