Deployment guide

OSV-Scanner

Overview

OSV-Scanner is Google's open-source scanner for the OSV.dev vulnerability database. It reads the dependency manifests and lockfiles already in your projects, such as requirements.txt, package-lock.json, go.mod, pom.xml, Cargo.lock, Gemfile.lock, packages.lock.json, composer.lock and many more. It reports every known vulnerability that affects the exact versions you have pinned, with the fixed version for each.

The SecureAnyCloud image is a ready-to-run dependency-scanning host, not just the binary. The OSV database is baked into the image, so your first scan is instant and runs with no network egress at all. This VM can sit in a locked-down subnet with no outbound internet and still scan. A daily timer refreshes the database, and a second daily timer scans your projects and writes machine-readable JSON and SARIF reports for CI pickup. It is backed by 24/7 SecureAnyCloud support.

What you get:

  • OSV-Scanner 2.4.0 (/usr/local/bin/osv-scanner), installed from the official Google release binary and verified against the upstream SHA256 checksum
  • A baked offline OSV database (~338 MB) at /var/lib/osv-scanner/db, covering 10 ecosystems: PyPI, npm, Go, Maven, crates.io, RubyGems, NuGet, Packagist, Alpine and Debian
  • OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY exported for every login shell, so osv-scanner uses the offline database automatically
  • osv-db-refresh and osv-db-refresh.timer: a daily database refresh, with configurable ecosystems
  • osv-scan-run and osv-scan.timer: a daily scan of your projects that writes JSON and SARIF reports with latest.* symlinks
  • Read-only self-test fixtures under /usr/share/osv-scanner/fixtures, so you can prove the scanner works on your own VM
  • 24/7 SecureAnyCloud support

This is a command-line product with no web UI, no listening service, no admin account and no password. SSH on port 22 is the only open port, and the SSH key you choose at launch is the only way in.

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet and subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point. The offline database needs roughly 1 GB of free disk on top of the base OS.

For NSG inbound rules, allow 22/tcp from your management network. No inbound application ports are needed, and scanning needs no outbound internet.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for OSV-Scanner by SecureAnyCloud, and select Create.

On Basics, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22). Then choose Review + create and Create.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:osv-scanner-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name osv-scanner \
  --image sac1647283583153:osv-scanner-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

There is no password and no console to log in to, because this product has no credentials at all. /etc/osv-scanner/credentials on the VM is an informational note that says exactly that and points at the paths below.

Step 4 - Confirm OSV-Scanner is installed

osv-scanner --version
osv-scanner version: 2.4.0 osv-scalibr version: 0.4.5 commit: b56b5191101d5f27d4787d5583d8d01e9518a7af built at: 2026-06-18T12:55:27Z

Step 5 - Confirm the offline database is baked in

The database ships inside the image, so there is nothing to download before your first scan.

du -sh /var/lib/osv-scanner/db ls /var/lib/osv-scanner/db/osv-scanner
338M /var/lib/osv-scanner/db Alpine Debian Go Maven NuGet Packagist PyPI RubyGems crates.io npm

The scanner finds this database automatically in every execution context: an interactive shell, sudo, a cron job, a systemd unit or a CI runner. You need no environment setup. /etc/profile.d/osv-scanner.sh exports the cache directory for login shells.

grep OSV_SCANNER /etc/profile.d/osv-scanner.sh
export OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY="/var/lib/osv-scanner/db"

A profile.d export only reaches login shells. For that reason, /usr/local/bin/osv-scanner is a small SecureAnyCloud wrapper that resolves the same path unconditionally before it hands off to the upstream binary at /usr/local/lib/osv-scanner/osv-scanner. That is what makes non-interactive use work out of the box.

env -i /usr/bin/env PATH=/usr/local/bin:/usr/bin:/bin \
  osv-scanner scan source --offline-vulnerabilities \
  --lockfile requirements.txt:/usr/share/osv-scanner/fixtures/clean/requirements.txt \
  >/dev/null 2>&1; echo "scrubbed-environment exit: $?"
scrubbed-environment exit: 0

If you set OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY yourself, it always overrides the wrapper. You can use it to point at a different database.

