"""
Vulnerability Timeline Tool
This module provides functionality to track vulnerability timeline including
patch availability, vendor responses, and remediation status.
"""
import re
from datetime import datetime
from typing import List, Optional
import httpx
import mcp.types as types
def parse_date(date_str: str) -> Optional[datetime]:
"""Parse various date formats commonly found in vulnerability data."""
if not date_str:
return None
# Remove timezone info for simplicity
date_str = date_str.replace("Z", "").replace("+00:00", "")
try:
# Try ISO format first
if "T" in date_str:
return datetime.fromisoformat(date_str.split("T")[0])
else:
return datetime.fromisoformat(date_str)
except (ValueError, TypeError):
# Try other common formats
for fmt in ["%Y-%m-%d", "%Y/%m/%d", "%m/%d/%Y", "%d/%m/%Y"]:
try:
return datetime.strptime(date_str, fmt)
except (ValueError, TypeError):
continue
return None
async def get_vulnerability_timeline(
cve_id: str,
) -> List[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""
Get vulnerability timeline including patch status and remediation information.
Args:
cve_id: CVE identifier in format CVE-YYYY-NNNN
Returns:
List of content containing timeline information or error messages
"""
# Clean up CVE ID format
cve_id = cve_id.upper().strip()
if not cve_id.startswith("CVE-"):
cve_id = f"CVE-{cve_id}"
# Validate CVE ID format (CVE-YYYY-NNNN)
if not re.match(r"^CVE-\d{4}-\d{4,}$", cve_id):
return [
types.TextContent(
type="text",
text=f"Error: Invalid CVE ID format. Expected format: CVE-YYYY-NNNN (e.g., CVE-2021-44228). Got: {cve_id}",
)
]
headers = {
"User-Agent": "MCP Vulnerability Timeline Tool v1.0",
"Accept": "application/json",
}
timeline_data = {}
try:
timeout = httpx.Timeout(20.0, connect=10.0)
async with httpx.AsyncClient(
follow_redirects=True, headers=headers, timeout=timeout
) as client:
# Get comprehensive CVE data from NVD
nvd_url = f"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve_id}"
try:
nvd_response = await client.get(nvd_url)
if nvd_response.status_code == 200:
nvd_data = nvd_response.json()
if nvd_data.get("totalResults", 0) > 0:
cve_item = nvd_data["vulnerabilities"][0]["cve"]
# Extract key timeline dates
published_date = parse_date(cve_item.get("published", ""))
modified_date = parse_date(cve_item.get("lastModified", ""))
timeline_data["nvd_info"] = {
"published": published_date,
"last_modified": modified_date,
"descriptions": cve_item.get("descriptions", []),
"references": cve_item.get("references", []),
"cvss_scores": cve_item.get("metrics", {}),
}
# Analyze references for patch and vendor information
patch_refs = []
vendor_refs = []
advisory_refs = []
for ref in cve_item.get("references", []):
url = ref.get("url", "").lower()
tags = [tag.lower() for tag in ref.get("tags", [])]
# Categorize references
if any(tag in ["patch", "vendor advisory"] for tag in tags):
if "patch" in tags:
patch_refs.append(ref)
if "vendor advisory" in tags:
vendor_refs.append(ref)
elif any(
keyword in url
for keyword in [
"security",
"advisory",
"bulletin",
"patch",
"update",
]
):
advisory_refs.append(ref)
timeline_data["patches"] = patch_refs
timeline_data["vendor_advisories"] = vendor_refs
timeline_data["security_advisories"] = advisory_refs
except Exception as e:
timeline_data["nvd_error"] = str(e)
# Try to get additional timeline info from MITRE
try:
mitre_url = f"https://cve.mitre.org/cgi-bin/cvename.cgi?name={cve_id}"
mitre_response = await client.get(mitre_url)
if mitre_response.status_code == 200:
content = mitre_response.text
# Look for timeline indicators in MITRE page
timeline_indicators = []
if "reserved" in content.lower():
timeline_indicators.append("CVE ID was reserved")
if "disputed" in content.lower():
timeline_indicators.append("Vulnerability is disputed")
if "rejected" in content.lower():
timeline_indicators.append("CVE was rejected")
timeline_data["mitre_indicators"] = timeline_indicators
except Exception as e:
timeline_data["mitre_error"] = str(e)
except Exception as e:
return [
types.TextContent(
type="text",
text=f"Error: Failed to fetch vulnerability timeline: {str(e)}",
)
]
# Generate timeline report
if not timeline_data.get("nvd_info"):
return [
types.TextContent(
type="text",
text=f"No timeline information found for {cve_id}. The CVE might not exist or be publicly available yet.",
)
]
nvd_info = timeline_data["nvd_info"]
published_date = nvd_info["published"]
modified_date = nvd_info["last_modified"]
# Calculate timeline metrics
current_date = datetime.now()
age_days = 0
update_days = 0
if published_date:
age_days = (current_date - published_date).days
if modified_date and published_date:
update_days = (modified_date - published_date).days
# Format the response
result = f"โฐ **Vulnerability Timeline Report: {cve_id}**\n\n"
# Timeline Overview
result += "๐ **Timeline Overview:**\n"
if published_date:
result += f" โข **Published:** {published_date.strftime('%Y-%m-%d')} ({age_days} days ago)\n"
if modified_date:
result += f" โข **Last Modified:** {modified_date.strftime('%Y-%m-%d')} ({update_days} days after publication)\n"
result += f" โข **Age:** {age_days} days\n"
# Age-based risk assessment
if age_days > 365:
age_risk = "๐ข STABLE"
age_desc = "Well-established vulnerability with likely available patches"
elif age_days > 90:
age_risk = "๐ก MATURING"
age_desc = "Patches likely available, check vendor advisories"
elif age_days > 30:
age_risk = "๐ RECENT"
age_desc = "Recent vulnerability, patches may be in development"
else:
age_risk = "๐ด NEW"
age_desc = "Very recent vulnerability, patches may not be available yet"
result += f" โข **Maturity:** {age_risk} - {age_desc}\n\n"
# Patch Information
patches = timeline_data.get("patches", [])
if patches:
result += (
f"๐ง **Patch Information:** โ
{len(patches)} patch reference(s) found\n"
)
for i, patch in enumerate(patches[:3], 1): # Show first 3
result += f" {i}. {patch.get('url', 'N/A')}\n"
if patch.get("source"):
result += f" Source: {patch.get('source')}\n"
if len(patches) > 3:
result += f" ... and {len(patches) - 3} more patch references\n"
else:
result += (
"๐ง **Patch Information:** โ ๏ธ No explicit patch references found in NVD\n"
)
result += "\n"
# Vendor Advisories
vendor_advisories = timeline_data.get("vendor_advisories", [])
if vendor_advisories:
result += f"๐ญ **Vendor Advisories:** โ
{len(vendor_advisories)} advisory(ies) found\n"
for i, advisory in enumerate(vendor_advisories[:3], 1):
result += f" {i}. {advisory.get('url', 'N/A')}\n"
if advisory.get("source"):
result += f" Source: {advisory.get('source')}\n"
else:
result += (
"๐ญ **Vendor Advisories:** โช No vendor advisories in NVD references\n"
)
result += "\n"
# Security Advisories
security_advisories = timeline_data.get("security_advisories", [])
if security_advisories:
result += f"๐ข **Security Advisories:** โ
{len(security_advisories)} security-related reference(s)\n"
for i, advisory in enumerate(security_advisories[:3], 1):
result += f" {i}. {advisory.get('url', 'N/A')}\n"
else:
result += "๐ข **Security Advisories:** โช No security advisories detected\n"
result += "\n"
# MITRE Status Indicators
mitre_indicators = timeline_data.get("mitre_indicators", [])
if mitre_indicators:
result += "๐๏ธ **MITRE Status:** โ ๏ธ Special status detected\n"
for indicator in mitre_indicators:
result += f" โข {indicator}\n"
else:
result += "๐๏ธ **MITRE Status:** โ
Standard active CVE\n"
result += "\n"
# Remediation Timeline Estimate
result += "๐ ๏ธ **Remediation Timeline Guidance:**\n"
if patches:
result += " โข **Patch Status:** โ
Patches appear to be available\n"
result += " โข **Action:** Verify and apply vendor patches immediately\n"
result += " โข **Timeline:** Patch within 24-72 hours for critical systems\n"
elif vendor_advisories:
result += " โข **Patch Status:** ๐ก Vendor acknowledgment found\n"
result += " โข **Action:** Monitor vendor channels for patch release\n"
result += " โข **Timeline:** Patches typically released within 30-90 days\n"
elif age_days > 90:
result += " โข **Patch Status:** โ ๏ธ Patches may be available from vendors\n"
result += " โข **Action:** Check vendor security pages directly\n"
result += " โข **Timeline:** Manual verification required\n"
else:
result += " โข **Patch Status:** ๐ด Patches likely not available yet\n"
result += " โข **Action:** Implement workarounds and monitor for updates\n"
result += " โข **Timeline:** Monitor for next 30-60 days\n"
result += "\n๐ **Next Steps:**\n"
result += " 1. ๐ **Verify Impact:** Check if your systems are affected\n"
result += " 2. ๐ก๏ธ **Implement Workarounds:** If patches unavailable\n"
result += " 3. ๐ **Monitor Updates:** Set up alerts for patch releases\n"
result += " 4. ๐ฏ **Apply Patches:** Test and deploy when available\n"
result += " 5. ๐ **Validate Fix:** Confirm vulnerability is resolved\n\n"
result += "๐ **Useful Resources:**\n"
result += f" โข NVD CVE Page: https://nvd.nist.gov/vuln/detail/{cve_id}\n"
result += f" โข MITRE CVE Page: https://cve.mitre.org/cgi-bin/cvename.cgi?name={cve_id}\n"
result += " โข Vendor Security Pages: Check affected product vendors\n\n"
result += f"๐ **Report Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
return [types.TextContent(type="text", text=result)]