Snyk's free tier limits you to 100 tests per month. GitHub Advanced Security is bundled with a $49/user/month seat. Mend's free plan needs a sales call. Meanwhile Google has been quietly running OSV.dev — a free, open, no-API-key vulnerability database that covers more ecosystems than any of them.
This post is a tour of OSV — what it is, where it wins, where it doesn't, and the four sharp edges we hit integrating it.
In the Dockier stack · The OSV integration lives inside the code-analysis Fastify service (TypeScript, Zod schemas, OpenAPI docs auto-generated). It queries OSV's public API for small repos and replicates the daily SQLite dump into Supabase Postgres for repos with 500+ dependencies. The dashboard view is a Vite + React 19 route on Cloudflare Pages, calling the Fastify gateway over a Supabase-JWT-authenticated channel.What OSV actually is
OSV is two things: a public vulnerability database under Apache 2.0, and a machine-readable schema for vulnerability records. The schema is the important bit. Every record names the affected package, the affected version ranges, and (often) the introducing and fixing commits. No CVSS-by-feel, no marketing summaries — just packages, versions, and references.
The database currently aggregates from PyPI, npm, Cargo, Maven, NuGet, RubyGems, Packagist, Go, Hex, Pub, Swift, Bitnami, OSS-Fuzz, RustSec, GitHub Security Advisories, and a few more.
Three things it does well
- Version-range queries. Send a package name and version, get back the vulnerabilities that affect that exact version. No false positives from version ranges that look unaffected once you read the patch.
- Commit-level queries. If you build from source, you can query by commit hash. Most scanners can't do this.
- No API key. No rate-limit headers, no auth, no quota. (There are courtesy limits but you'd have to be hammering it.)
The four sharp edges
1. Date semantics are inconsistent across ecosystems
OSV records have a published date and a modified date. For PyPI advisories these are the GitHub-advisory dates, not the PyPI-release dates. For RustSec they're the advisory authoring dates. If you display "this CVE has been known for X days" you need to normalise. We dropped the date column from our UI until we'd figured out a coherent story.
2. Some ecosystems return alias chains
A vulnerability can have a primary OSV ID, a CVE, a GHSA, and an upstream-specific ID. The same vulnerability might appear in your results 2–4 times if you join records naïvely. We deduplicate by walking the aliases array as a graph and keeping the lowest-prefixed canonical ID.
3. The "affected" field is permissive
Version ranges are expressed as { "introduced": "1.0.0", "fixed": "2.0.0" } — but ecosystems treat semver differently. Cargo prereleases (2.0.0-rc1) sort before 2.0.0; npm 0.1.0-beta can or can't, depending on the comparator. We ended up writing a per-ecosystem semver matcher and pinning it against the OSV examples corpus.
4. There's no severity column
OSV records often include a severity CVSS string, but it's optional and frequently absent for non-CVE-derived advisories. If your UI groups by severity, you need to derive it. We fall back to scoring rules: any vulnerability with a CWE in the "injection" family is treated as High; anything marked "denial of service" without a CWE is Medium; everything else inherits from the upstream advisory if one exists.
What OSV doesn't replace
- Reachability analysis. OSV tells you what's vulnerable in your dependency graph; it doesn't tell you whether your code actually calls the affected function. That's a different layer.
- License compliance. Adjacent topic, different database. Don't expect OSV to flag a GPL leak.
- Container scans. OSV covers application dependencies; for OS packages you still want Trivy or Grype layered on top.
How we wire it into Dockier
We ship OSV.dev as the default dependency scanner on every plan, including Free. We hit the public API directly for small repos; for repos with more than 500 dependencies we use the daily SQLite dump that OSV publishes, replicated into Supabase Postgres so the Fastify service can hit it locally.
// backend/src/services/code-analysis/osv/routes.ts — Fastify + Zod
import { z } from "zod";
import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
import { osv } from "./osv-client";
import { dedupeAliases, deriveSeverity } from "./normalize";
const ScanManifest = z.object({
manifest: z.object({
dependencies: z.array(z.object({
name: z.string(),
ecosystem: z.string(),
version: z.string(),
})),
}),
});
const Finding = z.object({
pkg: z.string(),
version: z.string(),
id: z.string(),
severity: z.enum(["critical", "high", "medium", "low"]),
fixedIn: z.string().optional(),
});
export const osvRoutes: FastifyPluginAsyncZod = async (fastify) => {
fastify.post("/scan-manifest", {
schema: {
tags: ["dep-intel"],
body: ScanManifest,
response: { 200: z.array(Finding) },
},
handler: async (req) => {
const findings: z.infer<typeof Finding>[] = [];
for (const dep of req.body.manifest.dependencies) {
const hits = await osv.query({
package: { name: dep.name, ecosystem: dep.ecosystem },
version: dep.version,
});
for (const h of dedupeAliases(hits)) {
findings.push({
pkg: dep.name,
version: dep.version,
id: h.canonicalId,
severity: deriveSeverity(h),
fixedIn: h.affected[0]?.ranges?.[0]?.events?.find(e => e.fixed)?.fixed,
});
}
}
return findings;
},
});
};That's it. No API key. No rate-limit handling. Just a public database that Google maintains because everyone benefits.
If you're starting a security platform from scratch, OSV is your dependency scanner. Buying Snyk in 2026 to do what OSV does for free is a story you'll tell your CFO later.