An effective CVE monitoring system doesn’t require a 50-person SOC. With NVD’s JSON feed, a CVSS+EPSS prioritization pipeline, and tools like Nuclei, a 2-3 person team can filter out 95% of the noise and focus on what truly impacts their stack. Here’s the battle-tested architecture we use at CyberShield for LATAM clients, complete with real code and honest trade-offs.
Why NVD’s JSON feed is the foundation (and what they don’t tell you about it)
The NVD JSON feed is the de facto standard for CVE monitoring, but its widespread adoption obscures three critical issues no tutorial ever mentions:
- 2-8 hour latency: NVD updates its feeds every 2 hours (best-case scenario), but synchronization with vendor repositories (Microsoft, Red Hat, etc.) can take up to 8 hours. For critical vulnerabilities (e.g., Log4Shell), those hours can mean the difference between containing an attack and suffering a breach. We’ve documented this at CyberShield: in 2023, 18% of CVEs with CVSS ≥ 9.0 we monitored appeared in vendor feeds before NVD.
- False positives in LATAM products: NVD uses the
productfield to identify affected software, but many regional vendors (e.g., banks with legacy applications, local ERPs) aren’t mapped. A CVE for "SistemaContableXYZ v2.1" might appear as generic, forcing you to cross-reference with other sources. - The CPE problem: NVD’s Common Platform Enumeration (CPE) entries are notoriously inconsistent. The same product might appear as
cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*orcpe:2.3:a:vendor:product:*:*:*:*:*:*:*:*, breaking automated filters if you don’t normalize the strings.
The solution isn’t to abandon NVD but to complement it. In our architecture, we use:
- NVD JSON feed as the primary source (for its global coverage).
- Vendor-specific feeds (Microsoft, Red Hat, Ubuntu) to reduce latency for critical products.
- APIs from repositories like GitHub Advisory Database for open-source software (e.g., Python/Node.js libraries).
The code to download and parse NVD’s JSON feed is trivial (Python example):
import requests
import json
from datetime import datetime, timedelta
NVD_FEED_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
LAST_MODIFIED = (datetime.now() - timedelta(hours=2)).strftime("%Y-%m-%dT%H:%M:%S:000 UTC-00:00")
def fetch_nvd_feed():
params = {
"pubStartDate": LAST_MODIFIED,
"resultsPerPage": 2000 # Maximum allowed by NVD
}
headers = {"apiKey": "YOUR_NVD_API_KEY"} # Requires NVD registration
response = requests.get(NVD_FEED_URL, params=params, headers=headers)
return response.json()
Key trade-off: NVD limits requests to 5 per 30 seconds without an API key, and 50 with one. Exceed this limit, and your IP will be temporarily blocked. For small teams, this isn’t an issue, but if you scale to hundreds of daily queries, you’ll need a local caching system (e.g., Redis) or a rotating proxy.
Prioritization: CVSS isn’t enough (and how EPSS complements it)
The Common Vulnerability Scoring System (CVSS) is the standard for measuring CVE severity, but it has a fundamental flaw: it doesn’t measure real risk to your organization. A CVE with CVSS 9.8 in software you don’t use is irrelevant, while one with CVSS 6.5 in your primary database could be critical.
The solution is to combine CVSS with the Exploit Prediction Scoring System (EPSS), developed by FIRST. EPSS predicts the likelihood of a CVE being exploited within the next 30 days, using data from:
- Activity in exploit repositories (e.g., Exploit-DB, GitHub).
- Mentions in hacker forums (e.g., dark web, Telegram).
- Exploitation patterns in the wild (e.g., honeypot data).
EPSS updates daily and is available as a CSV feed or API. The key metric is the EPSS percentile: a CVE with EPSS ≥ 0.8 (80th percentile) has an 80% chance of being exploited within 30 days.
In our architecture, we prioritize CVEs as follows:
| CVSS | EPSS Percentile | Priority | Action |
|---|---|---|---|
| ≥ 9.0 | ≥ 70 | Critical | Patch within <24h |
| 7.0 - 8.9 | ≥ 50 | High | Patch within <7 days |
| 4.0 - 6.9 | ≥ 30 | Medium | Evaluate mitigation |
| < 4.0 | < 30 | Low | Monitor |
Real-world example: In January 2024, CVE-2024-21626 (runc) had a CVSS of 8.6 but an EPSS of 0.92 (92nd percentile). While the CVSS was "high," the EPSS indicated it was almost certain to be exploited. Indeed, public exploits emerged within 5 days. Teams relying solely on CVSS prioritized it as "medium," while those combining CVSS+EPSS patched it before exploits were published.
Code to integrate EPSS into your pipeline (using FIRST’s CSV feed):
import pandas as pd
def load_epss_data():
url = "https://epss.cyentia.com/epss_scores-current.csv.gz"
df = pd.read_csv(url, compression="gzip")
df["epss_percentile"] = df["epss"].rank(pct=True) * 100
return df.set_index("cve")
epss_df = load_epss_data()
def get_priority(cve_id, cvss_score):
epss_row = epss_df.loc[cve_id]
epss_percentile = epss_row["epss_percentile"]
if cvss_score >= 9.0 and epss_percentile >= 70:
return "CRITICAL"
elif cvss_score >= 7.0 and epss_percentile >= 50:
return "HIGH"
elif cvss_score >= 4.0 and epss_percentile >= 30:
return "MEDIUM"
else:
return "LOW"
Stack filtering: how to eliminate 95% of the noise
The biggest mistake in CVE monitoring is failing to filter by the organization’s actual technology stack. A team using only Python, PostgreSQL, and Nginx shouldn’t receive alerts about vulnerabilities in Apache Tomcat or Microsoft Exchange.
The solution is to maintain an up-to-date inventory of:
- Installed software (OS, libraries, frameworks).
- Exact versions (e.g., not "Python 3.x" but "Python 3.9.7").
- Transitive dependencies (e.g., a Python library might depend on OpenSSL).
Tools to generate this inventory:
- OpenVAS: A vulnerability scanner that also generates a software inventory. Ideal for servers and endpoints.
- Nuclei: ProjectDiscovery’s tool for vulnerability scanning, which includes templates to detect software versions. Lighter than OpenVAS but requires manual configuration.
- Dependabot/GitHub Advisory: For code repositories (detects vulnerabilities in dependencies).
Example command with Nuclei to detect software versions on a server:
nuclei -u https://yourserver.com -t nuclei-templates/http/technologies/ -json -o technologies.json
The technologies.json file will generate output like:
[
{
"template-id": "nginx-version",
"info": {
"name": "Nginx Version Detection",
"severity": "info",
"description": "Detects Nginx version",
"reference": [],
"tags": ["tech", "nginx"]
},
"host": "https://yourserver.com",
"matched-at": "https://yourserver.com",
"extracted-results": ["nginx/1.21.6"]
}
]
With this inventory, you can filter NVD CVEs using CPEs. Python example:
import re
Software inventory (example)
INVENTORY = {
"nginx": ["1.21.6", "1.23.1"],
"openssl": ["1.1.1k"],
"python": ["3.9.7"]
}
def is_cve_relevant(cve_data, inventory):
cpe_matches = []
for cpe in cve_data.get("configurations", []):
for node in cpe.get("nodes", []):
for cpe_match in node.get("cpeMatch", []):
cpe_str = cpe_match.get("criteria")
if not cpe_str:
continue
# Extract vendor, product, version from CPE
match = re.match(r"cpe:2\.3:[aoh]:([^:]+):([^:]+):([^:]+)", cpe_str)
if not match:
continue
vendor, product, version = match.groups()
product_key = f"{vendor}:{product}".lower()
# Check if product is in inventory
for inv_product, inv_versions in inventory.items():
if inv_product in product_key:
if version == "*" or version in inv_versions:
cpe_matches.append(True)
else:
# Check if CVE version is <= installed version
# (assuming CVE affects versions <= X)
try:
cve_version = parse_version(version)
for inv_version in inv_versions:
if parse_version(inv_version) <= cve_version:
cpe_matches.append(True)
except:
pass
return any(cpe_matches)
Usage example
cve_data = {
"configurations": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:nginx:nginx:1.21.0:*:*:*:*:*:*:*",
"vulnerable": True
}
]
}
]
}
]
}
print(is_cve_relevant(cve_data, INVENTORY)) # True (because 1.21.0 <= 1.21.6)
Key trade-off: This approach requires keeping the inventory updated. If a developer installs a new library without notifying anyone, the system won’t detect it. The solution is to automate the inventory with tools like OpenVAS or Nuclei and run them weekly.
Alert pipeline: how to avoid overwhelming the team
The biggest risk in a CVE monitoring system isn’t missing a critical vulnerability but overwhelming the team with irrelevant alerts. A poorly designed pipeline generates:
- Alert fatigue: the team ignores notifications due to excessive noise.
- False positives: alerts that don’t apply to the organization’s stack.
- False negatives: critical vulnerabilities lost in the noise.
Our pipeline architecture has four stages:
- Ingestion: Downloading feeds (NVD, vendors, EPSS).
- Filtering: Removing CVEs that don’t apply to the stack (using the inventory).
- Prioritization: Assigning priority based on CVSS+EPSS.
- Notification: Sending alerts only for critical and high priorities, with relevant context.
Tools to implement the pipeline:
- Prometheus + Alertmanager: For monitoring and alerts. Configure Alertmanager to group similar alerts and avoid spam.
- Slack/Teams webhooks: For real-time notifications. Use clear formatting and quick actions (e.g., a button to mark as "patched").
- Jira/ServiceNow: For vulnerability tracking. Automatically create tickets for critical and high priorities.
Example Alertmanager configuration to group alerts by product:
route:
group_by: ['product']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: 'slack-notifications'
receivers:
- name: 'slack-notifications'
slack_configs:
- channel: '#security-alerts'
send_resolved: true
title: '{{ .CommonLabels.severity }}: {{ .CommonLabels.product }}'
text: |-
*CVE*: {{ .CommonLabels.cve_id }}
*CVSS*: {{ .CommonLabels.cvss_score }}
*EPSS*: {{ .CommonLabels.epss_percentile }}%
*Description*: {{ .CommonAnnotations.description }}
*Action*: {{ .CommonAnnotations.action }}
Golden rule: Never send an alert without context. Every notification should include:
- The CVE ID and its NVD link.
- The CVSS and EPSS percentile.
- The affected product and its version in your stack.
- A clear description of the impact (e.g., "RCE in Nginx 1.21.6").
- The recommended action (e.g., "Upgrade to Nginx 1.23.1").
Real case: how we handled Log4Shell for a LATAM client
On December 9, 2021, CVE-2021-44228 (Log4Shell) was published, with a CVSS of 10.0. Our pipeline detected it at 10:17 AM (UTC-3), 37 minutes after it appeared in NVD’s feed. Here’s what happened:
- Ingestion: The NVD JSON feed captured it automatically.
- Filtering: Our inventory showed the client used Log4j 2.14.1 on 3 servers.
- Prioritization: CVSS 10.0 + EPSS 96% (96th percentile) = critical priority.
- Notification: An alert was sent to Slack with:
- CVE ID and NVD link.
- CVSS 10.0, EPSS 96%.
- Affected products: "Log4j 2.14.1 on servers A, B, C".
- Description: "RCE via JNDI lookup in Log4j 2.x <= 2.14.1".
- Action: "Mitigate with -Dlog4j2.formatMsgNoLookups=true or upgrade to 2.15.0".
- Response: The client’s DevOps team applied the mitigation within 2 hours and patched to 2.15.0 within 24 hours.
Lessons learned:
- NVD latency matters: Had we waited 8 hours, the client would have been exposed over the weekend (when many teams don’t monitor alerts).
- EPSS was key: While the CVSS was already 10.0, the EPSS of 96% confirmed it was almost certain to be exploited, accelerating the response.
- The inventory saved hours: Without an up-to-date inventory, we would have had to manually scan servers to check for Log4j usage.
This case is why CyberShield includes real-time CVE monitoring as part of its core stack. For LATAM SMEs, where security teams are small, automation isn’t a luxury—it’s the difference between containing an attack and suffering a breach.
Trade-offs and limitations: what no one tells you
No CVE monitoring system is perfect. These are the trade-offs we’ve encountered in practice:
- False negatives in software not mapped by NVD:
NVD doesn’t cover all products, especially those from regional vendors or legacy software. Example: In 2023, a LATAM client used a local ERP with a critical vulnerability that never appeared in NVD. The solution was to complement with:
- Manual scans using OpenVAS/Nuclei.
- Monitoring local vendor forums.
- Alerts from regional CERTs (e.g., CERT.br, OAS CSIRT).
- EPSS isn’t infallible:
EPSS predicts exploitation likelihood but isn’t 100% accurate. In 2022, CVE-2022-22965 (Spring4Shell) had an EPSS of 0.12 (12th percentile) when published but was massively exploited within 48 hours. The lesson: EPSS is one tool among many, not an oracle. Always manually verify high-CVSS CVEs, even if EPSS is low.
- The transitive dependency problem:
A CVE in a library you don’t directly use can still affect you if it’s a dependency of another software. Example: A CVE in OpenSSL might affect Nginx, even if Nginx isn’t listed in the CVE. The solution is:
- Using tools like
dependency-check(OWASP) to analyze dependencies. - Maintaining an updated dependency graph.
- Using tools like
- Inventory updates are manual (to an extent):
While tools like OpenVAS and Nuclei automate software detection, they don’t catch everything. Example: A developer might install a Python library with
pip install --user, which won’t appear in global scans. The solution is:- Frequent scans (weekly).
- Change policies: notify the security team before installing new software.
- Monitoring code repositories (e.g., GitHub Dependabot).
These trade-offs aren’t reasons to avoid implementing a CVE monitoring system but reasons to design it pragmatically. An imperfect but functional system is better than no system at all.
The architecture described here is what we use at CyberShield for LATAM clients, with adjustments based on each organization’s stack and resources. It’s not the only way to do it, but it’s a proven approach that balances automation, precision, and scalability for small teams.
Real-time CVE monitoring isn’t a project with a finish line—it’s an ongoing process of refinement. Feeds change, stacks evolve, and threats adapt. The advantage of a system like this is that it provides real-time visibility, allowing you to respond before a vulnerability becomes a breach. For small teams, that visibility is the difference between being proactive and reactive.
In an ecosystem where 60% of breaches exploit known vulnerabilities (according to Verizon’s 2023 Data Breach Investigations Report), not monitoring CVEs is like sailing without radar. The question isn’t whether you can afford to implement such a system but whether you can afford not to. The CyberShield team continues refining this architecture because, in cybersecurity, the only constant is change—and the only effective defense is continuous adaptation.
Sources
- NIST National Vulnerability Database (NVD). (2024). NVD JSON Feeds Documentation. https://nvd.nist.gov/vuln/data-feeds#JSON_FEED
- FIRST. (2024). Exploit Prediction Scoring System (EPSS). https://www.first.org/epss/
- ProjectDiscovery. (2024). Nuclei Documentation. https://docs.projectdiscovery.io/tools/nuclei
- Greenbone Networks. (2024). OpenVAS Documentation. https://www.openvas.org/
- Verizon. (2023). 2023 Data Breach Investigations Report. https://www.verizon.com/business/resources/reports/dbir/
- NIST. (2020). NIST SP 800-218: Secure Software Development Framework (SSDF). https://csrc.nist.gov/publications/detail/sp/800-218/final
- CISA. (2022). Binding Operational Directive 22-01: Reducing the Significant Risk of Known Exploited Vulnerabilities. https://www.cisa.gov/binding-operational-directive-22-01
- MITRE. (2024). Common Vulnerabilities and Exposures (CVE) List. https://cve.mitre.org/
- GitHub. (2024). GitHub Advisory Database. https://github.com/advisories
- CyberShield System. (2024). Log4Shell Case: Post-Incident Analysis. Internal document (available upon request).