Step 6 - Run the built-in self-test

The image ships two read-only fixtures, so you can prove the scanner works before you point it at anything of your own. The first pins a deliberately vulnerable urllib3==1.26.4.

osv-scanner scan source --offline-vulnerabilities \
  --lockfile requirements.txt:/usr/share/osv-scanner/fixtures/vulnerable/requirements.txt echo "exit: $? (1 = vulnerabilities found, which is the expected result here)"

This is real output from this image. Note Loaded PyPI local db from ..., which proves that the scan ran entirely offline.

Scanned /usr/share/osv-scanner/fixtures/vulnerable/requirements.txt file and found 1 package Loaded PyPI local db from /var/lib/osv-scanner/db/osv-scanner/PyPI/all.zip Total 1 package affected by 9 known vulnerabilities (0 Critical, 6 High, 3 Medium, 0 Low, 0 Unknown) from 1 ecosystem. 9 vulnerabilities can be fixed. +-------------------------------------+------+-----------+---------+---------+---------------+ | OSV URL | CVSS | ECOSYSTEM | PACKAGE | VERSION | FIXED VERSION | +-------------------------------------+------+-----------+---------+---------+---------------+ | https://osv.dev/PYSEC-2021-108 | 8.7 | PyPI | urllib3 | 1.26.4 | 1.26.5 | | https://osv.dev/GHSA-q2q7-5pp4-w6pg | | | | | | | https://osv.dev/PYSEC-2023-192 | 8.1 | PyPI | urllib3 | 1.26.4 | 1.26.17 | | https://osv.dev/GHSA-v845-jxx5-vc9f | | | | | | | https://osv.dev/PYSEC-2026-1995 | 4.4 | PyPI | urllib3 | 1.26.4 | 1.26.19 | | https://osv.dev/GHSA-34jh-p97f-mpxf | | | | | | +-------------------------------------+------+-----------+---------+---------+---------------+

The second fixture pins a clean six==1.17.0 and must report nothing.

osv-scanner scan source --offline-vulnerabilities \
  --lockfile requirements.txt:/usr/share/osv-scanner/fixtures/clean/requirements.txt
Scanned /usr/share/osv-scanner/fixtures/clean/requirements.txt file and found 1 package Loaded PyPI local db from /var/lib/osv-scanner/db/osv-scanner/PyPI/all.zip No issues found

Step 7 - Scan your own projects

Put your checkouts under /srv/projects, or point the configuration at wherever they already are. Then scan the tree.

osv-scanner scan source --offline-vulnerabilities -r /srv/projects

-r walks the directory recursively and picks up every manifest and lockfile that it recognises. To scan a single lockfile, use --lockfile <type>:<path>, as in Step 6.

Step 8 - Scheduled scans and machine-readable reports

osv-scan-run is the scheduled entry point. It reads /etc/osv-scanner/scan.conf, scans SCAN_TARGET offline, and writes both a JSON and a SARIF report with latest.* symlinks.

Your VM ships with /srv/projects empty, so a scan run right now correctly reports 128 (nothing to scan). Drop a project in and run the scan. The example below uses the shipped vulnerable fixture as a stand-in for one of your own checkouts.

sudo install -d /srv/projects/demo
sudo cp /usr/share/osv-scanner/fixtures/vulnerable/requirements.txt /srv/projects/demo/
sudo osv-scan-run echo "exit: $? (1 = vulnerabilities found)"
[2026-07-19T11:57:58+0000] osv-scan-run: scanning /srv/projects offline against /var/lib/osv-scanner/db Scanned /srv/projects/demo/requirements.txt file and found 1 package Loaded PyPI local db from /var/lib/osv-scanner/db/osv-scanner/PyPI/all.zip [2026-07-19T11:58:01+0000] osv-scan-run: VULNERABILITIES FOUND - see /var/lib/osv-scanner/reports/osv-scan-20260719T115758Z.json [2026-07-19T11:58:01+0000] osv-scan-run: reports: ... (latest.json / latest.sarif updated) exit: 1 (1 = vulnerabilities found)

The reports directory keeps dated files plus stable symlinks that your CI can always read.

