Daily CVE triage from public sources - no server, no paid feeds. MetaScraper collects new vulnerabilities from EUVD, NVD, CISA KEV, CIRCL, Red Hat and OSV, adds EPSS, matches them against your products and SBOMs (with VEX) and ranks them by a triage score. A workbench keeps the entries you work on, with notes, own CVSS rating, triage deadlines, reminders and a "buzz" indicator for vulnerabilities that go viral. PowerShell scripts plus a desktop GUI for Windows and Linux.
Quick start
- Download MetaScraperGui for Windows or Linux from the
Releases page and unpack it -
metascraper.ps1is included (or build it yourself, see 2.4). - Start
MetaScraperGui.exe(Linux:./MetaScraperGui, needs PowerShell 7) and enter your keywords on the Configuration tab - or create an inventory from your SBOMs on the SBOM Tools tab. - Click Run: the results open on the Results tab; put what needs a decision on the Workbench.
| Workbench with triage deadlines | Configuration |
|---|---|
![]() |
![]() |
1 Run ──► 2 Review (Results) ──► 3 Workbench ──► 4 Monitor & triage ──► 5 Report & hand over
- Run - Configuration: time window (
DaysToLookBack, e.g. 2 days), your keywords and/or the inventory from your SBOMs (SBOM Tools). Click Run; the coloured source indicators in the header and the Run log show progress. Every run is added to the results history, so several runs a day add up and always show the newest state. - Review on Results - the list is sorted by Triage (0-100: risk from CVSS, EPSS and known exploitation,
weighted by how well the entry matches your products). Per row:
- Severity (colour of the row), CVSS and the vector, EPSS % (probability of exploitation in 30 days), Appl. (applicability: keyword or SBOM component, version affected / not affected), Impact (e.g. RCE pre-auth), flame = known exploited (KEV), bug = public PoC;
- narrow it down with the chips (Known exploited, Matches only, Changed only), the thresholds or the search
(
vendor:acme cvss>=9 is:rce); the detail below explains the score, the CVSS vector and the matched component. - The Dashboard gives the overview (severity, sources, risk load, weaknesses).
- Push to the Workbench - select what needs a decision (Ctrl/Shift for several) and click Add to workbench. Each entry gets a triage deadline from its urgency (default: known exploited 4 h, critical 8 h, high 1 day).
- Monitor and triage on the Workbench -
- the Due column and the tab badge show what is due or overdue; Remind... sets a follow-up date;
- Live refresh updates EPSS, KEV and the NVD score without a new run, Buzz shows whether an entry goes viral (media, trade press, BSI / CISA warnings);
- write notes (date and time are added), set your own severity or an environmental CVSS vector, adjust the deadline by hand if needed;
- decide: Archive... with a reason (handed over, no action needed or your own text) ends the deadline; the note log keeps the reasoning.
- Report and hand over -
- Workbench: Markdown copies the entries shown (with deadline, notes and decision) for a ticket, mail or the stand-up; CSV... saves them for a spreadsheet. Only some entries: select them (Ctrl/Shift) and use Export selected to CSV... or Copy selected as Markdown in the right-click menu;
- Results: Export view to CSV... saves the filtered list; the script also writes an HTML report and JSON / CSV files into the run folder;
- Results: Auto Report writes a compact, pre-filtered vulnerability report of the latest run for the triage
round (
metascraper_report_<date>_<time>.htmlin the run folder, opened in the browser) - see below.
Free, fresh and reliable vulnerability information for IT operations and security teams, without paid feeds.
The repository has two parts:
- CVE Resource Lists: a curated list of security advisory pages, RSS feeds and vulnerability databases, so you can quickly find the place where a vendor publishes its advisories.
- MetaScraper tool set: PowerShell tools that pull recent vulnerabilities from several public databases (EUVD, NVD, CISA KEV, CIRCL, Red Hat), add EPSS scores, normalize them into one list and filter them for the products you actually run, optionally driven by your SBOMs.
The MetaScraper is built for daily use from a desktop or workstation: it only calls public APIs and needs no server, database or tool landscape (unlike Vulnerability-Lookup or Dependency-Track installations).
Note: MetaScraper (part of CVE-Resources) is a CVE triage tool set - it is not related to the npm package
metascraper(web page metadata).Disclaimer: MetaScraper is a triage aid. Scores, matches and severities are computed from public data that can be incomplete, late or wrong; they do not replace the vendor's advisory or your own assessment. Provided "as is", without warranty (see LICENSE).
Finding up-to-date vulnerability information is still time consuming. Information is spread across national databases, CERTs and hundreds of vendor portals, and many vendors publish advisories only on their own website. Those vendors are not CVE Numbering Authorities, or publish late or incompletely to NVD/EUVD. This project lets IT staff run vulnerability OSINT for security life-cycle management at no additional cost.
The initial focus is enterprise IT, including industrial automation, related software and free resources.
| File | Purpose |
|---|---|
cve_resources_list_alpha_sorted.JSON |
Resource list (primary format) |
cve_resource_list_alpha.csv |
Resource list as CSV |
cveresources_fetched_<DD-MM-YYYY>.html |
Resource list as HTML, with HTTP status of each link |
metascraper.ps1 |
Core: fetches, normalizes, filters and reports vulnerabilities |
config.toml |
Example configuration for metascraper.ps1 |
MetaScraperGUI.ps1 |
Older Windows GUI (WPF) for metascraper.ps1, kept for existing setups - successor: gui/ |
Start-MetaScraperGUI.cmd |
Starter: double-click launcher for the GUI |
SbomFilterExtractor.ps1 |
SBOM tooling: builds the keyword filter from CycloneDX SBOMs |
Start-SbomFilterExtractor.cmd |
Starter: drag & drop an SBOM onto it to run the SBOM tool |
Setup-LocalAI.ps1 |
Sets up a local AI runtime (portable Ollama + a small model) for the planned AI review, see 2.5 |
gui/ |
Cross-platform GUI (Avalonia, Windows + Linux, preview) - successor of MetaScraperGUI.ps1, see 2.4 |
samples/test-sbom.cdx.json |
Synthetic CycloneDX 1.6 test SBOM (27 components with known vulnerable versions, generic names, umbrella vendors, CPE-only entries) |
samples/demo-sbom-250.cdx.json |
Synthetic CycloneDX 1.6 demo SBOM, 250 components: Debian 12 base image (glibc, libstdc++, OpenSSL, curl, vim, nano, OpenSSH, sudo, nginx, Redis, PostgreSQL), runtimes (OpenJDK, Python, PHP, Go, Node.js) and typical libraries (Spring, Jackson, Bouncy Castle, Log4j, Netty, React, Express, Django, Laravel, gin ...), partly deliberately old versions - for trying out the SBOM Tools and the inventory matching |
CVE-resources-http-status-check.pl, JSON2HTM_Formatter.pl, scrappi.pl |
Perl helpers to maintain and monitor the resource list |
EUVD-Scraper.ps1, metascraperV14.ps1 |
Older versions of the scraper, kept for reference |
LICENSE, THIRD-PARTY-NOTICES.md |
Apache 2.0 license of this project; licenses of the libraries in the GUI builds |
SECURITY.md |
How to report a vulnerability in the tools (privately) |
CONTRIBUTING.md, CHANGELOG.md |
How to contribute (build, smoke test, rules); changes per release |
.github/ |
Issue templates, CI (build + headless smoke test) and release workflow (tag v1.2.3 -> Windows / Linux builds) |
docs/images/ |
Screenshots for this README and the social preview image (sample data) |
When a vulnerability hits a product you run, the national databases are often not enough: entries come late, lack vendor detail, or don't exist at all for vendors that publish only on their own portal. The list answers one question: "Where does vendor X publish its security advisories?"
It currently contains 142 resources:
- 106
supplier-specificresources: advisory pages, PSIRT portals and feeds of individual vendors and projects (e.g. ABB, Acronis, Apache ActiveMQ, Cisco, Siemens, Ubuntu ...). These are often the only or the earliest source for that vendor. - 36
meta-databaseresources: aggregators and databases covering many vendors (e.g. NIST NVD, CIRCL, OpenCVE.io), several of them with PoC/exploit information.
- Look up a vendor: open the HTML file in a browser, or the CSV in Excel, and search for the vendor
name. The
locationcolumn links straight to the advisory page or feed. - Subscribe: many entries are RSS feeds or mailing lists. Add the vendors you run to your feed reader.
- Automate: use the JSON file in your own tooling, or monitor the pages for changes with
scrappi.pl. - Cover the gap: for vendors in your inventory that are missing from NVD/EUVD, check their
supplier-specificentry regularly or monitor it with Scrappi.
Each entry has four fields:
| Field | Required | Description |
|---|---|---|
supplier |
yes | Information provider and/or product supplier |
location |
yes | URL, RSS feed, etc. |
comment |
no | Short description of the resource |
type |
yes | supplier-specific (direct vendor/project source) or meta-database (aggregator) |
Formats
- JSON (
cve_resources_list_alpha_sorted.JSON): array of objects with the four fields, sorted by supplier. This is the primary file; the other formats are derived from it. - CSV (
cve_resource_list_alpha.csv): comma-separated, with a header row, in the ordersupplier,location,comment,type. - HTML (
cveresources_fetched_<DD-MM-YYYY>.html): generated by the Perl helpers below and refreshed from time to time.
[
{ "supplier": "ABB",
"location": "https://global.abb/group/en/technology/cyber-security/alerts-and-notifications",
"comment": "ABB Cybersecurity Notifications and email-feed",
"type": "supplier-specific" }
]Other machine-readable formats (YAML, XML, bookmarks, database dumps) are welcome contributions.
Please add resources that are
- meaningful,
- validated to be not malicious,
- up to date and maintained,
- not already part of the list,
- reliable and trustworthy,
- free to access.
Please remove or replace resources that are malicious, pay-walled or no longer maintained.
All helpers need a recent Perl 5 (tested with Strawberry Perl 5.38.2, Windows and Linux) with the
perl executable and its libraries on the PATH. They read the current JSON list from this repository on GitHub.
Checks every location URL and writes cveresources_fetched_<DD-MM-YYYY>.html with the HTTP status of each link.
- 200: OK
- 403: authorization may be required
- 404 / 500: the resource has moved or is broken
perl CVE-resources-http-status-check.pl
Generates the plain HTML version of the list (cveresources_fetched_<DD-MM-YYYY>.html) without status checks.
perl JSON2HTM_Formatter.pl
A minimal change detector for static advisory pages. It fetches every location, stores an MD5 checksum
in a local SQLite database (urls.db) and logs changes to scrabbi_change.log and errors to
scrabbi_error.log. A change means the page content changed, for example because new advisories were
added. It also records fetch times, which reveals slow resources.
It does not work for dynamically generated pages, such as GitHub advisory boards or web database front ends. It can also monitor any JSON list of static pages, e.g. to detect defacement or hijacking.
Every morning, answer the question: "Which new vulnerabilities from the last N days affect the products we use, and how critical are they?"
How the parts work together (the daily steps in the GUI: see Typical workflow):
SBOMs (CycloneDX, VEX) ──► SBOM Tools / SbomFilterExtractor.ps1 ──► inventory + keywords
│
▼
EUVD · NVD · CISA KEV · CIRCL (incl. GCVE) · Red Hat ──► metascraper.ps1 ──► results of the run
+ EPSS · OSV · GitHub advisories (enrichment) ▲ (JSON / CSV / HTML)
│ │
MetaScraperGui: configure, run ───────┘ ▼
Results (history) ──► Workbench ──► Markdown / CSV for tickets and reports
| Tool | When to use it |
|---|---|
MetaScraperGui (gui/, Windows + Linux) |
Everyday use: configure, run, review, workbench, dashboard, SBOM Tools - see 2.4 |
metascraper.ps1 |
The engine behind the GUI; also on its own for scheduled or scripted runs, any OS with PowerShell |
SbomFilterExtractor.ps1 |
Inventory and keywords from your SBOMs (also built into the GUI's SBOM Tools tab) |
MetaScraperGUI.ps1 + Start-MetaScraperGUI.cmd |
Older Windows-only GUI (WPF), kept for existing setups; new features go into MetaScraperGui |
- PowerShell: 7.x recommended. Windows PowerShell 5.1 can be selected in the GUI.
- GUI: MetaScraperGui runs on Windows and Linux (self-contained, no .NET install needed); the older
MetaScraperGUI.ps1is Windows only (WPF). - Network: internet access to the APIs below; a proxy is supported.
- Skills: running scripts; a basic understanding of CVEs, CVSS and EPSS.
- Disk: a few MB per run for reports and logs (more with debug logging); the GUI's results history grows to some tens of MB with many runs (entries not seen for 30 days drop out).
| Indicator | Source | API |
|---|---|---|
| EUVD | ENISA European Vulnerability Database | euvdservices.enisa.europa.eu/api/search (paginated, date range, minimum score, optional exploited-only) |
| KEV | CISA Known Exploited Vulnerabilities - the official catalog, else its GitHub mirror, else the local copy of the last download, else the third-party mirror KEVin (GTFKD); KEVin also adds PoC flags when reachable | cisa.gov/.../known_exploited_vulnerabilities.json, raw.githubusercontent.com/cisagov/kev-data, kevin.gtfkd.com/kev/recent |
| RHS | Red Hat Security Data API | access.redhat.com/hydra/rest/securitydata/cve.json (after=<date>, paginated) |
| CIRCL | CIRCL Vulnerability-Lookup (Luxembourg) | vulnerability.circl.lu/api/vulnerability/ (paginated, since, sorted by update date) |
| NVD | NIST National Vulnerability Database | services.nvd.nist.gov CVE API 2.0 (date range, paginated) |
| OSV | OSV.dev, Google (confirmation, only with an SBOM inventory) | api.osv.dev/v1/querybatch + /v1/vulns/{id} (no key; 1 batch call per 1000 components, cached details) |
| GHSA | GitHub Advisory Database (targeted re-query, only with an SBOM inventory) | api.github.com/advisories?cve_id=... (no key: 60 calls/hour; optional token), then OSV /v1/vulns/CVE-... |
| EPSS | FIRST Exploit Prediction Scoring System (enrichment) | api.first.org/data/v1/epss (all CVEs, 100 per call; has priority) |
CIRCL Vulnerability-Lookup aggregates several feeds, so its records come in different formats: CVE JSON 5,
OSV (GitHub advisories, PyPI, ...) and CSAF vendor advisories. All of them are normalized. OSV records often
carry only a CVSS vector without a numeric score; these count as NOT RATED (see below) and are removed by
MinimumScore unless KeepUnrated is set.
EPSS is not a source of new entries. After filtering, every CVE is looked up at FIRST, and FIRST values take priority over values delivered by the sources. A source value is kept only where FIRST has no score; EUVD is the only source that delivers EPSS itself, and it already uses percent.
EPSS is stored machine-readably as a number plus separate fields, so it works in Excel and scripts:
| Field (JSON/CSV report) | Example | Meaning |
|---|---|---|
EPSS (vul_epss) |
0.95 |
Number, always in percent (0–100) for all entries |
EpssUnit (vul_epss_unit) |
% |
Unit of the value |
EpssSource (vul_epss_source) |
FIRST / EUVD |
Origin of the value |
EpssPercentile (vul_epss_percentile) |
35.5 |
FIRST percentile in percent: share of all CVEs with a lower EPSS |
FIRST delivers probabilities (0–1); they are converted to percent. A value of 0.95 therefore means
0.95 %, not 95 %. The HTML report and the GUI show the unit (0.95 %) and the source.
The tools fetch all data at run time from the public services below; the repository contains no copies of these databases (only sample reports created with the tools). Each service has its own terms of use - if you run the tools or pass on their reports, please respect them and keep the attribution. The cross-platform GUI additionally queries the services of the Buzz check (GDELT, Hacker News, CIRCL sightings, RSS feeds) and, for Live refresh, FIRST, CISA KEV and NVD directly.
| Source | Attribution / terms |
|---|---|
| NVD (NIST) | This product uses the NVD API but is not endorsed or certified by the NVD. Terms: https://nvd.nist.gov/developers/terms-of-use |
| EUVD (ENISA) | Data from the European Union Vulnerability Database, ENISA - https://euvd.enisa.europa.eu |
| CISA KEV | Known Exploited Vulnerabilities Catalog, U.S. CISA - https://www.cisa.gov/known-exploited-vulnerabilities-catalog (read directly since build 2026-11-11c; the KEVin mirror kevin.gtfkd.com adds PoC flags and is the last fallback) |
| CIRCL Vulnerability-Lookup | Data and sightings from CIRCL, Luxembourg - https://vulnerability.circl.lu (records keep the license of their original feed) |
| Red Hat Security Data | Red Hat Security Data API - https://access.redhat.com/security/data |
| OSV.dev | Open Source Vulnerabilities database - https://osv.dev (records keep the license of the ecosystem source) |
| GitHub Advisory Database | https://github.com/advisories - GitHub API terms apply |
| EPSS (FIRST) | Exploit Prediction Scoring System, FIRST.org - https://www.first.org/epss/ (cite EPSS when you use the scores) |
| GDELT | The GDELT Project - https://www.gdeltproject.org (citation requested) |
| Hacker News | via the HN Search API by Algolia - https://hn.algolia.com/api |
RSS feeds (buzz-feeds.txt) |
Only titles, dates and links are read; the content belongs to the publishers (BSI, CERT-Bund, CISA, heise, BleepingComputer ...) |
Please check the current terms of a service before heavy or commercial use; the tools stay within the documented rate limits (pauses and retries are built in).
- Fetch: queries all five sources for the last
DaysToLookBackdays. - Normalize: converts every record into one common object (ID, CVE, source, URL, date, CVSS, EPSS, vendor, product, description, known-exploited, PoC, raw data).
- Filter and enrich: removes entries below
MinimumScoreand adds missing EPSS values. - Match and triage: checks your keywords against vendor, product and description, and stores the result in every normalized entry (see Keyword matching and triage score).
- De-duplicate: keeps the entry with the highest triage score per CVE (optional).
- Report: sorts by triage score (then CVSS) and writes an HTML report with severity colouring and links to the sources, plus JSON and CSV.
# built-in defaults
.\metascraper.ps1
# with a TOML configuration
.\metascraper.ps1 -ConfigFile .\config.toml
# configuration plus command-line overrides
.\metascraper.ps1 -ConfigFile .\config.toml -DaysToLookBack 1 -MinimumScore 7.5 -OnlyExploited 1Settings are applied in this order of precedence: command line > TOML file > built-in defaults. The effective configuration, including where each value came from, is written to the log.
If Windows blocks the unsigned script:
powershell.exe -ExecutionPolicy Bypass -File .\metascraper.ps1 -ConfigFile .\config.toml
# for downloaded files, once:
Unblock-File .\metascraper.ps1| Setting | Default | Meaning |
|---|---|---|
DaysToLookBack |
10 | Look-back period. Some APIs cap it (e.g. 120 days / 2000 entries). |
MinimumScore |
5 | Entries with a lower CVSS score are dropped (0–10). |
StateFile |
"" | State of the previous run for the change markers (empty: next to the inventory, otherwise the working folder). |
DisabledSources |
"" | Sources switched off for the run, e.g. "CIRCL;NVD" (EUVD, KEV, RHS, CIRCL, NVD, EPSS, OSV, GHSA). Reported as [API-STATUS] <source> SKIPPED switched off. EPSS off: no FIRST lookup; OSV off: no confirmations / late confirmations; GHSA off: the re-query asks OSV only. In both GUIs: click on the source label in the status bar (black with grey text = off). |
IncludeUpdates |
false | Also keep entries published before the time window that were only updated in it (mainly CIRCL); see CVE updates. |
KeepUnrated |
false | Keep entries without a public CVSS score (NOT RATED) although MinimumScore is set; see Entries without a CVSS score. |
OnlyExploited |
0 | 1 = ask EUVD for known-exploited vulnerabilities only. |
ContinueToWindowEnd |
true | The page and entry limits below are stages: when one is reached but the source still has entries in the time window, paging continues (one pause per page) until the window is covered. A [LIMIT] line notes it. Off: stop at the limits; the source then reports PARTIAL (time window not complete). |
MaxEntriesPerSource |
20000 | Safety limit per source and run while continuing (protects the APIs and the run time, e.g. with a very long time window). Reached: [LIMIT] ... entries of the time window are missing and status PARTIAL. 0 = none. |
MaxPages / PageSize / MaxVulnerabilities |
5 / 80 / 1000 | EUVD limits: pages, entries per page (documented max 100, the script uses at most 80), stop after this many entries; 0 = unlimited. Half a second pause between pages after the stage limit. The GUI moves a saved 50 (former default) to 80 once. |
CirclMaxPages / CirclPageSize / CirclMaxVulnerabilities |
5 / 100 / 1000 | Same for CIRCL (max 100 per page). CIRCL allows 20 requests per minute, so the page size decides the time: 10 days (about 13,000 entries) take about 7 minutes with 100, 27 with 25. Read by publication date (default) pages are small; with IncludeUpdates on (read by update date, large CSAF advisories) the script uses at most 25. |
CirclCache / CirclFullRefreshHours |
true / 24 | CIRCL cache: the CIRCL entries are kept between runs (%LOCALAPPDATA%\MetaScraper\circl_cache.json.gz, Linux ~/.local/share/MetaScraper/); a run fetches only what was published since the last one (two hours overlap) - with several runs a day CIRCL takes seconds instead of minutes ([CIRCL] 400 fetched, 13,000 in the cache). Full fetch after CirclFullRefreshHours (0 = always), after a larger time window and when the cache is missing; an incomplete fetch never overwrites the cache. Not with IncludeUpdates. |
NvdMaxPages / NvdPageSize / NvdMaxVulnerabilities |
4 / 500 / 2000 | Same for NVD. The API allows 2000 per page, but large pages often fail; 500 (or 250 x 8) is reliable. A failed page is retried from the same position with half the page size (down to 250), keeping the total of pages x original size. 6 s pause between pages (NVD allows 5 requests per 30 s without key). The console reports when results in the date range were cut off. |
RedhatMaxPages / RedhatPageSize / RedhatMaxVulnerabilities |
1 / 500 / 1000 | Same for Red Hat (at most 1000 per page). Only CVEs public since the look-back date are requested (after=). |
OsvMaxDetailCalls |
500 | Maximum OSV alias lookups per run (cached IDs need none); 0 = unlimited. |
Documented API limits are respected. Page sizes stay below the documented maxima, because full pages failed in the
past: EUVD at most 80 (documented 100), CIRCL 50 (100), NVD 1000 (2000), Red Hat 1000; a higher setting is lowered with
a [LIMIT] line. NVD accepts a publication date range of at most 120 days per query - longer time windows are read
in ranges of 120 days. NVD without API key allows 5 requests per 30 seconds: 6 s pause between requests. While
continuing beyond the stages, EUVD and Red Hat pause 1 s per page. CIRCL allows 20 requests per minute
(429 Too Many Requests - 20 per 1 minute): every CIRCL request waits until 3.1 s have passed since the previous one.
HTTP 429 (rate limit) is retried after the Retry-After time of the server, otherwise after 30 s and 65 s; a page
is never made smaller because of it (that would only mean more requests). Still rate limited after that: the source
stops and reports PARTIAL / ERROR with "rate limit (HTTP 429) - try again later".
The limits protect the APIs (and you from being blocked) and keep runs short. KEV, EPSS and the OSV batch
call need no limit: KEV is limited by date, EPSS and OSV make one call per 100 / 1000 entries of the result set.
Limits are checked after each page, so a run can end slightly above ...MaxVulnerabilities.
| MinMediumScore / MinHighScore / MinCritCVSS | 4 / 5 / 9 | CVSS thresholds for Medium / High / Critical; map them to your internal rating. |
| MinCritEPSS | 10 | EPSS (%) at or above which an entry is rated critical. |
| dedublication | true | Remove duplicate CVEs across sources. Since results format 2 every CVE is one object anyway (see One object per CVE); the option only guards against leftovers. |
| proxy | false | Use the proxy from the https_proxy environment variable (with default credentials). |
| ProxyUser | (empty) | User for a proxy that asks for a login (HTTP 407), e.g. DOMAIN\user. The password is never a parameter or a config value: the script reads it from the environment variable METASCRAPER_PROXY_PASSWORD (set by the GUI for the run only), removes the variable right away and never logs it. Empty = the Windows login (default credentials). |
| LogLevel | troubleshooting | minimal: no log file, only progress and summary lines. troubleshooting: log file EUVD_Debug_<timestamp>.log and detail lines, API responses shortened (response: 25 record(s) ...) - fast, enough for most problems. debug: plus the API responses in detail (large log, slow runs). |
| EnableDebugLogging | true | Older switch, only used when LogLevel is not set: true = debug, false = minimal. |
| EnrichEPSS | true | Look up EPSS for all CVEs at the FIRST EPSS API (FIRST values have priority). |
| DisplayHTML | true | Open the HTML report in the browser when done. |
| DiscLength | 400 | Description length in the HTML report (truncated with "..."). |
| filterString | list | Keywords, ;-separated, case-insensitive, whole-word match against vendor, product and description. |
| priorityString | "" | Keywords, ;-separated, case-insensitive, substring match (e.g. bernetes matches Kubernetes). |
| InventoryFile | "" | sbom_inventory.json from the SBOM tool: structured matching and applicability score. Empty = keywords only. |
| MinApplicability | 0 | Entries below this applicability (0–100) are left out of the HTML report. |
| EnableOSV | true | With an inventory: confirm the components' vulnerabilities at the OSV API (purl + version). |
| EnableReQuery | true | With an inventory: ask GitHub Advisories (then OSV) per CVE for package data of the top text-only matches. |
| ReQueryMax | 50 | Maximum CVEs per run for the re-query (GitHub: 60 calls/hour without token). 0 = off. |
| GitHubToken | "" | Optional GitHub token for the re-query (5000 calls/hour); alternatively the environment variable GITHUB_TOKEN. Use only a fine-grained, read-only token for public repositories - see GitHub token. |
| OsvLookBackDays | 7 | OSV late confirmations: OSV advisories for your components published within this many days are added even outside DaysToLookBack (0 = off). |
Keywords: filterString uses whole-word matching, so spring matches "spring-boot" and "Spring Framework"
but opa does not match "opaque". priorityString matches parts of words; use it for spelling variants.
SbomFilterExtractor.ps1 can generate the filterString for you.
An entry can be matched against your environment in two ways, alone or combined:
- Keywords (
filterString/priorityString): fast and manual, but a single word can hit unrelated CVEs. - SBOM inventory (
InventoryFile, thesbom_inventory.jsonfromSbomFilterExtractor.ps1): structured matching per component (vendor + product, purl, CPE, version). This is much more precise.
Both feed the Applicability score (0–100): how likely the entry concerns your environment.
| Evidence | Applicability |
|---|---|
| OSV confirmed: OSV lists the CVE for exactly this component version | 100 |
| Package/purl of the CVE equals a component (OSV, CVE 5 and CSAF package data via CIRCL, Red Hat fixed rpm packages, re-query) | 95 |
Code repository of the component in CVE 5 affected[].repo (or the re-query), with version check |
85 |
| Code repository of the component (github.com/owner/repo ...) in the CVE's reference URLs | 75 |
Structured vendor + product equal a component (NVD CPE, CVE 5 affected, CSAF products, EUVD products, KEV) |
85 |
| Vendor and product named in the entry's vendor/product fields | 80 |
| Product in the product field, vendor in the description | 65 |
Structured product equals the component's vendor/framework (e.g. CPE sensiolabs:symfony for symfony/http-kernel) |
60 |
| Vendor and product in the description | 55 |
| Product only: in the product field / in the description | 40 / 25 |
Product only with a generic name (console, process, core, platform, or an ordinary English word such as express, next, commander ...) |
5 / 2 |
Generic product name with an umbrella vendor (google + core) as pair |
35 / 25 / 10 |
| Vendor only (umbrella vendor such as google/oracle) | 15 (5) |
| Keyword hit in vendor/product / description only | 60 / 35, + 5 or + 2.5 per additional keyword |
Pair keywords / coordinates (build 2026-11-13c): an entry with | - e.g. microsoft|azure,
grafana labs|grafana, red hat|openshift - hits only when every part occurs in the entry's vendor or product
(normal list: whole words, priority list: substrings). A pair is one coherent coordinate instead of independent words:
microsoft|azure finds the Azure service but not every Windows CVE, grafana labs|grafana the self-hosted Grafana
but not Azure Managed Grafana. A pair hit counts as a vendor/product hit (60); pairs are not subject to the generic
list. The AI review always reviews pair hits (they are coordinates you maintain), single keywords only with its switch.
Shadow value "coherent coordinates" (CoherentApplicability, not used for the triage yet): one keyword hit
counts once - pair 75, vendor/product 60, description only 35 - no bonus per further keyword, because several
words that meet in a foreign product name (microsoft; grafana; azure in "Microsoft Azure Managed Grafana" = 70
today) are no extra evidence. The run log says how many entries it would move ([SHADOW] Coherent coordinates ...);
the GUI computes it live, shows it in the detail pane when it differs, and searches it with coh<50 / coherent>=60.
Once it has proven itself on real data it can replace the current keyword part.
Generic words in the keyword lists are ignored (management, platform, policy, org, websocket,
core, api ... - the list $script:GenericKeywords in metascraper.ps1). They match countless unrelated CVEs.
They are listed on the console ([FILTER] Generic keywords ignored ...); the GUI shows them below the keyword list
and offers Remove generic keywords. The SBOM tool does not produce them any more. In the inventory, generic aliases (e.g. management from
management-api) and generic vendor names (json, java) are not used for matching.
OSV confirmation (EnableOSV, default on, only with an inventory): the purls of all components,
including their versions, are sent to the OSV API in one batch. OSV answers with the vulnerabilities that
affect exactly these versions (GHSA-, PYSEC-, GO-, RUSTSEC-, DEBIAN-CVE- ...), using each ecosystem's
own version rules. The IDs are resolved to their aliases (CVE ...) and cached next to the inventory
(osv_cache.json), so that only changed IDs are fetched again. An entry is OSV confirmed when its ID, CVE or
aliases are in such an alias group. NVD/Red Hat/KEV are matched via the CVE, CIRCL via the GHSA/CVE, and EUVD via
the CVE/GHSA in its aliases; OSV knows no EUVD IDs. Confirmed entries get applicability 100,
VersionStatus = affected, the reason OSV: GHSA-... (= CVE-...) affects <component>, and the field
OsvIds. Distribution records without a fix (Debian, Ubuntu, Alpine ... - DEBIAN-CVE-..., build 2026-11-13d)
are the distribution's open list: for a fresh CVE often not assessed yet (Debian lists the package as unfixed until it
has looked at it). They give 85 and version status unknown (reason "OSV: DEBIAN-... lists ... as open"), not a
confirmation; with a fix version they confirm (100). OSV late confirmations (OsvLookBackDays, default 7): package data (OSV/GHSA) often arrives
days after a CVE was published. If you look at only 1-2 days (DaysToLookBack), the CVE would already be
outside the window when OSV confirms it for your component. Therefore OSV advisories for your components that
were published within the last OsvLookBackDays days but are not in this run's source data are added to the
results as entries with source OSV: applicability 100, VersionStatus = affected, affected versions from OSV,
reason "OSV late confirmation (published N day(s) ago ...)" and the field LateConfirmation (vul_late_confirmation
in the JSON/CSV report). Their CVSS score is computed from the CVSS 3.x vector; without one, the GHSA severity is mapped
to an estimate (CRITICAL 9.0, HIGH 7.5, MODERATE 5.5, LOW 3.0; source in BaseScoreSource). MinimumScore applies;
EPSS is added as for all entries. 0 switches this off. Recommendation: keep 7 even with a short DaysToLookBack.
OSV hits for your components that are older still are written to osv_known_vulnerabilities.json. OSV covers open-source ecosystems only; commercial
products stay with the other sources.
Text hits are checked for plausibility:
- Path segments and URLs do not count: a component name directly next to
/or\(frontend/express/app.js,node_modules/lodash/...) is a file or folder name, not a product mention. This applies to the inventory text matching and to the keywords (script and GUI). Repository URLs are matched separately (see Repository URLs). - One mention is one piece of evidence: for components whose vendor and product have the same name
(
django/django), a single "Django" in the description or product field counts as a product hit only (25 / 40), not as vendor + product (55 / 80). A real pair needs a separate vendor mention or the vendor field. - Coverage of the product field: a product-field hit is scaled by the share of the product name that the
component's names (product and vendor aliases) cover, in 10% steps, per comma-separated product.
Apache Camelforapache/camel-core(aliases apache + camel): 100%, full score.YITH PayPal Express Checkout for WooCommerceforexpress: 10%, so 40 becomes 4. The reason says "(10% of the product name)"; the shadow comparison lists it as "partial product name". - Ordinary English words as package names (
express,next,commander,async,chalk,cookie,moment,sharp,helmet,passport,click,six...) are flagged generic bySbomFilterExtractor.ps1and are on metascraper's list of ignored keywords. Structured matches (purl, OSV, GitHub re-query, repository) are not affected, so a real Express CVE with package data still reaches 100.
Version check: where a source provides structured version ranges (OSV ranges, CVE 5 versions,
NVD CPE versionStart/End, EUVD product_version such as 4.0.0 <4.18.5), the component version from the SBOM
is compared. Inside the range: +5 and VersionStatus = affected. Outside all ranges: capped at 20 and
not affected. Free-text version statements are not parsed.
EUVD product names are compared with and without the vendor prefix ("Apache Camel" and "Camel", vendor "Apache"
from "Apache Software Foundation"); the vendor is used only when EUVD lists exactly one.
Structured verdict wins: a structured not affected (version check or VEX) also beats keyword hits: the
applicability is the inventory score, not max(keyword, inventory). The reason then says
"keyword hit ignored (inventory: not affected)". The GUI applies the same rule when it recalculates.
SBOM VEX: if the SBOM contains VEX statements (vulnerabilities[] with analysis.state),
SbomFilterExtractor.ps1 writes them to sbom_vex.json next to the inventory, and metascraper.ps1 reads that
file automatically. A statement applies when its ID or an alias equals the entry's CVE, ID, OSV IDs or EUVD aliases,
and it concerns the matched components (or the whole product, e.g. a statement for the SBOM's main component):
not_affected,false_positive,resolved: applicability 0,VersionStatus = not affected, reason "VEX: not_affected (justification) - detail ..."; the entry stays visible, its triage drops. VEX wins over OSV.exploitable,in_triage: only noted in the reason.
Separate CycloneDX VEX documents are added with SbomFilterExtractor.ps1 -VexPath (GUI: Separate VEX document on
the SBOM tab). Their affects[].ref is mapped to the SBOM components by bom-ref (also as BOM-Link
urn:cdx:...#ref) or purl; statements whose references match no component are ignored (with a warning).
CSAF vendor advisories (via CIRCL): CIRCL also delivers vendor advisories in CSAF format (Red Hat, SUSE, Siemens,
Cisco ...). Their product tree is read: products with purl or CPE (product_identification_helper), otherwise vendor and
product name from the branches, with the version (product_version) or version range (product_version_range,
also in vers: notation). The product status of the vulnerabilities becomes the version check:
known_affected/first_affected/last_affected: that version (or range) is affected;fixed/first_fixed: for rpm packages "below the fixed build of the same RHEL stream" (as Red Hat below), otherwise that version is not affected;known_not_affected: that version is not affected (wins over the ranges).
A purl match counts 95 (+5 with an affected version), vendor + product 85 - e.g. a Siemens advisory with
vers:intdot/<3.1.2 against siemens/simatic s7-1500 cpu@3.0.1 gives 90 and affected. The product names also
fill the Product field of the CSAF entries. How many CSAF records a run contains depends on CIRCL.
Red Hat fixed packages: the Red Hat API lists the fixed builds per CVE (affected_packages, e.g.
openssl-1:3.0.7-27.el9_4). A component with an rpm purl pkg:rpm/redhat/<name> is compared against the fixed
version of its own RHEL stream (el8, el9 ...; epoch ignored): below it affected, at or above not affected.
Components of another stream stay unknown. Shown as openssl: < 3.0.7-27.el9_4; < 1.1.1k-12.el8_9.
Repository URLs: SbomFilterExtractor.ps1 stores each component's code repository (host/owner/repo from the
externalReferences, the purl qualifier vcs_url, or Go module paths such as github.com/gin-gonic/gin). Only code
hosts count (github.com, gitlab.com, bitbucket.org, codeberg.org), not plain websites. A CVE whose references (NVD,
CVE 5, OSV, EUVD) point to that repository matches at 75; CVE 5 affected[].repo at 85 with the version check.
Targeted re-query (EnableReQuery, default on, only with an inventory): entries that match a component only by
text (no structured data, version unknown, applicability at least 25) get package data per CVE - highest risk
first, at most ReQueryMax (50) per run:
- GitHub Advisories
api.github.com/advisories?cve_id=CVE-...(ecosystem, package, vulnerable range, repository). Without token GitHub allows 60 calls per hour; on HTTP 403/429 the run continues with OSV only (indicator GHSA yellow) - setGitHubTokenor the environment variableGITHUB_TOKENfor 5000 calls per hour. - If GitHub knows nothing: OSV
/v1/vulns/CVE-...and a linked GHSA record.
The entries are then matched again, so the version check decides (affected up to 100, not affected capped at 20);
the reason ends with "[re-query: GHSA GHSA-...]". Results are cached next to the inventory (requery_cache.json:
hits 7 days, "nothing found" 1 day), so a second run needs no calls. Log line: [REQUERY] 50 CVEs checked ....
Dependency scope: components the SBOM marks as transitive (not a direct dependency of the main component,
from the dependency graph) get -5, scope: optional components -10 on the heuristic and structured scores;
the reason says "(transitive dependency -5)". OSV confirmations (100) and VEX are not changed. Without a
dependency graph nothing is deducted.
Shadow comparison ("what do the stricter rules suppress?"): for every entry the script also computes the
applicability it would get without the suppressing rules (ignored generic keywords, generic product names,
umbrella vendors, dependency scope, structured "not affected", VEX). If that is higher, the entry carries
ShadowApplicability and SuppressedHits (e.g. "google/adk@0.5.0: umbrella vendor",
"generic keyword: docker") - shown in the GUI detail pane and the CSV/JSON (vul_suppressed_hits,
vul_shadow_applicability). The run log summarizes entries that would gain at least 10 points:
[SHADOW] 37 entries would rank higher without the suppressing rules: 21 via ignored generic keywords, ... and
[SHADOW] Most suppressed: keyword 'docker' (12), google/core@15.0.0 (5) ... - the data to decide whether a word or
vendor belongs back on the list. The shadow values do not change any score or filter.
Changes since the last run: a small state file metascraper_state.json (no CVE database: per CVE only
triage, applicability and whether it was confirmed) is compared with the current run. It lives next to the
inventory file, without inventory in the working folder (GUI: the output folder); StateFile sets another path.
Markers in ChangeStatus (vul_change, GUI column Changed, HTML below the ID):
new, newly confirmed (version check / OSV now says affected), applicability +N, triage +N (from 10 points).
Entries that dropped out are listed in the run log ([CHANGES] Gone: ..., mostly out of the time window).
The first run with a state file creates it and sets no markers.
The triage score combines risk (danger and urgency) with applicability (does it concern us):
Risk = CVSS/10 x 50 + min(EPSS / MinCritEPSS, 1) x 20 + known exploited 20 + PoC 10 (0-100)
Factor = 0.3 + 0.7 x Applicability/100 (1.0 when neither keywords nor an inventory are configured)
Triage = min(100, Risk x Factor)
Example with MinCritEPSS = 10: CVSS 9.8, EPSS 35 %, known exploited (risk 89):
exact package match with affected version (applicability 100) → 89; keyword hit in the product (60) →
64.1; no match (0) → 26.7.
Fields in every normalized entry (and in combined_vulnerabilities.json):
| Field | Meaning |
|---|---|
Applicability |
0–100, see above (empty if nothing is configured to match against) |
ApplicabilityReason |
Why, e.g. symfony/http-kernel@5.4.16: package composer:symfony/http-kernel, version 5.4.16 affected |
MatchedComponents |
Up to three inventory components, e.g. symfony/http-kernel@5.4.16 |
VersionStatus |
affected, not affected or unknown |
KeywordScore / InventoryScore |
The two parts of the applicability |
MatchedKeywords / MatchedIn / Relevance |
Keywords that matched, the fields they matched in, number of fields |
TriageScore |
0–100, used for sorting |
MinApplicability (default 0) hides entries below that value in the HTML report; the GUI has the same
filter. Matches are written to the debug log (Inventory hit: ..., Filter hit: ...), and summary lines are
printed on the console ([INVENTORY] ..., [FILTER] ...). The HTML report shows the columns
Applicability (hover for the reason), Matched and Triage. The JSON/CSV report has vul_applicability,
vul_components, vul_version_status, vul_applicability_reason, vul_matched and vul_triage.
CIRCL is queried by updated since (it has no "published since"), so it also delivers records that were published long ago - e.g. a CVE from 2017 or April - and only updated in the time window (new score, KEV marker, package data). Without explanation such entries look like a broken date filter.
- Published / Updated: every entry keeps its publication date; the new Updated field holds the last update
where the source provides it (CIRCL, NVD, EUVD; GUI column Updated,
vul_updated). - Default (
IncludeUpdates = false): entries published before the time window are dropped. The run log says how many:[FILTER] IncludeUpdates off: 175 entries published before the time window (only updated in it) dropped (CIRCL.LU 175).CIRCL is then read by publication date (newest first) and paging stops at the first page published entirely before the time window, so the old records are not downloaded at all (much shorter CIRCL time, build2026-11-10d). IncludeUpdates = true(GUI: Include CVE updates): they are kept and marked as updates (UpdateOnly,vul_update_only; GUI Updated column "2026-10-20 (update)"; HTML "update 2026-10-20" below the date). The results filter Show updates hides them without a new run.- KEV from the official CISA catalog (build
2026-11-11c): the whole catalog is read (about 1.8 MB, no key) and filtered bydateAdded; a local copy (%LOCALAPPDATA%\MetaScraper\kev_catalog.json, Linux~/.local/share/...) covers an outage ([KEV] CISA catalog not reachable - using the local copy of ...). New per entry: known ransomware use (KnownRansomware, searchis:ransomware), the CISA due date (KevDueDate, BOD 22-01 - for US federal agencies, shown as a hint next to your own triage deadline on the workbench) and the required action. The catalog carries no CVSS: KEV entries take the score of another source of the same CVE (CVSS from NVD), and KEV entries without any score are kept anyway - exploitation outweighs a missing score. - KEV is always kept: its date is the date of the KEV listing, and old CVE IDs are the normal case there
(a 2023 CVE exploited and listed yesterday is news). Such entries carry the note "added to KEV on 2026-10-19"
(GUI Updated column and tooltip, HTML,
vul_note). - Other old-looking IDs are fine: CVE IDs are reserved early, so a CVE-2024-... can be published today.
Some entries have no public CVSS score yet: fresh NVD/CIRCL records awaiting analysis, KEV entries without NVD data, OSV advisories with neither a CVSS vector nor a GHSA severity. Showing them as "0.0 / Low" would be wrong (CVSS 0.0 means severity None), so they are marked transparently and handled as classic risk management handles an unassessed risk: not as a low risk, but conservatively until someone has looked at it.
- Detection: no score, an empty score or 0 counts as NOT RATED.
- Display: CVSS shows
NOT RATED(GUI, HTML), the CSV/JSON keep the score empty and carryScoreStatus/vul_score_status(not rated,estimated,rated). Severity is Not rated (own colour), sorted between Medium and High; EPSS aboveMinCritEPSSstill makes it Critical (not rated), known exploitation High (not rated). - Triage: the risk part counts a NOT RATED entry as CVSS 7.0 (bottom of High) - only for the calculation, never as a displayed value. An advisory OSV confirmed for your component therefore stays near the top.
- Filtering: by default (
KeepUnrated = false)MinimumScoredrops them, as before. WithKeepUnrated = truethey are kept (EUVD then filters the score locally instead of on the server); this can add many fresh entries. OSV late confirmations without a score are always kept. The run log always reports the effect ([FILTER] KeepUnrated on/off: N entries ...). In the GUI results, the Keep unrated chip (default on) shows NOT RATED entries also when Min CVSS is set; unchecked, it hides them. The count line shows how many are loaded - withKeepUnrated = falsein the run there are usually none, so the chip has nothing to act on. - Estimated scores (GHSA severity mapped to a value, see OSV late confirmations) are shown as
7.5 (est.), with the origin inBaseScoreSource/vul_score_sourceand the GUI tooltip.
Every entry carries the CVSS vector of its score, so the triage can see why it is rated as it is
(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = remote, no login, no user interaction, full impact).
| Field (combined JSON / report CSV) | Meaning |
|---|---|
CvssVersion / vul_cvss_version |
version of BaseScore: 3.1, 3.0, 4.0 or 2.0 |
CvssVector / vul_cvss_vector |
vector of BaseScore, e.g. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
Cvss4Score / vul_cvss4_score, Cvss4Vector / vul_cvss4_vector |
an additional CVSS 4.0 metric next to a 3.x score |
- Sources: NVD (
cve.metrics, the NVD "Primary" metric first), CVE JSON 5 of CIRCL (CNA first, then ADP such as CISA), EUVD (baseScoreVector), Red Hat (cvss3_scoring_vector), OSV (severity), other formats (KEVin, CSAF) by any vector string in the record. - 3.1 first: the main score is CVSS 3.1 > 3.0 > 4.0 > 2.0. When a source scores with 4.0 only and another part of
the record (e.g. the CISA ADP) has a 3.x score, the 3.x score becomes
BaseScore(BaseScoreSourcesays so) and the 4.0 metric is kept inCvss4Score/Cvss4Vector. This keeps severity and triage thresholds comparable. Entries that only carry a vector (OSV records of CIRCL, NVD entries with CVSS 4.0 only) now get the score from it. - GUI: column Vector (version and vector) on Results, Known (OSV) and Workbench; the detail pane and the Workbench CVE viewer explain every metric in plain text with a one-line summary. CVSS 2.0 vectors are shown, not explained.
Build 2026-11-11a: CIRCL also delivers records of GCVE numbering authorities (GNA) - the decentralised,
European identification scheme started by CIRCL (e.g. GCVE-1988-2026-0544 of GNA 1988 VULNARCHIVE, an archive of
mailing lists such as Bugtraq, Full Disclosure and the distribution security lists). Earlier builds dropped them
(their ID is cveMetadata.vulnId, not cveId).
- Advisory that names CVEs of the run (e.g. Debian DSA 6599-1 nss security update naming CVE-2026-...): no row
of its own - it is attached to those CVE entries as further evidence (
Advisories/vul_advisories; GUI: megaphone icon next to the severity, also reported (GCVE) in the detail with the archive link). - Advisory without such a CVE: its own row, clearly marked Advisory (
IsAdvisory/vul_advisory), shown without a CVSS score (NOT RATED) regardless ofKeepUnrated/MinimumScore. The CVEs it names are inAdvisoryCves. - Run log:
[GCVE] 12 advisories attached to the CVE entries they name, 5 shown on their own (marked Advisory). - Search:
is:advisory(advisory rows),is:gcve(anything from GCVE),advisories:dsa.
Build 2026-11-10e: every entry carries its weakness (CWE) and impact classes, so the triage sees at once what a
flaw allows - above all remote code execution without login.
| Field (combined JSON / report CSV) | Meaning |
|---|---|
Cwe / vul_cwe |
CWE IDs, e.g. CWE-416, CWE-787 |
Impact / vul_impact |
classes, most urgent first: RCE, Auth bypass, Privilege escalation, Data exposure / injection, DoS, XSS / web client |
ImpactReason / vul_impact_reason |
why, e.g. RCE: CWE-787 (memory corruption, network, C/I high) or RCE: description 'remote code execution' |
PreAuth / vul_preauth |
true = no account and no user interaction needed (CVSS PR:N and UI:N; CVSS 2 Au:N); empty = unknown |
- CWE sources: CVE JSON 5
problemTypes(CNA and ADP, CIRCL), NVDweaknesses, CISA KEVcwes, Red HatCWE(also chains likeCWE-20->CWE-787), CSAFvulnerabilities[].cwe, OSV / GHSAcwe_ids. EUVD carries no CWE: an entry takes the CWEs of the same CVE from the other sources of the run. Not every CVE has a CWE (e.g. most Linux kernel CVEs); the Dashboard shows the share. - Classes from three signals:
- CWE groups, e.g. RCE = code / command injection, deserialization, unrestricted upload, template / expression injection, backdoor (CWE-94, 77, 78, 502, 434, 1336, 917, 506 ...); bypass = CWE-287, 288, 290, 306, 639, 798, 862, 863 ...; data exposure / injection = CWE-89, 22, 611, 918, 200 ...; DoS = CWE-400, 770, 835, 1333, 476 ...; XSS / web client = CWE-79, 352, 601 ...; privilege escalation = CWE-269, 250, 732 ...
- CVSS vector for memory flaws (CWE-787, 416, 119, 122, 125, 843 ...): network and confidentiality + integrity high = RCE; local with C/I high = privilege escalation; availability only = DoS; confidentiality only = data exposure.
- Description as fallback when the CWEs give no class (none, or generic ones like CWE-20 Improper input validation): words like "remote code execution", "authentication bypass", "SQL injection". The reason names the word - check such entries in the detail.
- RCE next to DoS drops the DoS (code execution includes taking the service down).
- The GUI classifies results of older script builds itself from the vector and the description (no CWE then).
Version coverage (build 2026-11-13d): a version that is not among the versions a vendor reports is not
"not affected" - vendors often stop reporting versions that are out of support. For the best matched component the
script compares its upstream version (epoch and distribution revision removed: 1:2.39.2-1.1 -> 2.39.2) with the
lowest version the vendor reports for that product (lists often name many products; only the matching one counts):
- at least 1 major or, in the same major, at least 2 minor versions below -> hint "potentially unmaintained"
(⚠ in the Versions column, detail, Workbench viewer; search
is:unmaintained); - otherwise below -> hint "not in the vendor data";
- version status and applicability stay as they are; a range starting at 0 (or without a lower bound) covers it.
- not affected only when the description says so explicitly ("Versions before PostgreSQL 18 are unaffected",
"introduced in 2.4") - unless a distribution confirmed it with a fix version.
The JSON/CSV report has
vul_version_hint/ column VersionHint; the run log a[VERSIONS]summary.
Every entry gets the affected versions for display:
| Field | Meaning |
|---|---|
Versions |
e.g. symfony/http-kernel: >= 5.0.0, < 5.4.20 or < 2.4.0 |
VersionsSource |
structured (source data) or description (taken from the text) |
GitRanges |
Number of git commit ranges (e.g. Linux kernel CNA); these are commits, not versions |
- Structured (preferred): CVE 5
affected.versions(version / lessThan / lessThanOrEqual), OSV ranges (introduced / fixed / last_affected), NVD CPEversionStart…/versionEnd…, EUVDproduct_version. - Description text (fallback, marked
~): phrases such as before 2.4.0 →< 2.4.0, from 9.0.0 before 9.20.0 →>= 9.0.0, < 9.20.0, 2.0-beta9 through 2.15.0, 3.1.4 and earlier →<= 3.1.4, <= 4.2.1, fixed in 1.5.3, versions 1.2, 1.3 and 1.4. Bare numbers without such context are ignored (TLS 1.2, CVSS 3.1, HTTP/1.1 ...).
40-character hexadecimal values such as f72cf46f601efb2a0618c3814cc2f61380b38930 are git commit hashes.
Some sources (e.g. the Linux kernel CNA, OSV GIT ranges) describe the affected range by the commit that
introduced and the commit that fixed the bug. They cannot be compared with versions and are only counted.
The versions are shown in the HTML report below the product, and in the GUI's Versions column (source as
tooltip) and detail pane. The JSON/CSV report has vul_versions and vul_versions_source. Free-text versions
are for display only; the inventory version check uses structured data.
Distribution packages and backports (build 2026-11-11d): distributions (Red Hat, Debian, Ubuntu ...) fix
vulnerabilities in their own builds without raising the upstream version - openssl-3.0.7-27.el9 can contain the
fix of "OpenSSL < 3.0.13". The version check therefore looks at the source of each package in the SBOM
(SbomFilterExtractor.ps1 writes Origin, Distro, Supplier and Backports per inventory component):
- Backport stated in the SBOM: a CycloneDX
pedigree.patches[]entry of typebackportorcherry-pickwhoseresolves[]names the CVE -> not affected ("fixed by a backport according to the SBOM"). - Distribution package (purl
pkg:rpm/pkg:deb/pkg:apk, e.g. withdistro=rhel-9.4) against an upstream range (NVD, EUVD, CVE 5 ...): unknown with the reason "upstream version range does not apply to a distribution build (backports possible)" instead of a false affected. - Distribution package against a distribution source (Red Hat fixed packages per stream, OSV for Debian /
Ubuntu / Alpine): checked with the version rules of the package system - rpm (epoch, release,
~,^) and dpkg (epoch, Debian revision,~);openssl-3.0.7-25.el9is affected by a fix in-27.el9,-27.el9_4is not. - Packages from a registry (
pkg:pypi,pkg:npm,pkg:maven...) are checked against upstream ranges as before. - Search
is:backport. For backports to be recognised, the SBOM needs the full distribution version (3.0.7-27.el9, not just3.0.7) - SBOMs of hosts / images made by Syft or Trivy have it.
The sources deliver the same CVE several times (EUVD, NVD, CIRCL / CVE 5, Red Hat, KEV). Since build 2026-11-13b
they are merged into one object per CVE right after collecting, before the score filter, the matching and the
triage. Entries without a CVE (e.g. EUVD-only) and GCVE advisories stay on their own.
- Observations: every source response stays as a normalized entry - source, ID, URL, published / updated, CVSS
with version and vector, exploited, EPSS, vendor / product, CWE, affected versions, coordinates
(
vendor|product|rangeorpackage|range), description (shortened) and a hash for change detection. The raw records stay in the source caches;combined_vulnerabilities.jsoncarries the observations. - Rules per field: score from the CNA record (CIRCL CVE 5, EUVD) -> NVD -> Red Hat -> OSV -> KEV, the highest of
all sources as
MaxScore; exploited from any source (KEV details: ransomware, due date, action); description from the CNA record, else the longest; published = earliest, updated = latest; CWE and GCVE advisories: union. - Matching reads the affected data of every source of the CVE (CVE 5 versions, NVD CPE ranges, EUVD, Red Hat, OSV), so the CNA's version ranges and NVD's CPEs, which arrive later, both count.
- Disagreements are kept, not resolved silently (
Conflicts): CVSS scores of the same CVSS version that differ (CVSS 3.x 9.6 EUVD / 8.8 RedHat Security; 4.0 vs. 3.1 differs by design and is no disagreement), and affected versions that differ between sources. In a test run over two days: 2,768 source entries -> 1,475 objects, 1,121 of them from several sources, 61 with disagreements. - Breaking change: results files and a results history of older builds (one row per source) are not read by MetaScraperGui any more - run the script again.
| File | Content |
|---|---|
vul_<timestamp>.html |
Report, sorted and colour-coded by severity |
vul_<timestamp>.JSON / vul_<timestamp>.csv |
Report data for further processing |
combined_vulnerabilities.json |
One object per CVE (results format 2) with the observations of every source, disagreements, matches and triage score (read by the GUI) |
run_snapshot.clixml.gz |
Run snapshot for a re-evaluation (build 2026-11-14a and newer): the merged entries before the inventory phases with the raw records of every source, PowerShell CLI XML, gzip (about 2-3 MB for 1,500 CVEs; adds about 10 s to a run) |
run_stats.json |
Operational statistics per source: duration, requests, retries, failures, waiting time, request time, slowest request, records, network bytes received (build 2026-11-10a and newer; also printed as [API-STATS] lines) |
osv_known_vulnerabilities.json |
With OSV: known vulnerabilities of your components that are not in this run's data (published, CVSS, summary, versions, VEX state; GUI tab Known (OSV)) |
metascraper_state.json |
State for the change markers (next to the inventory, otherwise here) |
requery_cache.json |
Re-query results per CVE (next to the inventory) |
EUVD_Debug_<timestamp>.log |
Log file (LogLevel troubleshooting or debug) |
Clean up old reports and logs from time to time.
A new SBOM, a Dependency-Track import or a changed filter list changes the matches, not the CVE data. Instead of a new run, the data of the last run can be evaluated again:
.\metascraper.ps1 -ConfigFile config.toml -Reevaluate [-SnapshotFile <run folder>\run_snapshot.clixml.gz]- Reads the run snapshot (default: in the working folder) - no source is queried, no EPSS.
- Applies the current configuration again: inventory matching, OSV and GitHub Advisories for the inventory's components (with their caches - new components may need lookups), version coverage, VEX, filter lists, triage, and writes the outputs as a run does.
- The change markers stay those of the original run; the state file is not touched. The snapshot and
run_stats.jsonare copied next to the new results, so a further re-evaluation finds them. - Settings of the download (time window, minimum score, KeepUnrated, IncludeUpdates) come from the snapshot.
- No snapshot: exit code 2 with a message.
For every source the script prints one machine-readable line at the start and one at the end:
[API-STATUS] CIRCL START
[API-STATUS] CIRCL OK 250 records
The states are START, OK (data received), EMPTY (no data), PARTIAL (some requests failed) and
ERROR (failed, no data). The GUI uses these lines for its status indicators. You can also use them in
your own scheduling or monitoring.
A Windows desktop front end for metascraper.ps1. The GUI does not change the script; it writes a
TOML configuration, runs the script as a child process and shows the results.
Start: double-click Start-MetaScraperGUI.cmd. It launches the GUI in an STA thread (required by
WPF), without a console window, and with execution policy Bypass for this process only. Alternatively:
powershell -STA -ExecutionPolicy Bypass -File .\MetaScraperGUI.ps1 [-ScriptPath C:\path\metascraper.ps1]Keep metascraper.ps1 next to the GUI, or select it with Browse.... The path is remembered.
Features
-
Configuration tab: every setting as a form field with explanations, with keyword lists edited one per line - in MetaScraperGui kept in alphabetical order (sorted when loaded, generated from an SBOM and when you leave the box; duplicates removed; the order has no effect on the matching). In MetaScraperGui: a search over all settings (labels and help texts), jump chips to the groups, collapsible groups (click the group header), and settings that differ from the defaults of
metascraper.ps1marked with a bar, counted per group and in the Changed chip (which shows only them); the arrow at the end of a marked row resets that setting. Load config... / Save config... use the script's TOML format; Reset to defaults reads the defaults frommetascraper.ps1.- Proxy URL: passed to the script as
https_proxy. - Proxy user: for a proxy that asks for user and password. The password is asked for once per session (at the
first run, live refresh, Buzz check or Dependency-Track fetch) and kept in memory only - not in the settings,
config.toml, the command line or any log. The script gets it through the environment of its own process. If the proxy refuses the login (HTTP 407), the password is asked for again at the next run. - Output folder: each run gets its own timestamped sub-folder with config, logs and reports.
- Proxy URL: passed to the script as
-
Run with: choose Windows PowerShell 5.1 or PowerShell 7. Click ▶ Run to start and Cancel to stop.
-
API status indicators next to the Run button, one per source (EUVD, KEV, RHS, CIRCL, NVD) plus EPSS, OSV and GHSA:
Colour Meaning grey not started blue running green finished, data received yellow finished without data, or some requests failed red failed, or the run ended before this source finished black, grey text switched off by you (not queried) Zen (switch on the Configuration tab, group This app) changes to a calm dark colour scheme - warm dark grey, soft off-white text, muted row and status colours in the same order - for less eye strain; the choice is saved with the settings. Hover over an indicator to see the record count. Click an indicator to switch that source off for the next runs (click again to switch it on); the choice is saved with the settings (
DisabledSources). -
Run log tab: the script's live output, coloured by line type: red = warnings, errors and failed sources; orange = retries, sources without data or partly failed; green = source finished; blue = source started and command line; cyan = summaries (
[INVENTORY],[OSV],[FILTER]...); grey = verbose/debug details. Open run folder opens the folder of the current run. Copying: select lines with Ctrl+click / Shift+click (Ctrl+A for all) and copy them with Ctrl+C, the context menu (right click) or Copy selected; Copy all copies the whole log. Text view shows the log as plain text, where any part - also within a line - can be selected and copied. The SBOM log copies lines the same way. -
SBOM filter tab (SBOM Tools in MetaScraperGui): runs
SbomFilterExtractor.ps1from the GUI.- Select an SBOM file or folder at the top - or fetch the components of a Dependency-Track project
(Dependency-Track...): API server URL and API key (its team needs
VIEW_PORTFOLIO), Load projects, pick one (filter by name; by default only the latest version of each project), Fetch components. The GUI downloads the project's CycloneDX BOM (/api/v1/bom/cyclonedx/project/{uuid}, components only - Dependency-Track's own findings are not used) into the output folder and generates the filter list from it right away; continue with Use as inventory. URL and last project are remembered; the API key is stored only on Windows, encrypted for your Windows account (DPAPI, a file of its own - never in the settings orconfig.toml), otherwise it is entered again or taken from the environment variableDT_API_KEY. The proxy of the Configuration tab is used, and the Windows login is sent to a proxy that asks for authentication (HTTP 407). - Set the options on the left (direct dependencies only, aggregation, stop words ...).
- Click Generate filter list.
- The right side shows the generated list, which you can edit, plus all candidate keywords with the reason they were kept or dropped.
- Use as inventory sets the generated
sbom_inventory.jsonas inventory file: the next run matches CVEs against the components (applicability score). This is the more precise way. - Merge into keywords adds the list to the Configuration tab. Duplicates are removed, and so are entries already covered by a shorter keyword. Replace keywords overwrites the list.
In MetaScraperGui the tab follows these numbered steps: 1 SBOM (file or folder) and the optional VEX document, 2 options (grouped into which components and how keywords are built) with the run output below, 3 the keyword list with a counter and the actions - Use as inventory (recommended), Merge into keywords, Replace keywords, copy, open the output folder - and all candidates with Kept / Dropped filter chips and a search.
Optionally add a separate VEX document (CycloneDX VEX, JSON or XML); embedded VEX in the SBOM is always used.
SbomFilterExtractor.ps1must be next tometascraper.ps1or next to the GUI. - Select an SBOM file or folder at the top - or fetch the components of a Dependency-Track project
(Dependency-Track...): API server URL and API key (its team needs
-
Results tab:
- Results history: every finished run is added to a history, and the Results tab shows the history - overlapping
runs (e.g. 2 days look-back, several runs a day) add up instead of replacing each other.
- Same entry (source + ID) in several runs: the newest state wins (score, EPSS, KEV, inventory ...). An older run added later never overwrites a newer state.
- First and last run that delivered an entry are in the detail (Results history: first seen ..., last seen ...);
chip Latest run only (the view of a single run), search
is:latest,is:history,seen>=2026-11-01. Change markers belong to the run that set them: entries only in earlier runs show none. - Entries not seen in any run for 30 days drop out (Configuration: Keep results history (days), 0 = keep all).
- History... lists the earlier run folders below the output folder: add them on purpose (several at once), show the history again after opening a single file, or clear it.
- Stored in
%LOCALAPPDATA%\MetaScraperGUI\results_history.json(format version 1; every entry keeps the record of its script build, without the raw data - older builds are read like their results files).
- Open results... shows any
combined_vulnerabilities.jsonon its own; the history stays as it is (History... > Add the open file takes it over). - Layout (MetaScraperGui, section 2.4; the WPF GUI keeps the older layout with check boxes): one fixed filter row (search, Severity, Source, Open / Export), below it the on/off filters as chips (filled = active): Known exploited, PoC available, Changed only, Matches only | Show updates, Hide duplicates, Keep unrated | Thresholds. The thresholds row (Min triage, Min CVSS, Min EPSS %, Min appl.) opens with Thresholds; the number on the button shows how many are set, also while the row is closed. Filters follow the column order of the grid. The status line lists every active filter as a tag - click a tag to remove that filter. Ctrl+F jumps to the search. In a narrow window the buttons shrink to their icons (tooltip names the action), so the controls keep their places. The tabs carry icons and counters (e.g. Results 5, Workbench 2).
- Grid: rows are coloured by severity; Triage (bar and value, with the severity colour as stripe at the row start), Severity as coloured label, CVE - these three stay in place when scrolling sideways - then on workbench (pin) / archived (box), known exploited (flame), PoC (bug), CVSS, EPSS %, Appl., Impact, Vendor, Product, Versions, Published, Updated, Description, Vector, ID, Component, Source, Changed (marker as label) and Matched. Hover a column title for a short explanation of the column. Columns can be sorted, resized and moved.
- Detail below the grid (drag the line above it to resize): header with severity, CVE, exploited / PoC, source and the actions (Add to workbench, Advisory, CVE record, Copy CVE ID), the key figures Triage / CVSS / EPSS / Applicability, then the full text.
- Triage column (default sort order), Appl. (applicability, reason as tooltip), Matched (keywords) and Component (matched inventory components). Keyword part and triage are recalculated live from the keyword lists on the Configuration tab. The inventory part comes from the run.
- Min applicability filter (e.g. 33 hides weak matches such as a generic product name only), Min triage filter, Changed only (entries with a change marker since the last run).
- Changed column: new / newly confirmed / applicability +N / triage +N since the last run.
- The detail pane shows the triage breakdown (risk × factor), applicability with reason and the matched keywords.
- One row per CVE (results format 2): column Sources lists every source, ⚠ marks disagreements (tooltip).
The detail pane shows right below the key figures where the score comes from, the highest score of all sources,
the disagreements and one line per source (score, version, updated, vendor / product, versions, coordinates);
the Workbench CVE viewer shows the same, and a new disagreement or source is noted under Changes. The source
filter matches any source of an entry; long product lists are summarized (Windows 10 (1607, 1809) · Windows
Server (2016, 2025); the full list stays in the detail and the search) and the versions shown as a table per range
(products with the same range on one line); a source with the same products and versions as the main one only says
so; search
is:conflict,is:multisource; CSV export columns Sources, Conflicts, MaxCVSS, MaxCVSSSource. A results history of the old format (rows per source) is dropped. - Search - in the WPF GUI words only; MetaScraperGui has two modes, switched with Advanced next to the search box (the choice is saved):
-
Simple (default): space-separated words; every word must appear somewhere in ID, CVE, source, vendor, product or description.
-
Advanced: all terms must match; ? next to the box shows the syntax.
Input Meaning vendor:redhat product:openshift desc:gatekeeperfield contains (vendor, product, desc, cve, id, source, matched, component, severity, versions, change, workbench, vector, note, cwe, impact) desc:"admission webhook","remote code"phrase in a field / anywhere product:/open.?shift|okd/,/log4(j|shell)/regular expression (case-insensitive), in a field or anywhere -product:windowsorproduct:-windowsexclude (minus before any term; for fields also after the colon) cvss>=9,epss>5,triage>=40,appl>=33,cvss:9.8numbers: >>=<<=,:= equalpublished>=2026-10-01,updated:2026-11dates (year, month or day) is:exploited,is:poc,is:changed,is:new,is:unrated,is:workbench,is:archived,is:updateflags is:rce,is:preauth,is:bypass,is:privesc,is:injection,is:dos,is:xss,cwe:787,impact:bypassweakness and impact class ( is:rce is:preauth= RCE without login)is:advisory,is:gcve,advisories:dsaGCVE advisories (rows marked Advisory) and advisories attached to CVE entries A mistake (unknown field, invalid regex) marks the box red and is named in the status line; the rest of the query still applies. In simple mode a field term like
vendor:...gets a hint to switch on Advanced.
-
- Export view to CSV... exports the current filtered view.
- Auto Report - a pre-filtered vulnerability report for the triage round (an aid in between, not a triage
result). One local HTML file per click in the run folder; nothing is loaded from outside, the links open the
public sources for the details. Filters of the view and workbench markings do not apply.
- What goes in: CVEs confirmed to affect a component of the inventory (AI verdict affected for that component; without AI the version check affected), with severity Critical, High or not rated (early warning), and every KEV-listed CVE whatever its severity. Medium / Low and everything unclear stay out. All exposures.
- Structure: vendor > product, one block per affected component (no duplicates: the CNA record "Git / Git" and the Debian record "debian / git" of the same component are one product). The vendor comes from the CVE records, not from the distribution: a Debian package keeps its real vendor (NVD / CNA) and is marked Debian package; vendor "n/a" in the record -> the project itself; only distribution data -> Debian package. Products sorted by their highest triage.
- Per product: counts Critical / High / not rated, Exploited, PoC and the exposure side by side, new / changed since the last run, and the fix version for its own branch (15.5 -> 15.20, not 18.7; upstream for a distribution package; none from enumerated version lists). Below: one line per CVE without description (severity, triage, KEV, PoC, exposure, impact, fix, change, links).
- Local AI (optional): with the AI review on, one summary sentence per product; without it the sentence is missing.
- Notes on this report at the end: failed, incomplete or skipped sources of the run, CVEs without AI verdict or exposure rating, AI summaries not written, and the data basis.
- The detail pane also shows the change marker and, if the stricter rules lowered the entry, Suppressed by rules with the applicability it would have without them (shadow comparison).
- Results history: every finished run is added to a history, and the Results tab shows the history - overlapping
runs (e.g. 2 days look-back, several runs a day) add up instead of replacing each other.
-
Known (OSV) tab: known vulnerabilities of your SBOM components that are not in the current results (
osv_known_vulnerabilities.jsonof the run, loaded with the results; Open file... for another run). Component, OSV ID, CVE, published, CVSS (NOT RATED / est.), VEX state, affected versions and summary; search, Hide VEX not affected, double-click or Open OSV page opens osv.dev. In MetaScraperGui the score is a coloured label (same colours as the severity on the Results tab), VEX as a green label, and a detail below the grid shows the summary, affected versions, aliases and vector with OSV page and Copy ID.
Settings are stored in %APPDATA%\MetaScraperGUI\settings.json. Runs go to
%LOCALAPPDATA%\MetaScraperGUI\runs unless you change the output folder.
Note for maintainers: the GUI contains a small C# block (row model, severity/relevance/triage logic, fast filter, process runner) compiled at start with
Add-Type. The severity, keyword matching, triage and de-duplication rules there mirrormetascraper.ps1(Add-FilterMatches,ConvertTo-HtmlTable); keep both in sync when changing them.metascraper.ps1itself does not depend on the GUI and runs standalone.Keep the
.ps1files ASCII-only (use\u2013etc. in regular expressions). Windows PowerShell 5.1 reads files without a BOM as Windows-1252; the UTF-8 bytes of characters such as an en dash then contain a typographic quote, which PowerShell treats as a string delimiter (parse errors "Unexpected token"). After editing the C# block, start a new PowerShell session.
Keyword lists maintained by hand get outdated quickly. If you have CycloneDX SBOMs (spec 1.4–1.6,
JSON or XML) of your applications or platforms, this tool derives the filterString from the components you
actually ship or run.
# one SBOM
.\SbomFilterExtractor.ps1 -SbomPath .\bom.json
# a folder of SBOMs, direct dependencies only, merged with the existing config
.\SbomFilterExtractor.ps1 -SbomPath .\sboms\ -DirectOnly -MergeConfig .\config.toml -OutDir .\filterWithout a console: drag an SBOM file (or a folder of SBOMs) onto Start-SbomFilterExtractor.cmd.
The result goes to a folder <sbom name>_filter next to the SBOM, which opens automatically when the run is done.
For precise matching, set sbom_inventory.json as InventoryFile in config.toml (or click Use as
inventory in the GUI). metascraper.ps1 then matches every CVE against the components by vendor + product,
purl, CPE and version, and computes the applicability score.
For keyword matching, copy the line from filterString.toml into your config.toml, or use the GUI's SBOM filter tab (SBOM Tools in MetaScraperGui),
which runs the tool and merges the result into the keyword list.
- Extract vendor, name and version of every component, including nested ones. The vendor is taken from the first available of: manufacturer, supplier, publisher, CPE vendor, group/namespace.
- Normalize names: remove scopes and paths (
@angular/core,github.com/x/y), Scala suffixes (_2.13) and generic trailing parts. For example,log4j-corebecomeslog4jandbcprov-jdk18onbecomesbcprov. - Normalize vendors: "The Apache Software Foundation" and
org.apache.*both becomeapache. - Aggregate: names sharing a first part are merged into one family keyword
(
spring-web+spring-boot-starter-web→spring). - De-duplicate: because
filterStringmatches whole words, a keyword already matched by a shorter one is dropped (jacksoncoversjackson-databind). This also cleans up a merged existing list (spring boot,spring.io→ covered byspring). - Vendor from the package namespace, not from people:
group/ purl namespace (e.g. Composersymfony/console→ vendorsymfony) is used beforeauthorentries, which are mostly people's names. Product names that are ordinary words (console,process,container,math...) are not used as standalone keywords. In the inventory they only count together with their vendor. - Drop noise: generic words (
core,utils,api...), keywords shorter than 3 characters, and author names. Author names are listed in the component CSV but not used as keywords. - No umbrella vendors: vendors with many unrelated products (google, microsoft, amazon/aws, azure,
apache, oracle, ibm, red hat, eclipse, vmware, cisco, sap ...) never become keywords. They would match a
large share of all CVEs. Names keep their product part instead:
Microsoft.Extensions.Logging→microsoft.extensions,google-auth-library→google-auth, andapacheis dropped whilelog4jandtomcatstay. Extend the list with-BroadVendors, or switch it off with-AllowBroadVendors.
| Parameter | Meaning |
|---|---|
-SbomPath |
File(s), folder(s) or wildcards |
-OutDir |
Output folder (default .\sbom_filter_output) |
-DirectOnly |
Only direct dependencies of the main component, from the dependency graph |
-IncludeRoot |
Also include the SBOM's main component (usually your own product) |
-IncludeExcluded |
Also include components with scope excluded (e.g. test-only) |
-NoVendors / -NoAggregate |
Skip vendor keywords / family aggregation |
-AggregateThreshold |
Components needed to form a family keyword (default 2) |
-MinLength |
Minimum keyword length (default 3) |
-StopWords / -StopWordFile |
Additional words to ignore |
-BroadVendors / -AllowBroadVendors |
Additional umbrella vendors to exclude / allow umbrella vendors as keywords again |
-MergeConfig / -MergeFilter |
Merge an existing config.toml or filter string |
-Delimiter |
CSV delimiter (default ;) |
-VexPath |
Separate CycloneDX VEX documents (files, folders, wildcards); merged into sbom_vex.json |
| File | Content |
|---|---|
filterString.txt |
The keyword list |
filterString.toml |
Ready-to-paste filterString = "..." line |
sbom_keywords.csv |
Every candidate keyword: kept or dropped, reason (e.g. covered by 'spring'), source components. Review this file. |
sbom_inventory.json |
Component inventory for metascraper.ps1 -InventoryFile: vendor, product aliases, generic flag, version, purl, CPE |
sbom_components.csv |
Per component: vendor, name, version, purl, CPE, type, scope, direct dependency |
sbom_vex.json |
Only with VEX data in the SBOM: ID, state, justification and affected inventory components (read by metascraper.ps1 next to the inventory) |
- The keyword list has no versions. Version matching happens with the inventory (
sbom_inventory.json): structured ranges, OSV, Red Hat and CSAF packages. Free-text versions are not checked, and distributions backport fixes without changing version numbers. For full scans use dedicated scanners (OSV-Scanner, Grype, Trivy, Dependency-Track) next to MetaScraper. - CycloneDX only (JSON or XML, spec 1.4 - 1.6); SPDX is not supported. XML documents with a DTD are rejected.
- Aggregation can be too broad (e.g.
node-fetch+node-forge→node). Checksbom_keywords.csvand add unwanted words with-StopWords.
gui/ contains the successor of the WPF GUI: the same tabs and logic (Configuration, Run log, Results,
Known (OSV), SBOM filter - here called SBOM Tools) plus a Workbench (see below), built with Avalonia on .NET 10, so it runs on Windows and
Linux. The PowerShell scripts stay unchanged: the GUI starts metascraper.ps1 / SbomFilterExtractor.ps1 with
PowerShell and reads their JSON output. MetaScraperGUI.ps1 stays until the new GUI has been tested.
Requirements
- Windows: Windows PowerShell 5.1 (built in) or PowerShell 7. Linux: PowerShell 7 (
pwsh, install guide). - The single-file builds need no .NET installation (self-contained).
- Icon: shield with a cockroach under a magnifier (
gui/MetaScraper.Desktop/Assets/app.svg;app-small.svgis the simplified drawing for 16-32 px;app.icoholds 16-256 px and is the window, taskbar and Windows .exe icon).
Start
- Windows:
MetaScraperGui.exe; Linux:chmod +x MetaScraperGui && ./MetaScraperGui. - Default script:
metascraper.ps1next to the program (also found in its parent folders and in the current folder). Another script and the PowerShell version are chosen on the Configuration tab, group This app (Browse..., Default goes back to the default; only a script other than the default is remembered), or start with--script /path/to/metascraper.ps1. The line next to Run shows which script (build, custom) runs with which PowerShell.SbomFilterExtractor.ps1is expected next tometascraper.ps1. - Settings are saved when the window closes: Windows
%APPDATA%\MetaScraperGUI\settings-avalonia.json, Linux~/.config/MetaScraperGUI/settings-avalonia.json. Runs go to%LOCALAPPDATA%\MetaScraperGUI\runs/~/.local/share/MetaScraperGUI/runs(setting Output folder).
Re-evaluate
- The button Re-evaluate next to Run evaluates the data of the last run (its run snapshot) again with the current inventory and filter lists - no new download, in a new run folder; the results load like after a run and the AI review checks what changed (the store answers the rest). Its tooltip names the run it uses.
- A new inventory starts it at once: Use inventory on the SBOM Tools tab (SBOM file or Dependency-Track import) or a new file chosen for SBOM inventory file on the Configuration tab. Without a snapshot the next run uses it.
- Needs
metascraper.ps1build2026-11-14aor newer (older runs have no snapshot).
Dashboard
- A tab with key figures and charts of the loaded results (LiveCharts2, animated, tooltips on hover, Light and Zen):
- key figures: entries, critical, known exploited, new / changed since the last run, matching your products, median EPSS (numbers count up when they change);
- world map of the data sources right below the key figures (EUVD Athens, CIRCL Luxembourg, NVD / CISA / Red Hat / FIRST in the US, OSV and GitHub in California) with pulsing bubbles scaled by the records fetched in the latest run (hover for details);
- donuts: severity and share per source - their legend chips filter the whole dashboard (Show all resets);
- bars: entries per source, CVSS, EPSS, triage and applicability distributions, top 20 products, top 15 vendors;
- risk map CVSS x EPSS (log scale, bubble size = triage score, the "act first" corner marked);
- risk load: the vendors or products where the danger piles up - by Σ CVSS x EPSS (severity weighted by the probability of exploitation), Σ CVSS or Σ EPSS, switched with the chips of the card;
- vendor map - what a vendor costs you: the top 30 vendors by entries, one point per vendor; across the number of entries (log scale), up the average CVSS, bubble size the median triage score or median applicability (chips of the card) - the typical flaw, i.e. the effort of running, monitoring and patching that vendor's products, not the single worst one. The background is a fixed risk zone: the more entries and the higher the average CVSS, the hotter (top right red). A line marks the average CVSS of all entries (value and median in the subtitle): vendors above it are worse than the typical CVE. Tooltip: entries, Ø CVSS, triage median / mean / max, applicability, matching entries;
- CVSS vector statistics: share of each value per base metric (red = the more dangerous value);
- timeline of the last 30 days of publications (all sources and per source, lines can be hidden) and the activity curve per source (3-day average) with the peak days marked;
- Weaknesses - what the flaws allow: remote code execution without login (pre-auth), RCE, auth bypass, pre-auth entries of any class and the share with CWE; impact classes stacked by pre-auth / needs an account or a user / unknown; how the class was found (CWE and vector, or the description as fallback); top 15 CWEs with names; the products with the most RCE entries. See Weakness (CWE) and impact class.
- Matching - keyword filter and inventory: entries with keyword hits, hits per entry (average and distribution), inventory matches, components hit (of the components in the inventory file), confirmed affected / not affected by version or VEX; top 20 keywords ("hitters"), top 20 inventory components, the field where the keywords hit (vendor / product / description) and the keywords without any hit - candidates to review in the keyword list. Follows the keyword lists and the inventory file of the Configuration tab.
- Operations - how the latest run went (from
run_stats.jsonof the run folder, also for runs opened later): run time, requests, data received and retries / failures; query time per source split into request time, waiting for retries and processing; data volume per source; requests per source (successful / retried / failed); throughput per source as records/s, MB/s or ms per request; local processing after the queries per phase (normalizing per source, CVSS / CWE, EPSS, inventory, keyword matching and triage, change markers, writing the results and the report) from the[TIMING]lines of build2026-11-11a- where the time goes after the last query. The data volume is measured as the bytes the network received while a source was queried (the sources run one after the other) - it includes other traffic of the machine at that time.
- Skeletons while a run is going, an empty state without results; the cards rearrange in 3, 2 or 1 columns with the window width.
Workbench (former watchlist, "Merkzettel")
-
Layout (same pattern as the Results tab; the tab icon is a wrench, also used for Add to workbench): one top row with the search, Show archive, the refresh actions (Update from results, Live refresh, Buzz, feed list, Cancel) and the hand-over actions (CSV..., Markdown, Remove all... as bin icon). Rows keep the severity colour;
#(own order, with the severity stripe), Severity (label) and CVE stay in place when scrolling sideways, then KEV (flame), Buzz, own and source CVSS, EPSS, Triage, Changes and the latest note. Below the grid (drag the line to resize): header with severity, CVE, exploited and source plus the actions for the selection (CVE viewer, Open link, Copy CVE ID, Archive... / Restore, Remove), then notes on the left and the own assessment (severity, CVSS vector, buzz terms) on the right. In a narrow window the buttons shrink to their icons. -
Select one or more rows on the Results tab (Ctrl/Shift+click) and use Add to workbench (button or context menu). The same CVE is kept once; adding it again updates its values.
-
Triage deadlines: every open entry gets a deadline for the triage decision (affected? handed over / no action needed) - not a patch deadline, that belongs to the ticket system of the team that patches. Per class a time span from adding the entry:
4h= clock hours,3d= working days (Mon-Fri),0= no deadline. Set on the Configuration tab, group Workbench: triage deadlines (saved with the window settings, not inconfig.toml; an invalid value is marked red and the default is used):Class Default known exploited (KEV) 4hcritical 8hhigh (also entries without a score) 1dmedium 3dlow 10dHour deadlines run around the clock (also at night and at the weekend); adjust them to your on-call times. The class follows the effective severity (own rating first). Escalation (e.g. new in KEV, higher own severity) shortens the deadline at once to the earlier of the old one and now + days of the new class, noted in Changes. A lower class does not lengthen it by itself: the detail offers Apply ... deadline, noted in the log. By hand: Change... next to Triage deadline in the detail sets the deadline of the selected entries (in 4 h, 8 h, 1, 3 or 5 working days, or a date and time) with a reason - e.g. agreed with the team; noted in the log, saved with the workbench. It stays until Automatic sets it back to the deadline of the class; an escalation that is earlier still wins. Archiving ends the deadline; restoring starts a new one. Entries of older versions get their first deadline from the time of the update on.
-
Due column and alerts: a coloured dot with in 3 d / today 17:00 / overdue 2 d (green = in time, amber = less than a quarter of the time left - for deadlines of several days also the last day -, red = overdue). The Workbench tab shows a red badge with the number of overdue entries and due reminders; the Due chip filters what needs attention (overdue, due soon, reminder due); a hint in the status bar at start. Due date and reminder are in the CSV / Markdown export and the CVE viewer.
-
Reminder ("Wiedervorlage"): Remind... in the detail header: tomorrow, in 3 days, 1 week, 2 weeks or a date, with a reason (e.g. waiting for vendor patch); it goes into the note log. From that day the entry counts as due (bell in the Due column, red badge). Done / clear removes it, also noted in the log.
-
Buzz (prototype): how much is a CVE talked about? Buzz (button or context menu) checks the last 7 days: general media and trade press via the GDELT news monitoring (news sites worldwide, also regional papers and broadcasters), Hacker News and the CIRCL Vulnerability-Lookup sightings - no key needed. The column shows a loudspeaker and two volume meters with up to 10 bars: left the general media (red, what management reads), right the community (blue: trade press, Hacker News, sightings); one bar per doubling of the hits (1, 2, 4, 8 ... 512+). The tooltip shows the counts per source and the latest article (leading media such as tagesschau.de or bbc.co.uk first). Search terms are the CVE ID plus the entry's Buzz terms (editor below the grid), e.g. the nickname
PHP2Shell- general media rarely name CVE IDs. Feeds: the Buzz check also reads the RSS/Atom feeds ofbuzz-feeds.txt(button Feeds..., next to the settings; one feed per linecategory | name | URL [| min=risk]). Categoryauthority- government warnings such as CISA, CERT-Bund, CERT-FR, NCSC-NL/UK, CCCS, JPCERT (from the resource list) - is shown as an orange warning sign next to the meters (tooltip: which authority, when, headline); such entries sort first. Feeds with many entries can set a threshold:min=hochlets the CERT-Bund WID advisories set the warning sign only from risk "hoch" on (from the title[NEU] [hoch] ...), the others count towards the community meter. Categorybsireads the BSI Cyber-Sicherheitswarnungen (RSSNewsfeed_CSW.xml): the warning level (1 grey, 2 yellow, 3 orange, 4 red) comes from the feed text or, if missing, from the linked warning (HTML page or its PDF, read with PdfPig); the warning text is also searched for the CVE ID and buzz terms, because the feed itself rarely names them. The warning sign takes the colour of the level (tooltip:BSI orange 2026-10-21), and a raise is noted under Changes (BSI orange -> red).trade(BleepingComputer, heise Security, The Hacker News, SANS ISC diaries, Cisco Talos) andexploit(Exploit-DB, ZDI; Packet Storm retired its RSS feeds in October 2025) count towards the community meter; articles GDELT already found are not counted twice. Each feed is loaded once per run; a feed that does not answer is reported in the Run log ([BUZZ] Feed ... failed) and skipped. A first appearance in the general media, a new authority warning and clear rises are noted under Changes. GDELT allows one request every 5 seconds, so a check takes about 5 s per entry (at most 30 per run; entries checked within the last 2 hours are skipped, the context menu checks the selected entries again). Buzz is an attention signal, not an exploitation signal - KEV and EPSS stay responsible for that. Needs network access toapi.gdeltproject.org,hn.algolia.com,vulnerability.circl.luand the feed hosts. -
References as buzz signal: the references of the CVE record (all sources pooled) already show attention without any request: links to Hacker News, Mastodon or Reddit (community), trade press, exploitation write-ups, PoC repositories, detection rules, mailing lists (oss-security, full-disclosure) and distribution trackers. The Buzz tooltip and the detail list them per category (
community 3, PoC 2, ...); a nickname named at least twice in the references (e.g. GitHub repositorysshhole-reproand websitesshhole.com) is taken over as buzz term, so the media search finds articles that never name the CVE ID. New references after an update are noted under Changes. Live refresh reads the references from NVD as well, so write-ups and PoCs that are added days later (when the CVE is long out of the run's time window) still reach the Workbench entry. In the results: searchrefs:hackernews,is:named(nickname found),is:discussed(community or press references). -
Columns Published and Updated: publication and last update of the CVE record at the source. They come from the run results (Update from results) and from NVD on Live refresh; a newer update date is noted under Changes (
record updated 2026-11-09 (NVD)). Entries added before this version get the dates with the next update. -
Double-click an entry to open the CVE viewer: all values, the rating parameters of the source score, of an additional CVSS 4.0 metric and of the own vector in plain text, notes, last changes and the description (Copy text, Open link; Esc closes it). Several viewers can be open side by side.
-
Per entry: notes with date and time (Add note or Enter adds a line
yyyy-MM-dd HH:mm text; the whole log stays editable, the grid shows the latest note), an own severity (Low ... Critical) and/or an own CVSS vector (3.0 / 3.1, base plus optional temporal and environmental metrics, e.g..../MAV:Lor.../CR:H). The GUI calculates the score (FIRST formulas). The Severity column shows the own severity first, then the rating of the own CVSS score, then the source severity. Rows are coloured by this effective severity (same row colours as the Results tab; known exploited High / Critical in the darker shade); entries with an own assessment are shown in semi-bold. -
Drag and drop a row to change the own order (a blue line shows where it goes: above or below the target row). Column sorting is also possible; after a drop the list shows the own order (
#) again. Remove / Remove all, search. Added, Refreshed and the Changes markers carry date and time. -
Update from results: takes CVSS, EPSS, KEV, version status (OSV / VEX) and applicability from the loaded results and marks the differences in the Changes column (e.g.
CVSS 3.1 7.5 -> 9.8 (NVD); now known exploited). Note and own assessment are never changed. Only scores of the same CVSS version are compared: a 3.x score is kept against a 4.0-only score, a change of the version is marked as such (CVSS 3.1 9.8 (NVD) instead of CVSS 4.0 9.2 (EUVD) - other CVSS version, not a re-rating), and a changed vector with the same score is listed by metric (vector changed (NVD): AC:H -> AC:L). Column Score from shows where the current score comes from. -
Live refresh (no scraper run): EPSS from FIRST, the official CISA KEV catalogue (GitHub mirror as fallback, incl. "known ransomware use") and the NVD CVSS score and vector (6 s per CVE, at most 25 per refresh, oldest first). Uses the proxy setting of the configuration. Start from source vector copies a 3.x source vector into the own vector (the calculator handles CVSS 3.0 / 3.1).
-
Archive handled entries (decision taken): select one or several (Ctrl/Shift+click) and use Archive... or the context menu. One dialog for all selected entries: reason handed over / no action needed / other (preset to the last one used, handed over at first), optional reference (change / ticket number) and note; Enter archives. The reason goes into the note log (
2026-11-09 14:20 Archived: handed over (CHG-1234)). Show archive switches the list to the archived entries; Restore brings them back. Archived entries keep their notes and history, the tab header shows both counts (Workbench (12 · 40 archived)), the Results tab marks CVEs as on workbench / archived (column Workbench), and adding an archived CVE again restores it with what changed since archiving. -
Right-click on the selected entries: Open CVE viewer, Update from results, Live refresh (EPSS, KEV, NVD), Archive... / Restore from archive, Remove. A right-click on an unselected row selects it first.
-
Export CSV... and Copy as Markdown (stand-up / ticket) use the entries currently shown (open list or archive).
-
Stored in
workbench.csvnext to the settings file (saved after every change; UTF-8, culture-independent numbers). The file can be copied or edited in a spreadsheet; keep the header line. Awatchlist.csvof an older version is taken over on the first start (the old file is left as it is).
Build from source (.NET 10 SDK)
dotnet build gui/MetaScraper.slnx
dotnet run --project gui/MetaScraper.Desktop # start
dotnet publish gui/MetaScraper.Desktop -c Release -r linux-x64 -o publish/linux-x64
dotnet publish gui/MetaScraper.Desktop -c Release -r win-x64 -o publish/win-x64
Each publish produces one self-contained, compressed file (about 48 MB).
Structure
gui/MetaScraper.Core- logic without UI: row model, severity/keyword/triage rules (mirror ofmetascraper.ps1), filter, process runner, field table, TOML, results/CSV/known-OSV readers. Keep it in sync with the script rules.gui/MetaScraper.Desktop- the Avalonia window (one partial class file per tab).gui/MetaScraper.Smoke- headless test: opens the window without a display, exercises every tab (with a mockmetascraper.ps1and the realSbomFilterExtractor.ps1) and writes screenshots:dotnet run --project gui/MetaScraper.Smoke -- <outdir> gui/MetaScraper.Smoke/mock-metascraper.ps1(pwsh on PATH).gui/PLAN.md- work plan and status of the migration.
Differences to the WPF GUI
- The run log is a list of coloured lines (copy selected lines or all; Text view for free selection).
- PowerShell 7 output is shown without ANSI colour codes (
NO_COLOR). - Settings are stored in a separate file (
settings-avalonia.json), so both GUIs can be used side by side.
Part of the release packages (next to metascraper.ps1). Besides the language model it pulls the embedding model
for the vector review (-EmbedModels, default nomic-embed-text) and tests it with two product names; localai.json
names both, MetaScraperGui presets them.
Prepares a local language model for the planned AI review of pre-filtered CVEs (see BACKLOG.md): a second opinion
on whether a hit is the affected product or only mentioned. Windows, no administrator rights, everything in one folder
(default %LOCALAPPDATA%\MetaScraper\LocalAI):
-
checks the NVIDIA GPUs (
nvidia-smi): free memory per card, what the desktop and other programs use; the models run on the card with the most free memory (-Gpu auto), on given cards (-Gpu 1,-Gpu 0,1) or spread over all (-Gpu all, slower than one card, much faster than the CPU) - written into the start scripts asCUDA_VISIBLE_DEVICES. With a second card the model runs there and the other one keeps the desktop; with the monitor on the onboard graphics of the CPU the whole card is free for the model; -
downloads the latest portable Ollama (
ollama-windows-amd64.zip) and checks its SHA-256 against the release'ssha256sum.txt; -
writes
Start-LocalAI.cmd/Start-LocalAI.ps1- the server listens on127.0.0.1only, models stay in the folder; for 8 GB cards: one model in memory at a time (OLLAMA_MAX_LOADED_MODELS=1), one request at a time (OLLAMA_NUM_PARALLEL=1- every parallel slot has its own context memory), flash attention and a q8 KV cache (half the context memory), context 4096 tokens (MetaScraper's requests stay well below 2000); -
pulls the models - without
-Modelschosen by-Profileand the free memory of the card (fallback one tier lower when a model cannot be pulled):free GPU memory -Profile standard(default)-Profile eu(Mistral AI, France, Apache 2.0)24 GB cards qwen3.5:27b(from 20 GB, about 17.5 GB)ministral-3:14b-instruct-2512-q8_0(from 17 GB, about 15.2 GB)12 GB cards qwen3.5:9b(from 9 GB, about 6.6 GB)ministral-3:14b(from 10.5 GB, about 9.1 GB)8 GB cards granite3.3:8b(from 6 GB, about 4.9 GB)ministral-3:8b(from 6.8 GB, about 6 GB)less / CPU qwen3:4b(about 2.5 GB)ministral-3:3b(about 3 GB)euis the choice for a setup without US or Chinese language models; the embedding model of the vector review staysnomic-embed-text(it only compares names). The prompts were tuned with Qwen - compare the AI lines of a run before relying on another model family. GGUF models from Hugging Face ashf.co/<owner>/<repo>:<quant>), each on its own - a name the installed Ollama does not know is reported, the others are set up - and sends the same test question to every model (answer, time and how much of the model is on the GPU, from/api/ps: below 100 % parts run on the CPU - a warning names the remedies); -
writes
localai.json(endpoint, OpenAI-compatible endpoint.../v1, models, card, free GPU memory).
.\Setup-LocalAI.ps1
.\Setup-LocalAI.ps1 -Models 'qwen3.5:9b','qwen3.5:4b','phi4-mini'
.\Setup-LocalAI.ps1 -Proxy http://proxy.example:8080 -ProxyUser 'DOMAIN\user'
.\Setup-LocalAI.ps1 -Profile euWith -ProxyUser the password is asked for interactively and kept in memory only. Remove everything by stopping the
server and deleting the folder. The whole output is also written to setup-localai.log in the install folder; started
in its own window ("Run with PowerShell"), the script waits for Enter at the end - also after an error - so the window
does not close before the output is read (-NoPause turns that off).
Vector review (first check, default since v0.6.0-preview.12): an embedding model (nomic-embed-text, about
0.3 GB, installed by Setup-LocalAI.ps1 -EmbedModels) turns the candidate (component without distribution prefix
and version, e.g. postgresql 15) and the products of the CVE into vectors; their cosine similarity in % decides:
from Vector: same product from (default 85 %) the candidate is the product (the version comes from the version check:
affected / not affected / product match), up to Vector: other product up to (default 78 %) it is only
mentioned, in between unclear. The thresholds come from real runs with nomic-embed-text: the same product scored
86-100 %, unrelated short names (bootstrap, gzip, perl ...) 61-76 %; saved settings with the old defaults 80/55 move
to 85/78 by themselves. The AI column shows the similarity where it is the reason of the verdict (e.g. ≈ only
mentioned 64%; a similarity of the names, not a probability). For affected / not affected the version check
decides - no % there, the tooltip names the product name match. Deterministic, fast (also on a CPU), no free text to
parse.
- Other package world: a component from a package registry (purl type maven, npm, pypi ... from the SBOM
inventory) against a CVE that names no package of that ecosystem stays unclear even at a high similarity - the
JDBC driver
postgresql/postgresql(maven) is named like the PostgreSQL server. The language model decides. A CVE that names the package itself (maven:commons-codec/commons-codec) is compared with that package name. - Review with:
vector+llm(default),vector,llm. Invector+llmthe language model gets every unclear component - only those, not the ones the vector review already decided - and its verdicts replace the unclear ones (also when the main verdict of the CVE was clear). Without the embedding model the language model reviews alone (logged). Calibrate the thresholds with the[AI] ... by vector (similarity ...)lines of the run log.
AI review in MetaScraperGui (Configuration > AI review (local model); preset from localai.json, switched on
when it exists): a second opinion of the local model on pre-filtered entries - affected, only mentioned, not
affected or unclear, with a short reason. Advisory only: it never changes the score. The work is split: the
model judges only the product - is the component the vulnerable product itself, or only mentioned (platform,
framework, a similarly named product such as laravel-mongodb vs. laravel/framework)? The version comes from the
version check of the script (given to the model as a fact). Together: same product + version affected = affected,
same product + version outside the range = not affected, same product + version not checkable = product match;
otherwise only mentioned or unclear. By default only components with version (SBOM / Dependency-Track) are
reviewed - keyword-only hits add little to the Matched / Appl. / Triage columns. Pair keywords (vendor|product) are coordinates you maintain and
are always reviewed; the request also carries the coordinates of the CVE's sources (vendor|product|range, results
format 2), so the model can compare like with like. With several candidates (e.g. the PostgreSQL server package and the JDBC driver) the model gives one verdict per
component; the row shows the most important one (affected > product match > partial > unclear > vendor only > not
affected > only mentioned), the tooltip and the run log all of them - a side component that is only mentioned no longer
hides the real hit. Single keyword hits are reviewed when they are a strong indicator
(keyword score >= the applicability threshold, i.e. a hit in vendor or product, e.g. nginx in
nginx-http-flv-module). Where a keyword stands is worked out by rule
first - the product name (full), part of the product name, only the vendor, only the description; the vendor's
name inside the product name does not count (Microsoft SharePoint Server), unless the product is just the vendor
name plus an edition (GitLab CE). A keyword that only names the vendor is decided by rule as vendor only, without
a model request; a keyword that is only part of the product name can be at most a ◐ partial match (e.g. nginx in
nginx-http-flv-module, which may be another product), never a full product match. The switch Also review keyword-only
hits adds all single keywords (product match, vendor only - check whether you run that product - , only mentioned).
- What is reviewed, automatically when the model is available: Workbench entries without a valid verdict; Results with a strong indicator - applicability >= 60 (configuration; the triage only sets the order), once per CVE, after each run and after loading results (switch Review results after each run); context menu AI review (again) on the selection in both tabs - only the selected entries, asked again, ahead of a running automatic pass (which continues afterwards); an entry with nothing to judge against is named in the run log. Always the most urgent first (triage, then applicability); at most Max. reviews per pass model requests - when more entries are open, the GUI asks: review all now, only the most urgent up to the limit (the rest in the next pass), or not now; one request per CVE (its result rows and its workbench entry share the verdict). The model can only name one of the candidates (enforced by the JSON schema); an unusable answer is not asked again automatically in the same session.
- In the background, one request at a time; the AI symbol (microchip) in the header: grey = no AI (server not
running), green = ready, blinking = running (tooltip with progress), red = problem (e.g. model missing); a click
starts a pass or cancels it. Column AI in Results and Workbench shows every state - the verdict, … waiting,
! no answer (unusable answer), ○ open (meets the criteria of the review - applicability from the threshold,
pair keyword, component or strong keyword - but has no verdict yet; the tooltip says why: limit per pass, "not now",
AI not available, cancelled), – not needed (below the thresholds or nothing of your environment to judge against;
the tooltip names the reason) or – not reviewed (AI review switched off) (tooltip: reason, confidence, model, time;
CSV export columns AI, AIComponent, AIConfidence, AIReason, AIModel, AIChecked), AI filter in the filter bar of
Results (reviewed, each verdict, no answer, open, not needed, not reviewed; shown as removable tag), search
is:ai-affected,is:ai-mentioned,is:ai-notaffected,is:ai-unclear,is:ai-open,is:ai-notneeded,is:ai-unchecked. - Exotic configuration (palm tree): after the product review the language model also reads the description of
the hits (affected, product / partial match, unclear) and of the Workbench entries and lists the
conditions of the setup the vulnerability needs (build, platform, linked libraries, enabled features,
configuration, kind of input - not versions, attacker privileges or user interaction), each classified as default,
common option or uncommon. The rating comes from a rule: no uncommon condition = common, one = uncommon,
two or more = rare (nothing to go by = unclear). A palm tree in its own column next to AI (Results and
Workbench) marks only rare; the column header explains it ("Multiple uncommon or unlikely conditions are
required to enable this vulnerability"), the cell tooltip lists the conditions with their class, e.g. OpenSSH
built against musl libc (uncommon); without PAM (uncommon). Uncommon is in the tooltip and the AI filter
(exposure: rare / uncommon). Also in the detail pane, the Workbench viewer, the CSV export (
Exposure,ExposureConditions), the workbench file and the search (is:rare,is:uncommon,is:exotic). One more request per CVE, stored per CVE record state and model (prompte5, with examples; a sentence about affected or fixed versions, attacker privileges and the attacker as a client of a server are no conditions, a user action the attack needs and a malicious or already compromised server the target connects to are); advisory, no effect on the score. Switch: Assess exotic configurations (palm tree) (on by default; not in the modevector). - When the AI runs: not at the start - the GUI shows the verdicts in the store (no server contact, no model is loaded); open entries say "not reviewed since the start". A review starts with new results (a run, a results file opened by hand), entries added to or updated in the Workbench, a saved config with a changed filter list, or a click on the AI symbol / right-click > AI review (again). Every automatic pass looks into the store first and contacts the server only when something is still open.
- One model at a time: a pass first runs the vector review for every entry, then unloads the embedding model and only then asks the language model about the unclear ones - both models never sit in the GPU memory together.
- Logged: a run log line per review (
[AI] CVE-... component: verdict (confidence) reason - model, seconds), the audit fileai_review_log.jsonlnext to the settings, and in the Workbench a Changes note when a verdict is new or changes; the verdict is saved with the workbench. - Token-thrifty: one short fixed system prompt, a compact block per CVE (description cut to 1,200 characters, no references or raw records), the answer forced to a JSON schema (at most 160 tokens), temperature 0, thinking off, context 8,192, the model kept loaded during a pass. An answer naming a component outside the candidates, or invalid JSON, is dropped.
- Store (
ai_review_cache.json): verdicts for uniquely identified components (SBOM / Dependency-Track components with version) are kept - components overlap a lot between projects; a verdict expires when the CVE is updated, the component version changes, the model or prompt changes, or after Verdicts valid (days) (default 10). Keyword-only hits are reviewed but kept for the session only.
- Unstable APIs: endpoints, parameters and response formats change without notice. CIRCL, for
example, moved from
cve.circl.lu/api/vulnerability/recent/tovulnerability.circl.lu/api/vulnerability/. A red or yellow indicator in the GUI, orERROR/EMPTYin the[API-STATUS]lines, shows which source is affected. The log contains the exact URL and error. - Temporary server errors (HTTP 429, 502, 503, 504, e.g. CIRCL's "503 Backend fetch failed / Guru Meditation" from its Varnish cache): every request is retried twice, after 5 and 15 seconds. If the error persists, the source turns red and the run continues with the other sources. Try again later.
- Rate limiting and blocking: APIs may block your IP temporarily after too many requests. Most limits
reset within 24 h. Keep
MaxPages/PageSizemoderate and don't run the scraper in tight loops. - Double-clicking a
.ps1opens the editor: this is Windows' default behaviour. Use the.cmdstarters, or run the script from a PowerShell window (.\metascraper.ps1 ...). - GUI hangs or a run fails right away on another machine (often Windows PowerShell 5.1 or a managed PC):
downloaded scripts carry the "mark of the web", and PowerShell asks "Security warning … Run once?". Run
Get-ChildItem *.ps1 | Unblock-Filein the script folder once. The GUI detects blocked scripts and offers to unblock them. It runs scripts non-interactively, so a prompt shows up as an error in the output instead of a hang. If the log reports an execution policy set by Group Policy,-ExecutionPolicy Bypasshas no effect and only unblocking (or your admin) helps. - Unsigned script: use
-ExecutionPolicy Bypass(asStart-MetaScraperGUI.cmddoes) orUnblock-Fileon downloaded files. - Slow runs: use
LogLevel = "troubleshooting"(orminimal) instead ofdebug, which logs the API responses in detail. - Umlauts garbled in the GUI run log (
AUSFšHRLICH): the script is older than build 2026-11-09g; current scripts write UTF-8 when the GUI reads their output. - GUI warns "metascraper.ps1 is outdated": the selected script is older than the GUI expects (build marker
$script:ScriptBuild, first line of the run log). Select the script that came with the GUI (Browse...; in MetaScraperGui on the Configuration tab, This app, or Default); a run with the old script is possible after a confirmation. - Proxy: set
proxy = trueand providehttps_proxy, or the Proxy URL in the GUI. HTTP 407 (the log shows the proxy's HTML page): the proxy wants a login - turn on Use proxy, set the Proxy URL and, if the Windows login is not accepted, the Proxy user (the GUI asks for the password once per session). - "CIRCL API returned non-JSON content" / CIRCL EMPTY with 0 records: some records contain keys that differ only in
case (e.g.
cweandCWE) or empty keys. PowerShell's JSON conversion rejects them, andInvoke-RestMethodthen returns the whole page as text. The script now parses such responses leniently (the first of the keys is kept) and logsparsed leniently; this applies to every source. - CIRCL page cut off (
Unterminated string ... position 91856016in the log): with full records (light=0) one page of 100 entries can exceed 90 MB because of large CSAF documents, and the transfer breaks off. The script then requests the same position again with half the page size, down to a single record ([CIRCL] Page N (...): response not readable ... retrying). A single record that is still too large is fetched as a light record (light=1, fewer details) or, if that fails too, skipped with its ID in the log ([CIRCL] Entry 52 (CVE-...) ... skipped). Afterwards the page size grows back. A cut-off response is never taken as a short last page. WithIncludeUpdateson the script reads CIRCL with at most 25 per page, which avoids most of the large downloads from the start. - Secrets in the log:
GitHubToken(and any setting named like a token, password or API key) is logged as***** (set). Logs of older versions contain the token in plain text - revoke it if you shared such a log. In the cross-platform GUI the token field is masked (screen sharing); Show reveals it while the button is held down. The value is still written to the run'sconfig.tomland the GUI settings file (plain text on disk) - or leave the field empty and set the environment variableGITHUB_TOKEN.
The token is only used to read the public GitHub Advisory Database (higher rate limit). Use only a token that can do nothing else:
- GitHub -> Settings -> Developer settings -> Fine-grained tokens -> Generate new token;
- Repository access: "Public repositories (read-only)";
- no account permissions and no repository permissions;
- an expiration date (e.g. 30-90 days).
Such a token can only read public data - if it leaks, someone can at most use up your rate limit. Never use a
classic token or a token with write access (e.g. repo scope or write permission to your repositories): it is
stored in plain text (GUI settings %APPDATA%\MetaScraperGUI\settings-avalonia.json, the config.toml of each run
folder under %LOCALAPPDATA%\MetaScraperGUI\runs), and a leaked token with write access allows reading private
repositories or changing code that others run. Do not pass on logs, run folders or config.toml files with a token;
revoke a token that was shared by mistake. Keep this token separate from the credentials you use for Git
(commit / push).
Apache License 2.0 - see LICENSE. The GUI builds contain third-party libraries under their own licenses (MIT, BSD, Apache 2.0, ISC, SIL OFL), listed in THIRD-PARTY-NOTICES.md. Security problems: see SECURITY.md.
Please feel free to contribute, maintain and keep the lists up to date. Any help is welcome.
Thanks, Boa






