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.
What runs during a scan
| Stage | What it does | Source |
|---|---|---|
| Dependency discovery | Parses manifests and lockfiles, including transitive dependencies | Your repository |
| Vulnerability lookup | Known CVEs per package version | OSV.dev |
| Exploitation signals | EPSS probability and CISA KEV (exploited in the wild) | FIRST.org, CISA |
| License resolution | Declared licenses, source repo, deprecation, provenance | deps.dev |
| Dependency paths | Which direct dependency introduced a transitive package | deps.dev + your lockfiles |
| Malicious package checks | Known malware, typosquatting, install-script behaviour | OSV malware DB, registries, AI |
| AI triage | Decides which CVEs apply to your code, using the advisory text | Your configured LLM |
| Container images | OS and application CVEs in referenced images | Trivy |
| Documents | SBOM (CycloneDX + SPDX), VEX (OpenVEX + CycloneDX), signatures | Pepper |
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.
| Ecosystem | Files parsed | Coverage |
|---|---|---|
| npm | package.json, package-lock.json, yarn.lock, pnpm-lock.yaml | Full |
| PyPI | requirements.txt, Pipfile.lock, poetry.lock, pyproject.toml | Full |
| Maven / Gradle | pom.xml, build.gradle, build.gradle.kts | Full |
| Go | go.mod | Full |
| crates.io | Cargo.toml, Cargo.lock | Full |
| NuGet | .csproj, .fsproj, .vbproj, packages.config | Full |
| RubyGems | Gemfile.lock | Full |
| Packagist | composer.json, composer.lock | Vulnerabilities only |
| Pub (Dart) | pubspec.yaml | Vulnerabilities only |
| Hex (Elixir) | mix.lock | Vulnerabilities only |
| SwiftPM | Package.resolved | Vulnerabilities only |
How findings are prioritised
Each dependency vulnerability carries the evidence behind its priority:
- EPSS score — probability of exploitation in the next 30 days.
- CISA KEV — confirmed exploited in the wild. These are never suppressed.
- Direct vs transitive — whether you depend on the package directly.
- Import evidence — where your code actually imports the package.
- Fix availability — whether a patched version exists.
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.
| Verdict | Severity | Default patterns |
|---|---|---|
| Denied | HIGH | AGPL-*, GPL-*, SSPL-*, BUSL-*, CC-BY-NC-*, Commons-Clause, Elastic-2.0 |
| Review | LOW | LGPL-*, MPL-*, EPL-*, CDDL-*, OSL-*, MS-RL, CPAL-* |
| Allowed | — | Everything else (MIT, Apache-2.0, BSD, ISC, …) |
| Unknown | — | Not 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:
OR— the most permissive branch applies (you may choose it).AND— the most restrictive branch applies (all obligations bind).
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:
- Known malware — batch check against the OpenSSF Malicious Packages database (OSV
MAL-*advisories). Confirmed hits are CRITICAL. - Registry metadata — package age, missing source repository, and the presence of install scripts, across npm, PyPI, Maven, Go, crates.io, and RubyGems.
- AI analysis — typosquatting against popular names, and install-script behaviour such as credential exfiltration, remote code execution, and obfuscation.
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.
SBOM and VEX documents
Every scan with dependencies produces downloadable artifacts:
| Artifact | Format | Contents |
|---|---|---|
| SBOM | CycloneDX 1.5 JSON | Components, PURLs, licenses, dev/runtime scope |
| SBOM | SPDX 2.3 JSON | Same inventory in SPDX form |
| VEX | OpenVEX 0.2.0 | Whether each vulnerability affects your product |
| VEX | CycloneDX 1.5 VEX | Same statements for CycloneDX-native tools |
| Signatures | JSON bundle | Cosign 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 status | Meaning | Comes from |
|---|---|---|
affected | Applies to us; includes what to do | Open, in-progress, and accepted-risk findings |
not_affected | Does not apply; includes why | Findings marked false positive, and CVEs ruled out by AI triage |
fixed | Already remediated | Findings 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:
| Feature | Offline 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 KEV | Skipped. Set ENABLE_EPSS_KEV=false to disable entirely |
| Container CVEs (Trivy) | Uses a pre-populated database; no update attempted |
| SAST / IaC / triage | Runs against your in-network LLM (for example Ollama) |
| SBOM / VEX / signing | Fully 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
Related environment variables
See Environment variables for the full list, including tuning for local models and cost controls.