| 1 | from typing import Dict |
| 2 | from typing import List |
| 3 | from typing import Optional |
| 4 | |
| 5 | from pydantic import BaseModel |
| 6 | from pydantic import Field |
| 7 | |
| 8 | |
| 9 | # Request schemas |
| 10 | class PatchTuesdayRequest(BaseModel): |
| 11 | """Request to fetch Patch Tuesday data for a specific cycle""" |
| 12 | |
| 13 | cycle: Optional[str] = Field( |
| 14 | None, |
| 15 | description="CVRF doc id in format YYYY-Mmm (e.g., 2026-Jan). Defaults to current month.", |
| 16 | pattern=r"^\d{4}-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$", |
| 17 | ) |
| 18 | include_epss: bool = Field(True, description="Include EPSS scores in the response") |
| 19 | include_kev: bool = Field(True, description="Include CISA KEV data in the response") |
| 20 | |
| 21 | |
| 22 | class PatchTuesdayCVESearchRequest(BaseModel): |
| 23 | """Request to search for specific CVEs in Patch Tuesday data""" |
| 24 | |
| 25 | cve_ids: List[str] = Field(..., description="List of CVE IDs to search for") |
| 26 | cycle: Optional[str] = Field(None, description="Optional cycle to limit search to") |
| 27 | |
| 28 | |
| 29 | # Response component schemas |
| 30 | class CVSSInfo(BaseModel): |
| 31 | """CVSS score information""" |
| 32 | |
| 33 | base: Optional[float] = Field(None, description="CVSS base score") |
| 34 | vector: Optional[str] = Field(None, description="CVSS vector string") |
| 35 | |
| 36 | |
| 37 | class EPSSInfo(BaseModel): |
| 38 | """EPSS score information""" |
| 39 | |
| 40 | score: Optional[float] = Field(None, description="EPSS probability score") |
| 41 | percentile: Optional[float] = Field(None, description="EPSS percentile ranking") |
| 42 | date: Optional[str] = Field(None, description="Date of EPSS score") |
| 43 | |
| 44 | |
| 45 | class KEVInfo(BaseModel): |
| 46 | """CISA Known Exploited Vulnerabilities information""" |
| 47 | |
| 48 | in_kev: bool = Field(False, description="Whether the CVE is in CISA KEV") |
| 49 | date_added: Optional[str] = Field(None, description="Date added to KEV") |
| 50 | due_date: Optional[str] = Field(None, description="Required remediation due date") |
| 51 | required_action: Optional[str] = Field(None, description="Required remediation action") |
| 52 | known_ransomware_campaign_use: Optional[str] = Field(None, description="Known ransomware usage") |
| 53 | vendor_project: Optional[str] = Field(None, description="Vendor or project name") |
| 54 | product: Optional[str] = Field(None, description="Product name") |
| 55 | vulnerability_name: Optional[str] = Field(None, description="Vulnerability name") |
| 56 | short_description: Optional[str] = Field(None, description="Short description") |
| 57 | notes: Optional[str] = Field(None, description="Additional notes") |
| 58 | |
| 59 | |
| 60 | class AffectedProduct(BaseModel): |
| 61 | """Affected product information""" |
| 62 | |
| 63 | product: str = Field(..., description="Product name") |
| 64 | family: str = Field(..., description="Product family classification") |
| 65 | component_hint: Optional[str] = Field(None, description="Component hint from title") |
| 66 | |
| 67 | |
| 68 | class RemediationInfo(BaseModel): |
| 69 | """Remediation information""" |
| 70 | |
| 71 | kbs: List[str] = Field(default_factory=list, description="Related KB article numbers") |
| 72 | |
| 73 | |
| 74 | class PrioritizationInfo(BaseModel): |
| 75 | """Prioritization recommendation""" |
| 76 | |
| 77 | priority: str = Field(..., description="Priority level (P0-P3)") |
| 78 | reason: List[str] = Field(default_factory=list, description="Reasons for priority assignment") |
| 79 | suggested_sla: str = Field(..., description="Suggested SLA for remediation") |
| 80 | |
| 81 | |
| 82 | class SourceInfo(BaseModel): |
| 83 | """Source information for the vulnerability data""" |
| 84 | |
| 85 | msrc_cvrf_id: str = Field(..., description="MSRC CVRF document ID") |
| 86 | msrc_cvrf_url: str = Field(..., description="MSRC CVRF document URL") |
| 87 | cisa_kev_url: str = Field(..., description="CISA KEV feed URL") |
| 88 | |
| 89 | |
| 90 | class PatchTuesdayItem(BaseModel): |
| 91 | """Individual Patch Tuesday vulnerability item""" |
| 92 | |
| 93 | cycle: str = Field(..., description="Patch Tuesday cycle (e.g., 2026-Jan)") |
| 94 | release_type: str = Field("patch_tuesday", description="Release type") |
| 95 | cve: str = Field(..., description="CVE identifier") |
| 96 | title: Optional[str] = Field(None, description="Vulnerability title") |
| 97 | severity: Optional[str] = Field(None, description="Microsoft severity rating") |
| 98 | cvss: CVSSInfo = Field(default_factory=CVSSInfo, description="CVSS information") |
| 99 | epss: EPSSInfo = Field(default_factory=EPSSInfo, description="EPSS information") |
| 100 | kev: KEVInfo = Field(default_factory=KEVInfo, description="KEV information") |
| 101 | affected: AffectedProduct = Field(..., description="Affected product information") |
| 102 | remediation: RemediationInfo = Field(default_factory=RemediationInfo, description="Remediation information") |
| 103 | prioritization: PrioritizationInfo = Field(..., description="Prioritization recommendation") |
| 104 | source: SourceInfo = Field(..., description="Data source information") |
| 105 | timestamp_utc: str = Field(..., description="Timestamp when data was fetched") |
| 106 | |
| 107 | |
| 108 | class PriorityCounts(BaseModel): |
| 109 | """Counts by priority level""" |
| 110 | |
| 111 | P0: int = Field(0, description="Emergency priority count") |
| 112 | P1: int = Field(0, description="High priority count") |
| 113 | P2: int = Field(0, description="Medium priority count") |
| 114 | P3: int = Field(0, description="Low priority count") |
| 115 | |
| 116 | |
| 117 | class PatchTuesdaySummary(BaseModel): |
| 118 | """Summary of Patch Tuesday data""" |
| 119 | |
| 120 | cycle: str = Field(..., description="Patch Tuesday cycle") |
| 121 | patch_tuesday_date: str = Field(..., description="Date of Patch Tuesday") |
| 122 | generated_utc: str = Field(..., description="When the summary was generated") |
| 123 | unique_cves: int = Field(..., description="Number of unique CVEs") |
| 124 | total_records: int = Field(..., description="Total CVE x product records") |
| 125 | by_priority: PriorityCounts = Field(..., description="Counts by priority level") |
| 126 | by_family: Dict[str, int] = Field(default_factory=dict, description="Counts by product family") |
| 127 | by_severity: Dict[str, int] = Field(default_factory=dict, description="Counts by severity") |
| 128 | |
| 129 | |
| 130 | class PatchTuesdayResponse(BaseModel): |
| 131 | """Full Patch Tuesday response""" |
| 132 | |
| 133 | success: bool = Field(..., description="Whether the request was successful") |
| 134 | message: str = Field(..., description="Response message") |
| 135 | summary: Optional[PatchTuesdaySummary] = Field(None, description="Summary of the data") |
| 136 | items: List[PatchTuesdayItem] = Field(default_factory=list, description="Vulnerability items") |
| 137 | |
| 138 | |
| 139 | class PatchTuesdaySummaryResponse(BaseModel): |
| 140 | """Summary-only Patch Tuesday response (without full items)""" |
| 141 | |
| 142 | success: bool = Field(..., description="Whether the request was successful") |
| 143 | message: str = Field(..., description="Response message") |
| 144 | summary: Optional[PatchTuesdaySummary] = Field(None, description="Summary of the data") |
| 145 | top_items: List[PatchTuesdayItem] = Field(default_factory=list, description="Top prioritized items") |
| 146 | |
| 147 | |
| 148 | class AvailableCyclesResponse(BaseModel): |
| 149 | """Response with available Patch Tuesday cycles""" |
| 150 | |
| 151 | success: bool = Field(..., description="Whether the request was successful") |
| 152 | message: str = Field(..., description="Response message") |
| 153 | cycles: List[str] = Field(default_factory=list, description="Available cycles") |
| 154 | current_cycle: str = Field(..., description="Current/default cycle") |
| 155 | next_patch_tuesday: str = Field(..., description="Date of next Patch Tuesday") |