OSV Dependency Scanner
The Java and Python scanners carry a short hand-written CVE table. This one carries none. It parses six kinds of manifest into (name, version, ecosystem) triples, posts them in batches of 100 to Google's OSV.dev database, and turns every vulnerability record that comes back into a finding. It needs no API key.
This scanner has no docs/scanners/*.md file. The page is built from isitsecure/engine/code_analysis/osv_dependency_scanner.py and the one-line row in scanners/README.md. Every number below is read from that code.
The scanner reads summary, details, affected and database_specific.severity from each entry in the /v1/querybatch response. A live query for lodash@4.17.15 on 2026-10-25 returned only id and modified for each vulnerability. The scanner therefore follows the batch query with a GET /v1/vulns/{id} per unique id (at most 100 ids, a few at a time, with a per-request timeout) and merges the full record in before reading severity, summary and fixed version. A CVSS v3.x vector in severity[] is converted to a base score; CVSS 4.0 vectors are not scored and fall back to Medium. If a record cannot be fetched, its finding falls back to Medium severity and the generic upgrade text, and a progress message says so. If a batch query itself fails, the packages in it are reported as not checked rather than as clean.
From manifest to query
The scanner walks every file in the repository snapshot and routes it by file name. Each routine produces ParsedDependency records: name, version, ecosystem, file, line, and the raw line. The ecosystem is the label OSV uses to know which version-ordering rules apply.
1Routing is by file name alone: package.json, requirements*.txt, pyproject.toml, pom.xml, build.gradle or .kts, and go.mod. Four ecosystems come out of those six formats.
2npm. Both dependency sections are read. Only leading operator characters are removed, so ^4.17.15 is queried as 4.17.15. A wildcard * is dropped. package.json has no line numbers, so these findings carry none.
3PyPI. A line counts only if it has a name, an operator and a version, so an unpinned requests is skipped here. Nothing is flagged for being unpinned.
4Maven. The pom and Gradle extractors use the same regexes as the Java scanner, with a different consumer: the result is a query, not a table lookup. A property version in a pom is skipped; in Gradle it is sent to OSV as text.
5Go. Direct and indirect requires are both read; module, go, replace and similar directives are ignored. The v prefix is stripped to the semver OSV compares.
The query and response flow
All parsed dependencies, across all manifests, are queried together. The only network host is api.osv.dev; no source code leaves the machine, only package names and versions.
1The scanner counts what it parsed and reports progress as OSV: querying N package(s) across Go, Maven, PyPI, npm. If nothing parsed, it returns with no network call at all.
2The list is cut into slices of 100. 230 packages make three batches of 100, 100 and 30. They run one after another, not in parallel.
3Each batch is one POST to https://api.osv.dev/v1/querybatch with a queries array. Every query is a package name, an ecosystem and a version. A fresh HTTP client with a 30 second timeout is made per batch.
4OSV answers with a results array in the same order as the queries. The scanner pairs each result with its dependency by position and keeps only those with a non-empty vulns list.
5Offline or slow. A status other than 200, a timeout, a DNS failure or an unreadable reply logs a warning and returns an empty list for that batch. The scan does not abort and does not raise a finding, but it now logs a warning and emits a progress message such as OSV: 12 of 12 package(s) could not be checked, so an offline run no longer looks like a clean one.
From OSV record to finding
For each dependency that matched, every vulnerability record becomes at most one finding. The example below is illustrative; it lists only the fields this scanner reads.
1OSV returns one record per vulnerability. The scanner reads these fields from it: id, aliases, summary (or details), database_specific.severity, severity[], affected[].ecosystem_specific.severity, and the fixed events of affected[].ranges[].
2Dedup. Findings are keyed on name:version:id. If the same package at the same version appears in two manifests, only the first is kept.
3Severity. The first match wins: database_specific.severity (CRITICAL, HIGH, MEDIUM, LOW; MODERATE maps to Medium), then the severity[] score, which may be a plain number or a CVSS v3.x vector whose base score is computed from its metrics, then ecosystem_specific.severity on an affected entry, then a default of Medium.
4Title and text. The first alias that starts with CVE- is added to the title. The description carries the summary cut to 200 characters, the package, the OSV id, all CVE aliases and the osv.dev link.
5Fix. Only ECOSYSTEM and SEMVER ranges of the affected entry that matches the package and ecosystem count (never GIT commit SHAs). The fixed event of the range that contains the installed version gives Upgrade <name> to <version> or later; failing that, the smallest fixed version above the installed one. With none, the text is Upgrade <name> to the latest version.
Why it matters
A hand-written table goes stale the day it is written. The README describes this scanner as real-time lookups against OSV.dev (200K+ vulnerabilities), with no hardcoded CVE list and no API key, so a new advisory is picked up without a release of isitsecure. Your application is mostly other people's code, and a bug in any dependency is your bug.
Equifax (2017). 147 million records were stolen through an unpatched Apache Struts dependency (CVE-2017-5638). The patch had been available for two months, and the settlements cost $700 million. Log4Shell (2021), CVE-2021-44228 in Log4j, allowed remote code execution from a crafted log message and affected hundreds of thousands of applications.
Vulnerable vs fixed
// package.json: lodash 4.17.15 matches GHSA-35jh-r3h4-6jhm
"lodash": "^4.17.15"
// go.mod: queried as the Go ecosystem, version 0.17.0
golang.org/x/net v0.17.0// package.json: the version named in "Upgrade lodash to 4.17.21 or later"
"lodash": "^4.17.21"
// go.mod: take the version each finding's fix line names
golang.org/x/net vX.Y.ZLimits and what it stays quiet about
| Stage | Value in the code |
|---|---|
| Endpoint | https://api.osv.dev/v1/querybatch, no API key |
| Batch size | 100 queries per request, sent sequentially |
| Timeout | 30 seconds per request |
| Ecosystems sent | npm, PyPI, Maven, Go |
| Severity order | database_specific.severity, then severity[] (score or CVSS v3 vector), then ecosystem_specific.severity, then Medium |
| Confidence | 0.95 on every finding |
Only the four ecosystems above are ever queried. The README row lists Rust among the covered ecosystems, but the code has no extractor for Cargo.toml or Cargo.lock, so Rust dependencies are not looked at. Only direct, pinned versions in the manifest are read; transitive dependencies and lockfiles are not. A network failure leaves no trace in the report. An npm range such as >=1.0.0 <2.0.0 has only its leading operator removed and is sent as 1.0.0 <2.0.0. The npm scanner also queries OSV for package.json, so the same vulnerability can appear from both scanners before triage merges them.