Supply chain security

Pepper analyses your third-party dependencies for known vulnerabilities, malicious packages, and license policy violations — then tells you which findings actually matter, why each dependency is present, and produces signed SBOM and VEX documents you can hand to a customer or auditor.

Design principle: a vulnerability database can tell you a CVE exists in a package. It cannot tell you whether that matters in your code. Pepper aims to report a small number of findings you should act on, and to state — in a verifiable document — what it ruled out and why.

What runs during a scan

StageWhat it doesSource
Dependency discoveryParses manifests and lockfiles, including transitive dependenciesYour repository
Vulnerability lookupKnown CVEs per package versionOSV.dev
Exploitation signalsEPSS probability and CISA KEV (exploited in the wild)FIRST.org, CISA
License resolutionDeclared licenses, source repo, deprecation, provenancedeps.dev
Dependency pathsWhich direct dependency introduced a transitive packagedeps.dev + your lockfiles
Malicious package checksKnown malware, typosquatting, install-script behaviourOSV malware DB, registries, AI
AI triageDecides which CVEs apply to your code, using the advisory textYour configured LLM
Container imagesOS and application CVEs in referenced imagesTrivy
DocumentsSBOM (CycloneDX + SPDX), VEX (OpenVEX + CycloneDX), signaturesPepper

Supported ecosystems

Dependency and vulnerability scanning covers all ecosystems below. License, provenance, and dependency-path data come from deps.dev, which covers the seven marked Full. The rest are scanned for vulnerabilities but report no license data.

EcosystemFiles parsedCoverage
npmpackage.json, package-lock.json, yarn.lock, pnpm-lock.yamlFull
PyPIrequirements.txt, Pipfile.lock, poetry.lock, pyproject.tomlFull
Maven / Gradlepom.xml, build.gradle, build.gradle.ktsFull
Gogo.modFull
crates.ioCargo.toml, Cargo.lockFull
NuGet.csproj, .fsproj, .vbproj, packages.configFull
RubyGemsGemfile.lockFull
Packagistcomposer.json, composer.lockVulnerabilities only
Pub (Dart)pubspec.yamlVulnerabilities only
Hex (Elixir)mix.lockVulnerabilities only
SwiftPMPackage.resolvedVulnerabilities only

How findings are prioritised

Each dependency vulnerability carries the evidence behind its priority:

AI triage reads the published advisory text alongside this evidence, so exploitability conditions come from the advisory rather than from the model's recollection of a CVE identifier. Where an advisory states a precondition that does not hold in your codebase, the finding is ruled out — and recorded in the VEX document rather than silently dropped.

Why is this dependency here?

Vulnerabilities in transitive dependencies include the chain that introduced them and the direct dependency to change:

Introduced by: express
Path: express@4.18.2 → accepts@1.3.8 → mime-types@2.1.35 → mime-db@1.52.0

The fix for a transitive vulnerability is usually to upgrade the introducing dependency or pin an override — not to add the vulnerable package as a direct dependency. Paths are verified against your own lockfiles: Pepper only reports a chain whose every hop genuinely exists in your project. If a path cannot be established, the finding simply carries no path rather than a guess.

License policy

Declared licenses are resolved per dependency version and recorded in both SBOM formats. Findings are raised only for policy violations — a compliant dependency produces no finding, and the SBOM remains the full inventory.

VerdictSeverityDefault patterns
DeniedHIGHAGPL-*, GPL-*, SSPL-*, BUSL-*, CC-BY-NC-*, Commons-Clause, Elastic-2.0
ReviewLOWLGPL-*, MPL-*, EPL-*, CDDL-*, OSL-*, MS-RL, CPAL-*
AllowedEverything else (MIT, Apache-2.0, BSD, ISC, …)
UnknownNot reported unless you opt in

Dual licenses are handled correctly

Licenses are SPDX expressions, not plain names. A package offered as BSD-3-Clause OR GPL-2.0 may be taken under BSD, so it does not violate a GPL ban. Pepper evaluates the expression:

Patterns are prefix-scoped, so GPL-* does not match LGPL-*, and a bare pattern such as GPL-2.0 also covers GPL-2.0-only, GPL-2.0-or-later, and GPL-2.0+.

Severity is not reduced for transitive dependencies, because copyleft obligations apply regardless of dependency depth.

Configuring the policy

# Comma-separated SPDX IDs or PREFIX-* wildcards
LICENSE_POLICY_DENY=AGPL-*,GPL-*,SSPL-*,BUSL-*
LICENSE_POLICY_WARN=LGPL-*,MPL-*,EPL-*
# Report dependencies whose license could not be determined (default false)
LICENSE_POLICY_FLAG_UNKNOWN=false