ls -l /var/lib/osv-scanner/reports/latest.json /var/lib/osv-scanner/reports/latest.sarif

SARIF is the format GitHub code scanning, Azure DevOps and most IDEs consume directly, so latest.sarif can be uploaded as-is.

Both timers are enabled and armed out of the box.

systemctl list-timers osv-db-refresh.timer osv-scan.timer --no-pager
NEXT LEFT UNIT ACTIVATES Mon 2026-07-20 00:07:28 UTC 14h osv-db-refresh.timer osv-db-refresh.service Mon 2026-07-20 00:27:19 UTC 14h osv-scan.timer osv-scan.service

Step 9 - The exit-code contract (CI integration)

osv-scanner and osv-scan-run both follow the documented exit-code contract, which makes them usable as a pipeline gate.

Exit code Meaning

0 No vulnerabilities found

1 Vulnerabilities found

128 No packages found (nothing to scan, or an unsupported project layout)

osv-scanner scan source --offline-vulnerabilities \
  --lockfile requirements.txt:/usr/share/osv-scanner/fixtures/clean/requirements.txt \
  >/dev/null 2>&1; echo "clean fixture exit: $?"
clean fixture exit: 0

Handle 128 explicitly in CI. It means the scan found nothing to look at, which is usually a path or layout problem rather than a clean result.

Configuration

All settings live in /etc/osv-scanner/scan.conf.

grep -v '^#' /etc/osv-scanner/scan.conf | grep .
SCAN_TARGET="/srv/projects" REPORT_DIR="/var/lib/osv-scanner/reports" OSV_DB_DIR="/var/lib/osv-scanner/db" OSV_ECOSYSTEMS="PyPI npm Go Maven crates.io RubyGems NuGet Packagist Alpine Debian" REPORT_RETENTION_DAYS=30
  • SCAN_TARGET: the directory that the daily scan walks. Point it at your checkouts.
  • OSV_ECOSYSTEMS: the ecosystems that are kept offline. See "Adding ecosystems" below.
  • REPORT_RETENTION_DAYS: dated reports older than this are pruned.

After you edit the file, restart the timer.

sudo systemctl restart osv-scan.timer

Adding ecosystems to the offline database

Ten ecosystems are baked in by default. The full list of ecosystems that OSV publishes is at osv-vulnerabilities.storage.googleapis.com/ecosystems.txt. Ubuntu is deliberately not cached by default, because it alone is roughly 535 MB. Add it, or any other ecosystem, if you need it.

sudo sed -i 's/^OSV_ECOSYSTEMS=.*/OSV_ECOSYSTEMS="PyPI npm Go Maven crates.io RubyGems NuGet Packagist Alpine Debian Ubuntu"/' /etc/osv-scanner/scan.conf
sudo osv-db-refresh

osv-db-refresh downloads only the ecosystems listed in the configuration, so adding one does not re-download the rest.

Keeping the database current

The database is baked at build time, so your first scan works immediately and offline. It is then refreshed at first boot and daily by osv-db-refresh.timer. A VM launched months after the image was published therefore never scans against a stale image-time database.

The first-boot refresh is deliberately network-tolerant. If your subnet has no outbound internet, the refresh logs the failure and moves on rather than failing the boot, because the baked database still makes the scanner fully useful. To refresh on demand, run this command.

sudo osv-db-refresh

Maintenance

  • Database: osv-db-refresh.timer refreshes it daily. Force a refresh with sudo osv-db-refresh.
  • Scans: osv-scan.timer runs them daily. Run one on demand with sudo osv-scan-run, and inspect the history with journalctl -u osv-scan.service.
  • Upgrades: download a newer release binary from the OSV-Scanner releases, verify it against the published SHA256SUMS, and replace /usr/local/bin/osv-scanner.
  • Security patches: unattended-upgrades remains enabled, so the OS continues to receive security updates automatically.
  • Air-gapped use: scanning needs no egress. Only osv-db-refresh does, and it fails soft.

Support

SecureAnyCloud provides 24/7 expert support for this image. Contact sales@secureanycloud.com.

Deploy on Azure

Launch OSV-Scanner on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure