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.
- Language: Python 3.10+
- Install:
pipx install cve-dedup-checker - GitHub: github.com/jashidsany/cve-dedup-checker
- PyPI: pypi.org/project/cve-dedup-checker
Why I Built This
The audit workflow for finding a new CVE looks roughly like this:
- Pick a target (a WordPress plugin, an npm package, an open-source web app).
- Audit the source code until you find something exploitable.
- Verify it with a local PoC.
- Check if it has already been disclosed.
- 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:
- Identifier check: does the advisory text contain the target’s slug, package name, or a permutation (e.g.
ele-custom-skinmatches bothele-custom-skinandele custom skin)? - Class check (if
--classwas supplied): does the advisory text mention the same vulnerability class, via a synonym dictionary?xssfolds incross-site scripting,rcefolds inremote code executionandcommand 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.*, oreval. - 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