Documentation

Send what's installed on a server. Get back which CVEs apply, whether a fix exists, what the vendor says, and what to do first.

Quick start

  1. Sign up with your email and click the link we send.
  2. In the dashboard, create an API key and copy it (shown once).
  3. On a Linux server (Ubuntu, Debian, RHEL, AlmaLinux or Rocky Linux), run:
export CVEFOCUS_KEY=cvf_live_…
{ cat /etc/os-release; uname -r; if command -v dpkg-query >/dev/null; then dpkg-query -W -f='${Package} ${Version} ${Architecture} ${source:Package}\n'; else rpm -qa --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH} %{SOURCERPM}\n'; fi; } \
  | curl -sS "https://api.cvefocus.com/v1/check?format=text" \
      -H "Authorization: Bearer $CVEFOCUS_KEY" \
      -H "Content-Type: text/plain" --data-binary @-

Collecting an inventory

The command lists installed packages with their versions and source package, plus /etc/os-release and the running kernel. It is read-only and sends no configs, hostnames, IPs or secrets.

Any Linux

{ cat /etc/os-release; uname -r; if command -v dpkg-query >/dev/null; then dpkg-query -W -f='${Package} ${Version} ${Architecture} ${source:Package}\n'; else rpm -qa --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH} %{SOURCERPM}\n'; fi; } > inventory.txt

Ubuntu / Debian

{ cat /etc/os-release; uname -r; dpkg-query -W -f='${Package} ${Version} ${Architecture} ${source:Package}\n'; } > inventory.txt

RHEL / Alma / Rocky

{ cat /etc/os-release; uname -r; rpm -qa --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH} %{SOURCERPM}\n'; } > inventory.txt

Windows

# Coming soon: Windows support (OS build + installed programs).

macOS

# Coming soon: macOS support (sw_vers + Homebrew).

We also accept dpkg -l output, plain name version lines (we look up the source package for you), JSON, and Debian purls:

{
  "os": { "id": "ubuntu", "version": "24.04" },
  "packages": [
    { "name": "openssl", "version": "3.0.13-0ubuntu3.16", "source": "openssl" },
    "pkg:deb/ubuntu/curl@8.5.0-2ubuntu10.6?distro=ubuntu-24.04"
  ]
}

Supported now: Ubuntu 20.04 and later, Debian 11 and later, Red Hat Enterprise Linux 7–10, AlmaLinux and Rocky Linux 8–10. Next: Amazon Linux, Alpine, language packages, then Windows and macOS.

RHEL family today: results cover every CVE with a released security advisory (RHSA/ALSA/RLSA), with the fixed version and the dnf command. CVEs Red Hat has not fixed yet (affected, will not fix) are not listed for these systems yet; the response says so in warnings. Results are for standard RHEL minor releases, not EUS/AUS streams.

Calling the API

POST https://api.cvefocus.com/v1/check with header Authorization: Bearer cvf_live_…. Send the inventory as text/plain or application/json, up to 5 MB.

Responses are JSON, for scripts and integrations. In a terminal, add ?format=text for a readable report instead:

curl -sS "https://api.cvefocus.com/v1/check?format=text" -H "Authorization: Bearer $CVEFOCUS_KEY" --data-binary @inventory.txt

Or pretty-print the JSON with jq: … | jq .decisions.

curl

curl -sS https://api.cvefocus.com/v1/check \
  -H "Authorization: Bearer $CVEFOCUS_KEY" \
  -H "Content-Type: text/plain" \
  --data-binary @inventory.txt

Python

import os, requests

resp = requests.post(
    "https://api.cvefocus.com/v1/check",
    headers={"Authorization": "Bearer " + os.environ["CVEFOCUS_KEY"],
             "Content-Type": "text/plain"},
    data=open("inventory.txt", "rb"),
    timeout=60,
)
resp.raise_for_status()
for d in resp.json()["decisions"]:
    print(d["priority"], d["title"])

Node.js

import { readFile } from "node:fs/promises";

const res = await fetch("https://api.cvefocus.com/v1/check", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CVEFOCUS_KEY}`,
    "Content-Type": "text/plain",
  },
  body: await readFile("inventory.txt"),
});
const result = await res.json();
for (const d of result.decisions) console.log(d.priority, d.title);

PHP

<?php
$ch = curl_init("https://api.cvefocus.com/v1/check");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("CVEFOCUS_KEY"),
        "Content-Type: text/plain",
    ],
    CURLOPT_POSTFIELDS => file_get_contents("inventory.txt"),
]);
$result = json_decode(curl_exec($ch), true);
foreach ($result["decisions"] as $d) echo $d["priority"], " ", $d["title"], PHP_EOL;

Ruby

require "net/http"
require "json"

uri = URI("https://api.cvefocus.com/v1/check")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch("CVEFOCUS_KEY")}"
req["Content-Type"] = "text/plain"
req.body = File.read("inventory.txt")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
JSON.parse(res.body)["decisions"].each { |d| puts "#{d["priority"]} #{d["title"]}" }

C#

using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("CVEFOCUS_KEY"));
var body = new StringContent(File.ReadAllText("inventory.txt"), System.Text.Encoding.UTF8, "text/plain");
var res = await http.PostAsync("https://api.cvefocus.com/v1/check", body);
res.EnsureSuccessStatusCode();
using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
foreach (var d in doc.RootElement.GetProperty("decisions").EnumerateArray())
    Console.WriteLine($"{d.GetProperty("priority")} {d.GetProperty("title")}");

Go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	f, _ := os.Open("inventory.txt")
	defer f.Close()
	req, _ := http.NewRequest("POST", "https://api.cvefocus.com/v1/check", f)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("CVEFOCUS_KEY"))
	req.Header.Set("Content-Type", "text/plain")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var out struct {
		Decisions []struct{ Priority, Title string } `json:"decisions"`
	}
	json.NewDecoder(res.Body).Decode(&out)
	for _, d := range out.Decisions {
		fmt.Println(d.Priority, d.Title)
	}
}

Java

import java.net.URI;
import java.net.http.*;
import java.nio.file.Path;

public class CveFocusCheck {
    public static void main(String[] args) throws Exception {
        var request = HttpRequest.newBuilder(URI.create("https://api.cvefocus.com/v1/check"))
            .header("Authorization", "Bearer " + System.getenv("CVEFOCUS_KEY"))
            .header("Content-Type", "text/plain")
            .POST(HttpRequest.BodyPublishers.ofFile(Path.of("inventory.txt")))
            .build();
        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

PowerShell

$headers = @{ Authorization = "Bearer $env:CVEFOCUS_KEY" }
$result = Invoke-RestMethod -Method Post -Uri "https://api.cvefocus.com/v1/check" -Headers $headers `
  -ContentType "text/plain" -Body (Get-Content -Raw inventory.txt)
$result.decisions | Format-Table priority, title

Other endpoints: GET /v1/cve/{id} (any published CVE: description, severity, exploitation, the products the vendor reported, and each distribution's status), GET /v1/usage, and GET /v1/status (data freshness, no key needed). Full details in the API reference.

Reading the response

{
  "status": "ok",
  "os": { "id": "ubuntu", "version": "24.04", "kernel": "7.0.0-1012-aws" },
  "summary": { "packages": 837, "products": 486, "affected_products": 3, "findings": 4,
               "by_priority": { "act_now": 2, "this_week": 1, "plan": 0, "monitor": 1 } },
  "decisions": [
    { "priority": "act_now",
      "title": "Upgrade openssh to 1:9.6p1-3ubuntu13.3",
      "detail": "Fixes 1 CVE. Known exploited: CVE-2024-6387. Highest exploit likelihood: CVE-2024-6387 (EPSS 0.93).",
      "command": "sudo apt-get install --only-upgrade openssh-client openssh-server",
      "package": "openssh", "cves": ["CVE-2024-6387"] }
  ],
  "findings": [
    { "cve": "CVE-2024-6387", "package": "openssh", "binaries": ["openssh-client", "openssh-server"],
      "installed": "1:9.6p1-3ubuntu13.2", "status": "affected", "priority": "act_now",
      "fix": { "available": true, "version": "1:9.6p1-3ubuntu13.3", "requires": null, "command": "sudo apt-get …" },
      "severity": { "cvss": 8.1, "vector": "CVSS:3.1/AV:N/AC:H/…", "distro_priority": "high" },
      "exploited": { "kev": true, "kev_added": "2024-07-01", "kev_due": null, "ransomware": false },
      "epss": { "score": 0.93, "percentile": 0.99 },
      "vendor": { "name": "Ubuntu", "status": "fixed", "reason": null, "source": "vex" },
      "summary": "A signal handler race condition was found in OpenSSH's server (sshd)…",
      "url": "https://ubuntu.com/security/CVE-2024-6387" }
  ],
  "ok": ["bash", "curl", "glibc", "…"],
  "warnings": [],
  "data_as_of": { "ubuntu": "2026-10-29T14:00:12Z", "ubuntu-vex": "…", "kev": "…", "epss": "…" },
  "usage": { "products_today": 486, "products_limit": 500, "calls_today": 1, "plan": "free" }
}

Priorities

Deterministic rules, no AI in the verdict path:

PriorityWhen
act_nowOn CISA's Known Exploited Vulnerabilities list, or EPSS ≥ 10% (likely to be exploited in the next 30 days). Also: a newer kernel is installed but not running.
this_weekCVSS ≥ 7, or the distribution rates it high or critical.
planCVSS ≥ 4, or the distribution rates it medium.
monitorEverything else, including CVEs the vendor decided not to fix (unless exploited).

Vendor status

Every finding carries what the vendor (here, the distribution) says, from its own VEX statements where available:

vendor.statusMeaning
fixedA fixed build is released; fix.version says which.
affectedAffected; the vendor intends to fix it but hasn't yet.
under_investigationThe vendor hasn't finished triage.
will_not_fixThe vendor reviewed it and decided not to fix it in this release; reason says why.
unknownRHEL module packages: the vendor fixed it only in a newer module stream (e.g. nodejs:24) and hasn't said whether your stream is affected. fix.requires is module-stream-switch; priority is capped at plan unless the CVE is known exploited.

When the vendor says a package is not affected, the CVE doesn't appear in your results at all.

Limits

Plans are metered by distinct products per UTC day, counting only products we hold vulnerability data for. A product is a source package: openssl, libssl3t64 and libssl-dev count once. Packages no distribution has ever published a CVE for are free, and re-checking products you've already checked today is free, so hourly cron or every CI run costs nothing extra.

A typical Ubuntu server has 400–900 source packages, of which roughly a third have vulnerability data, so a server usually counts 150–350 products. The free plan includes 500 products per day. There is also a limit of 120 requests per minute per key. Every response includes usage, and summary.products_with_data shows what a request counts.

Errors

StatusMeaning
401Missing, unknown or revoked API key.
413Inventory larger than 5 MB.
422No packages found in the body; detail.hint explains.
429Daily product limit reached (detail has the numbers) or more than 120 requests per minute.

Daily checks

Save as /usr/local/bin/cvefocus-check:

#!/bin/sh
# /usr/local/bin/cvefocus-check — writes the latest result to /var/tmp/cvefocus.json
{ cat /etc/os-release; uname -r; if command -v dpkg-query >/dev/null; then dpkg-query -W -f='${Package} ${Version} ${Architecture} ${source:Package}\n'; else rpm -qa --qf '%{NAME} %{EPOCHNUM}:%{VERSION}-%{RELEASE} %{ARCH} %{SOURCERPM}\n'; fi; } \
  | curl -sS --fail-with-body https://api.cvefocus.com/v1/check \
      -H "Authorization: Bearer $(cat /etc/cvefocus.key)" \
      -H "Content-Type: text/plain" --data-binary @- > /var/tmp/cvefocus.json

Then:

sudo chmod 755 /usr/local/bin/cvefocus-check
sudo sh -c 'umask 077; printf %s "cvf_live_YOUR_KEY" > /etc/cvefocus.key'
echo '15 6 * * * root /usr/local/bin/cvefocus-check' | sudo tee /etc/cron.d/cvefocus

Privacy

We never store your inventories, results, IP addresses or request logs. See exactly what we store.