To allow a license your policy denies, remove it from LICENSE_POLICY_DENY rather than suppressing each finding individually.

Malicious package detection

Runs in three passes, from cheapest and most certain to most exploratory:

  1. Known malware — batch check against the OpenSSF Malicious Packages database (OSV MAL-* advisories). Confirmed hits are CRITICAL.
  2. Registry metadata — package age, missing source repository, and the presence of install scripts, across npm, PyPI, Maven, Go, crates.io, and RubyGems.
  3. AI analysis — typosquatting against popular names, and install-script behaviour such as credential exfiltration, remote code execution, and obfuscation.
Adversary-resistant by design. In supply chain analysis the attacker writes the text being analysed — install scripts, package metadata, even lockfile version strings. Pepper treats all of it as untrusted data, never as instructions, and content that tries to talk the analysis out of a verdict is itself treated as a malicious signal. A confirmed malware advisory can never be downgraded by model output.

Container image scanning

Image references found in Dockerfiles, Compose files, and infrastructure code are scanned for OS and application CVEs with Trivy, which ships inside the Pepper worker image. Base-image OS packages are reported one severity level lower than application-layer packages, since they are owned by the base image maintainer.

Coverage is always reported. If image scanning cannot run — Trivy missing, a private registry without credentials, a tag that does not exist — Pepper raises an INFO finding naming the unscanned images. An empty result never silently means "these images are clean". These findings do not fail build gates.

SBOM and VEX documents

Every scan with dependencies produces downloadable artifacts:

ArtifactFormatContents
SBOMCycloneDX 1.5 JSONComponents, PURLs, licenses, dev/runtime scope
SBOMSPDX 2.3 JSONSame inventory in SPDX form
VEXOpenVEX 0.2.0Whether each vulnerability affects your product
VEXCycloneDX 1.5 VEXSame statements for CycloneDX-native tools
SignaturesJSON bundleCosign or RSA signatures over each document

What the VEX tells a consumer

An SBOM lists what is in your product. It says nothing about whether the vulnerabilities in those components affect you — so a customer scanning your SBOM re-derives every CVE with no idea which you already assessed. The VEX answers that:

VEX statusMeaningComes from
affectedApplies to us; includes what to doOpen, in-progress, and accepted-risk findings
not_affectedDoes not apply; includes whyFindings marked false positive, and CVEs ruled out by AI triage
fixedAlready remediatedFindings marked resolved

Each not_affected statement carries a machine-readable justification where one applies — for example vulnerable_code_not_in_execute_path or component_not_present — and otherwise carries the reason in prose. Pepper never invents a justification it did not establish.

A risk you have formally accepted is reported as affected, not not_affected, with your acceptance rationale as the action statement. Claiming otherwise would misstate your position to a customer.

Statements produced by AI triage rather than a human reviewer say so explicitly, so a reader can weigh them accordingly.

Verifying a signature

Enable signing under Settings. Signatures cover the SBOM and VEX bytes, so any edit to a document — including flipping a not_affected statement — invalidates them.

# Keyless (sigstore) mode
cosign verify-blob --signature sig.txt openvex.json

# Key-based mode
openssl dgst -sha256 -verify public.pem -signature sig.bin openvex.json

Air-gapped and offline operation

Pepper runs fully on-premise with a local LLM and no outbound network access. Set the vulnerability database mode to offline under Settings, or disable individual lookups:

FeatureOffline behaviour
CVE lookup (OSV)Skipped, or pointed at a mirror via OSV_API_URL
License / provenance (deps.dev)Skipped. Set ENABLE_DEPS_DEV=false to disable entirely
EPSS / CISA KEVSkipped. Set ENABLE_EPSS_KEV=false to disable entirely
Container CVEs (Trivy)Uses a pre-populated database; no update attempted
SAST / IaC / triageRuns against your in-network LLM (for example Ollama)
SBOM / VEX / signingFully offline

To scan container images without network access, mount a Trivy database into the worker:

# Populate a cache on a connected machine
trivy image --download-db-only --cache-dir ./trivy-cache

# Mount it into the worker and enable offline mode
services:
  worker:
    environment:
      TRIVY_CACHE_DIR: /var/cache/trivy
    volumes:
      - ./trivy-cache:/var/cache/trivy:ro
Every external lookup degrades to "unknown", never to "clean". If a data source is unreachable, Pepper reports less — it does not report success it cannot support.

Related environment variables

See Environment variables for the full list, including tuning for local models and cost controls.