← Back to feed API docs Patch now

Grafana, Prometheus & Slack Integration

Bring vulnfeed CVE data into your monitoring stack. The /vulns.json API is open, no auth required.

https://vulnfeed.it/vulns.json
JSON endpoint — no auth, CORS open, updated every 4h
https://vulnfeed.it/feed.xml
RSS 2.0 feed — subscribe in any reader
https://vulnfeed.it/badge/critical-count.svg
SVG badge — embed in README or docs

📊 Grafana via Infinity Plugin

Step-by-step Connect vulnfeed to Grafana

The Infinity data source plugin can query any JSON URL directly — no backend needed.

  1. In Grafana: Connections → Add data source → search "Infinity" and install if not already present.
  2. Add a new Infinity data source — no configuration needed (leave URL blank, we set it per-panel).
  3. Create a new Dashboard. For each panel, set data source to Infinity, type = JSON, source = URL, URL = https://vulnfeed.it/vulns.json.
  4. Add column selectors: id, severity, score, epss_pct, title, published.
  5. Use Transformations → Filter by value to filter by severity, or Sort by EPSS percentile.

Dashboard JSON Paste-ready panel config

In Grafana, use Dashboard settings → JSON model to paste this starter config (replace with your Infinity datasource UID).

{
  "title": "vulnfeed CVE Dashboard",
  "panels": [
    {
      "title": "Critical CVEs",
      "type": "stat",
      "datasource": "vulnfeed-infinity",
      "targets": [{
        "type": "json", "source": "url",
        "url": "https://vulnfeed.it/vulns.json",
        "parser": "backend",
        "root_selector": "",
        "columns": [{"selector": "severity","text": "severity","type": "string"}],
        "filters": [{"field":"severity","operator":"==","value":"CRITICAL"}]
      }],
      "options": {"reduceOptions": {"calcs": ["count"]}}
    },
    {
      "title": "All CVEs by Severity",
      "type": "piechart",
      "datasource": "vulnfeed-infinity",
      "targets": [{
        "type": "json", "source": "url",
        "url": "https://vulnfeed.it/vulns.json",
        "parser": "backend",
        "columns": [
          {"selector": "severity","text": "Severity","type": "string"},
          {"selector": "score","text": "Score","type": "number"}
        ]
      }],
      "options": {"pieType": "donut"}
    },
    {
      "title": "Top CVEs by EPSS",
      "type": "table",
      "datasource": "vulnfeed-infinity",
      "targets": [{
        "type": "json", "source": "url",
        "url": "https://vulnfeed.it/vulns.json",
        "parser": "backend",
        "columns": [
          {"selector": "id","text": "CVE ID","type": "string"},
          {"selector": "title","text": "Title","type": "string"},
          {"selector": "severity","text": "Severity","type": "string"},
          {"selector": "score","text": "CVSS","type": "number"},
          {"selector": "epss_pct","text": "EPSS %ile","type": "number"},
          {"selector": "published","text": "Published","type": "string"}
        ]
      }],
      "transformations": [{"id":"sortBy","options":{"fields":[{"desc":true,"displayName":"EPSS %ile"}]}}]
    }
  ]
}

📈 Prometheus / node_exporter textfile

Shell script Textfile collector exporter

Run this script via cron every 15 minutes. It writes a .prom file that node_exporter's textfile collector picks up automatically. Then alert on vulnfeed_kev_total > 0 or build panels in Grafana from Prometheus.

#!/bin/bash
# Prometheus textfile exporter for vulnfeed CVE metrics
# Run via cron every 15min, write to node_exporter textfile dir
# cron: */15 * * * * /opt/vulnfeed-exporter.sh > /var/lib/node_exporter/textfile_collector/vulnfeed.prom

OUT=$(curl -sf "https://vulnfeed.it/vulns.json")
if [ -z "$OUT" ]; then exit 1; fi

CRITICAL=$(echo "$OUT" | jq '[.[] | select(.severity=="CRITICAL")] | length')
HIGH=$(echo "$OUT"     | jq '[.[] | select(.severity=="HIGH")]     | length')
MEDIUM=$(echo "$OUT"   | jq '[.[] | select(.severity=="MEDIUM")]   | length')
KEV=$(echo "$OUT"      | jq '[.[] | select(.badge=="ACTIVELY EXPLOITED")] | length')
POC=$(echo "$OUT"      | jq '[.[] | select(.poc==true)] | length')
TOTAL=$(echo "$OUT"    | jq 'length')

