Before you submit a CVE finding to WPScan, Patchstack, or HackerOne, you really should check whether someone already reported it. I got tired of manually hunting through half a dozen databases for every candidate, so I built a tool that does it in parallel.

cve-dedup-checker is an open-source CLI that queries seven free vulnerability databases at once and classifies the result as clean, possible match, or confirmed duplicate before you file.


Why I Built This

The audit workflow for finding a new CVE looks roughly like this:

  1. Pick a target (a WordPress plugin, an npm package, an open-source web app).
  2. Audit the source code until you find something exploitable.
  3. Verify it with a local PoC.
  4. Check if it has already been disclosed.
  5. If clean, write it up and submit to the appropriate CNA (WPScan, Patchstack, MITRE direct).

Step 4 is where I kept losing time. For every candidate finding, I was opening seven browser tabs:

  • WPScan plugin page
  • Patchstack database
  • NVD keyword search
  • GitHub Security Advisories
  • CISA KEV
  • Exploit-DB
  • CVE.org

Each one has a different search syntax, a different UI, and sometimes a different concept of what counts as a “match.” On a tedious day I’d rush this check, miss a prior disclosure, and waste a CNA’s triage time filing a duplicate. Not a great look, and it wastes their limited bandwidth for actual new findings.

I wanted one command that hits all of them in parallel and gives me a clear yes/no/maybe answer. That’s cve-dedup-checker.


Databases It Queries

All seven are free and require no paid API tier. Most require no auth at all.

Database Auth Notes
NVD (NIST) none REST API 2.0. Keyword and CPE search.
OSV (Google) none Aggregator covering GHSA, PyPA, RustSec, Go, Maven, NuGet, crates.io, more.
GitHub Security Advisories optional Public. GITHUB_TOKEN bumps rate limit from 60/hr to 5000/hr but is not required.
WPScan none Scrapes the public plugin/theme page.
Patchstack none Scrapes the public vulnerability database page.
CISA KEV none Known Exploited Vulnerabilities JSON feed.
Exploit-DB none CSV index mirrored on GitLab.

I deliberately did not include databases that require signup, API tokens, or paid tiers (Wordfence Intelligence v3, Sonatype OSS Index, VulDB). The tool should work for anyone out of the box.


How the Classification Works

For every advisory returned from any source, the tool applies two checks:

  1. Identifier check: does the advisory text contain the target’s slug, package name, or a permutation (e.g. ele-custom-skin matches both ele-custom-skin and ele custom skin)?
  2. Class check (if --class was supplied): does the advisory text mention the same vulnerability class, via a synonym dictionary? xss folds in cross-site scripting, rce folds in remote code execution and command injection, and so on.

Results:

  • Both match → CONFIRMED DUPLICATE (exit code 2)
  • Only identifier matches → POSSIBLE MATCH (exit code 1, manual review needed)
  • Neither matches → CLEAN (exit code 0)
  • No sources responded at all → INCONCLUSIVE (exit code 3)

This is deliberately conservative. The tool flags things you might have missed and you make the final call. It will never silently drop a hit you should see.


Usage

Basic check

$ cve-dedup-checker wp-plugin wpforms-lite --class xss

Querying vulnerability databases for wp-plugin 'wpforms-lite'...
Target:   wp-plugin  wpforms-lite
Class:    xss
Sources:  5/5 responded

  Source       Status  Hits  Time   URL
  cisa-kev     clean      0   98ms  ...
  exploit-db   clean      0 5102ms  ...
  nvd          hits       5  140ms  ...
  patchstack   hits       3 5404ms  ...
  wpscan       hits       1  388ms  ...

Confirmed matches
  nvd  CVE-2020-10385  medium  Cross-site scripting in WPForms Lite...

Possible matches (review)
  ... additional related hits you should read ...

>>> STATUS: CONFIRMED DUPLICATE

Five sources responded, three returned hits, and one is a clear product+class match. That’s a confirmed dupe. Move on to the next candidate.

Seven target types

cve-dedup-checker wp-plugin <slug>
cve-dedup-checker wp-theme <slug>
cve-dedup-checker npm <package>
cve-dedup-checker pypi <package>
cve-dedup-checker github <owner/repo>
cve-dedup-checker cpe <cpe-2.3-string>
cve-dedup-checker keyword "<free text>"

Three output formats

cve-dedup-checker wp-plugin <slug> --output human      # default terminal table
cve-dedup-checker wp-plugin <slug> --output json       # pipe to jq, save to file
cve-dedup-checker wp-plugin <slug> --output markdown   # paste into your writeup

The Markdown mode is particularly handy. I paste its output straight into the “Prior art / duplicate check” section of my CVE writeups. It produces a ready-to-go table with source, status, and URLs.

Pipeline use

The exit codes make scripting easy:

#!/bin/bash
for slug in "${CANDIDATE_PLUGINS[@]}"; do
  if cve-dedup-checker wp-plugin "$slug" --class xss --output json > /dev/null; then
    echo "$slug: clean, worth auditing"
  fi
done

Watch Mode

During an active audit, you might want to keep an eye on whether a new advisory lands for your target before you submit. The --watch flag re-runs the check on a fixed interval and prints only what’s changed since the last run.

cve-dedup-checker wp-plugin my-target --class broken-access-control --watch 30m
  • Supported units: s, m, h, d (e.g. 300s, 15m, 2h, 1d).
  • Minimum interval is 60 seconds to stay polite to the free upstream APIs.
  • First run establishes a baseline. Subsequent runs print only new or resolved match URLs.
  • State is stored at ~/.local/state/cve-dedup-checker/watch/.
  • Ctrl+C exits cleanly.

I use this during a multi-week audit of a larger plugin. If Patchstack or WPScan publishes something that collides with my planned finding mid-audit, the tool tells me the next morning and I pivot.


Security Considerations

Since this is a tool people install and run, I wanted to keep the attack surface minimal.

  • No pickle, no arbitrary deserialization. The local response cache is plain JSON files, not pickled data. This avoids the dependency on diskcache (which has CVE-2025-69872: pickle deserialization from the cache can cause arbitrary code execution if an attacker can write to the cache directory).
  • No subprocess or shell execution. The tool never calls os.system, subprocess.*, or eval.
  • TLS verification stays on. httpx verifies certificates by default, and I don’t disable it.
  • User input never becomes the hostname. Target slugs are used only as URL path segments (URL-encoded) or query parameters (URL-encoded by httpx). The seven hostnames are hardcoded.
  • No telemetry. The tool makes no network calls outside the seven databases.
  • CI token is scoped to contents: read. A compromised third-party action in the build chain cannot push commits or mint releases.

Full threat model is in SECURITY.md on the repo.


Install

pipx install cve-dedup-checker
cve-dedup-checker --version

pipx puts the cve-dedup-checker command on your PATH automatically. Standard pip install also works if you prefer managing your own venv.

Requires Python 3.10 or later. All platforms (Linux, macOS, Windows).


What Comes Next

A few things I want to add over time:

  • More sources as they expose free/unauth APIs. Most vulnerability DB APIs have been moving toward paid tiers in 2025-2026, so the catalog is narrower than I’d like.
  • Semantic vuln-class matching that understands “authorization bypass via forged JWT” is the same class as “JWT validation failure,” rather than relying on keyword synonyms.
  • Auto-retry with backoff for flaky upstreams like NVD (they throttle aggressively without an API key).

If you use it and find a rough edge, open an issue on the repo. Feedback from other security researchers is what shapes where this goes.


Try It

github.com/jashidsany/cve-dedup-checker · pypi.org/project/cve-dedup-checker

pipx install cve-dedup-checker
cve-dedup-checker wp-plugin your-target --class xss

Jashid Sany - github.com/jashidsany