cat <
    

Prometheus rules Example alerting rules

groups:
  - name: vulnfeed
    rules:
      - alert: NewKEVVulnerabilities
        expr: vulnfeed_kev_total > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "{{ $value }} CVEs are actively exploited (CISA KEV)"
          runbook: "https://vulnfeed.it/patch-now.html"

      - alert: CriticalCVESpike
        expr: vulnfeed_cves_by_severity{{severity="CRITICAL"}} > 20
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "{{ $value }} critical CVEs tracked — review patch list"
          runbook: "https://vulnfeed.it/patch-now.html"

💬 Slack / PagerDuty webhook

Shell script Slack alert for KEV CVEs

Post a Slack message every 4 hours listing the top actively exploited CVEs. Set your SLACK_WEBHOOK from Slack → Apps → Incoming Webhooks.

#!/bin/bash
# Post new critical KEV CVEs to Slack every 4 hours
# cron: 0 */4 * * * /opt/vulnfeed-slack.sh

SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

NEW_KEV=$(curl -sf "https://vulnfeed.it/vulns.json" | jq -r '
  [.[] | select(.badge=="ACTIVELY EXPLOITED" and .severity=="CRITICAL")]
  | sort_by(-.score)
  | .[:5][]
  | "• *\(.id)* (\(.severity) \(.score // "?"))\n  \(.title[:100])\n  "
' | head -20)

if [ -z "$NEW_KEV" ]; then exit 0; fi

curl -sf -X POST "$SLACK_WEBHOOK" -H 'Content-type: application/json' -d "{
  \"text\": \":rotating_light: *vulnfeed — Active Exploits*\",
  \"blocks\": [
    {\"type\":\"header\",\"text\":{\"type\":\"plain_text\",\"text\":\":rotating_light: Active CVE Exploits — $(date +%Y-%m-%d)\"} },
    {\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"$NEW_KEV\"} },
    {\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"\"} }
  ]
}"

📄 curl & jq recipes

Shell One-liners for the terminal

# Count critical CVEs
curl -s https://vulnfeed.it/vulns.json | jq '[.[] | select(.severity=="CRITICAL")] | length'

# List actively exploited CVEs with scores
curl -s https://vulnfeed.it/vulns.json | jq -r '.[] | select(.badge=="ACTIVELY EXPLOITED") | [.id,.severity,(.score|tostring),.title[:60]] | @tsv'

# Top 10 by EPSS percentile
curl -s https://vulnfeed.it/vulns.json | jq -r '[.[] | select(.epss_pct != null)] | sort_by(-.epss_pct) | .[:10][] | "\(.epss_pct)%ile  \(.id)  \(.title[:60])"'

# CVEs with public PoC and score >= 9
curl -s https://vulnfeed.it/vulns.json | jq '[.[] | select(.poc==true and (.score // 0) >= 9)]'

# Filter by product keyword
curl -s https://vulnfeed.it/vulns.json | jq '[.[] | select(.title | ascii_downcase | contains("nginx"))]'

# Export to CSV
curl -s https://vulnfeed.it/vulns.json | jq -r '["id","severity","score","epss_pct","title"],(.[] | [.id,.severity,(.score|tostring),(.epss_pct|tostring),.title[:80]]) | @csv' > vulns.csv

🐍 Python

Python 3 Query the API — stdlib only

import json, urllib.request

with urllib.request.urlopen("https://vulnfeed.it/vulns.json") as r:
    vulns = json.load(r)

# Critical + actively exploited
urgent = [
    v for v in vulns
    if v.get("severity") == "CRITICAL"
    and v.get("badge") == "ACTIVELY EXPLOITED"
]

for v in sorted(urgent, key=lambda x: -(x.get("score") or 0)):
    print(f"{v['id']} ({v['score']}) — {v['title'][:80]}")

📄 Available fields in vulns.json

id
CVE ID or advisory ID
title
Short description
severity
CRITICAL / HIGH / MEDIUM / LOW / UNKNOWN
score
CVSS v3 base score (0–10)
epss
EPSS probability (0–1)
epss_pct
EPSS percentile (0–100)
badge
"ACTIVELY EXPLOITED" if on CISA KEV
poc
true if public exploit code exists
source
NVD / CISA KEV / Ubuntu / Debian / …
published
ISO 8601 publication date
references
Array of advisory/patch URLs
affected
Array of affected products
url
Canonical source URL

Full schema: API documentation