Skip to content

Commit 5e98bda

Browse files
committed
all
1 parent 786bf64 commit 5e98bda

5 files changed

Lines changed: 186863 additions & 1 deletion

File tree

‎.github/workflows/fetch-msrc.yml‎

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
name: Fetch MSRC CVE Data
2+
3+
on:
4+
schedule:
5+
- cron: '0 * * * *' # Hourly (at minute 0)
6+
workflow_dispatch: # Manual trigger from GitHub UI
7+
8+
jobs:
9+
fetch:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Fetch and Parse 12 Months of MSRC Data
19+
run: |
20+
mkdir -p data
21+
python3 << 'EOF'
22+
import json
23+
import time
24+
import urllib.request
25+
import urllib.error
26+
from datetime import datetime, date, timezone
27+
28+
def fetch_with_retry(url, retries=3, delay=2):
29+
req = urllib.request.Request(url, headers={'Accept': 'application/json'})
30+
for attempt in range(retries):
31+
try:
32+
with urllib.request.urlopen(req, timeout=15) as resp:
33+
return json.loads(resp.read().decode('utf-8'))
34+
except urllib.error.HTTPError as e:
35+
if e.code == 404:
36+
raise e
37+
print(f"-> HTTP Error {e.code} for {url}. Attempt {attempt + 1}/{retries}...")
38+
except Exception as e:
39+
print(f"-> Connection error: {e} for {url}. Attempt {attempt + 1}/{retries}...")
40+
41+
if attempt < retries - 1:
42+
wait_time = delay * (attempt + 1)
43+
print(f"Waiting {wait_time}s before retry...")
44+
time.sleep(wait_time)
45+
46+
raise Exception(f"Failed to fetch {url} after {retries} attempts")
47+
48+
# Generate last 12 months (oldest first, so newest overwrites in case of revisions)
49+
today = date.today()
50+
month_ids = []
51+
for i in range(11, -1, -1):
52+
m = today.month - i
53+
y = today.year
54+
while m <= 0:
55+
m += 12
56+
y -= 1
57+
month_name = date(y, m, 1).strftime('%b')
58+
month_ids.append(f"{y}-{month_name}")
59+
60+
print("Generating data for months:", month_ids)
61+
62+
cves_map = {}
63+
64+
for month_id in month_ids:
65+
url = f"https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/{month_id}"
66+
print(f"Fetching: {url} ...")
67+
68+
try:
69+
raw = fetch_with_retry(url)
70+
except urllib.error.HTTPError as e:
71+
if e.code == 404:
72+
print(f"-> Month {month_id} not published yet (404). Skipping.")
73+
continue
74+
except Exception as e:
75+
print(f"-> Error fetching {month_id}: {e}. Skipping.")
76+
continue
77+
78+
product_map = {}
79+
def walk(branch):
80+
if not branch:
81+
return
82+
for p in branch.get('FullProductName', []):
83+
product_map[p['ProductID']] = p['Value']
84+
for b in branch.get('Branch', []):
85+
walk(b)
86+
87+
walk(raw.get('ProductTree', {}))
88+
89+
vulns = raw.get('Vulnerability', [])
90+
for v in vulns:
91+
cve_id = v.get('CVE', '')
92+
if not cve_id:
93+
continue
94+
95+
title = ''
96+
if v.get('Title') and v['Title'].get('Value'):
97+
title = v['Title']['Value']
98+
99+
severity = ''
100+
for t in v.get('Threats', []):
101+
if t.get('Type') == 3:
102+
severity = (t.get('Description') or {}).get('Value', '')
103+
if severity:
104+
break
105+
if not severity:
106+
for t in v.get('Threats', []):
107+
if t.get('Type') == 0:
108+
severity = (t.get('Description') or {}).get('Value', '')
109+
if severity:
110+
break
111+
112+
cvss = None
113+
for s in v.get('CVSSScoreSets', []):
114+
if s.get('BaseScore') and ('3.1' in s.get('Vector','') or '3.0' in s.get('Vector','')):
115+
cvss = float(s['BaseScore'])
116+
break
117+
if cvss is None:
118+
for s in v.get('CVSSScoreSets', []):
119+
if s.get('BaseScore'):
120+
cvss = float(s['BaseScore'])
121+
break
122+
123+
products = []
124+
for ps in v.get('ProductStatuses', []):
125+
for pid in ps.get('ProductID', []):
126+
pname = product_map.get(pid, '')
127+
if pname and pname not in products:
128+
products.append(pname)
129+
130+
product = products[0] if products else ''
131+
132+
date_str = ''
133+
revs = v.get('RevisionHistory', [])
134+
if revs:
135+
date_str = revs[0].get('Date', '')[:10]
136+
137+
exploited = False
138+
for t in v.get('Threats', []):
139+
if t.get('Type') == 1:
140+
desc = (t.get('Description') or {}).get('Value', '')
141+
if 'exploited:yes' in desc.lower().replace(' ', ''):
142+
exploited = True
143+
break
144+
145+
kb_articles = []
146+
seen_kb = set()
147+
for r in v.get('Remediations', []):
148+
kb_num = (r.get('Description') or {}).get('Value', '').strip()
149+
if kb_num.upper().startswith('KB'):
150+
kb_num = kb_num[2:]
151+
url_str = r.get('URL', '').strip()
152+
fixed = r.get('FixedBuild', '').strip()
153+
subtype = r.get('SubType', '').strip()
154+
pids = r.get('ProductID', [])
155+
if not kb_num or not url_str or 'catalog' not in url_str.lower():
156+
continue
157+
if kb_num in seen_kb:
158+
for existing in kb_articles:
159+
if existing['kb'] == kb_num:
160+
for pid in pids:
161+
pname = product_map.get(pid, '')
162+
if pname and pname not in existing['products']:
163+
existing['products'].append(pname)
164+
continue
165+
seen_kb.add(kb_num)
166+
kb_products = [product_map.get(pid,'') for pid in pids if product_map.get(pid,'')]
167+
kb_articles.append({
168+
'kb': kb_num,
169+
'url': url_str,
170+
'fixed_build': fixed,
171+
'subtype': subtype,
172+
'products': kb_products[:8],
173+
})
174+
kb_articles.sort(key=lambda x: x['kb'])
175+
176+
cves_map[cve_id] = {
177+
'cve_id': cve_id,
178+
'title': title,
179+
'severity': severity,
180+
'cvss': cvss,
181+
'product': product,
182+
'products': products[:5],
183+
'date': date_str,
184+
'exploited': exploited,
185+
'kb_articles': kb_articles,
186+
'kb_count': len(kb_articles),
187+
}
188+
189+
parsed = list(cves_map.values())
190+
191+
sev_order = {'Critical':0, 'Important':1, 'Moderate':2, 'Low':3}
192+
parsed.sort(key=lambda x: (
193+
0 if x['exploited'] else 1,
194+
sev_order.get(x['severity'], 4),
195+
-(x['cvss'] or 0),
196+
x['cve_id']
197+
))
198+
199+
now = datetime.now(timezone.utc)
200+
counts = {
201+
'total': len(parsed),
202+
'critical': sum(1 for x in parsed if x['severity'] == 'Critical'),
203+
'important': sum(1 for x in parsed if x['severity'] == 'Important'),
204+
'moderate': sum(1 for x in parsed if x['severity'] == 'Moderate'),
205+
'low': sum(1 for x in parsed if x['severity'] == 'Low'),
206+
'exploited': sum(1 for x in parsed if x['exploited']),
207+
'patched': sum(1 for x in parsed if x['kb_count'] > 0),
208+
'updated_at': now.strftime('%Y-%m-%d %H:%M UTC'),
209+
'month': "Past 12 Months"
210+
}
211+
212+
output = {'meta': counts, 'cves': parsed}
213+
214+
with open('data/msrc_cves.json', 'w', encoding='utf-8') as f:
215+
json.dump(output, f, ensure_ascii=False, indent=2)
216+
217+
print(f"Success: {counts['total']} CVEs consolidated. Critical: {counts['critical']} | Exploited: {counts['exploited']} | Patched: {counts['patched']}")
218+
EOF
219+
220+
- name: Commit updated data
221+
run: |
222+
git config user.name "github-actions[bot]"
223+
git config user.email "github-actions[bot]@users.noreply.github.com"
224+
git add data/msrc_cves.json
225+
git diff --staged --quiet || git commit -m "chore: update rolling 12-month MSRC CVE data [$(date -u +'%Y-%m-%d %H:%M UTC')]"
226+
git push

‎README.md‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Windows Security Tracker
2+
3+
A live CVE tracking dashboard powered by the **Microsoft Security Response Center (MSRC) API**, built as a standalone HTML page with automated daily updates via GitHub Actions.
4+
5+
![Preview](https://img.shields.io/badge/CVEs-June%202026-blue) ![Auto-update](https://img.shields.io/badge/Auto--update-hourly-green) ![License](https://img.shields.io/badge/license-MIT-lightgrey)
6+
7+
---
8+
9+
## What It Does
10+
11+
- Fetches all CVE vulnerabilities from MSRC for the **past 12 months** — automatically, every hour.
12+
- Displays severity levels (Critical / Important / Moderate / Low), CVSS scores, and exploit status.
13+
- Shows **which KB article patches each vulnerability**, with direct links to the Microsoft Update Catalog.
14+
- Fully searchable and filterable — by severity, product, exploit status, or KB availability.
15+
16+
## Live Features
17+
18+
| Feature | Details |
19+
|---|---|
20+
| **Auto day/night theme** | Switches automatically (07:00–21:00 Light, 21:00–07:00 Dark) |
21+
| **Click a row** | Expands KB patch panel — shows KB numbers, fixed builds, affected products |
22+
| **"Has KB Patch" filter** | Instantly narrows to only patched CVEs |
23+
| **KB search** | Type `KB5094125` in the search box to find which CVE it fixes |
24+
| **Sorting** | Click any column header to sort |
25+
| **Pagination** | 50 CVEs per page |
26+
27+
---
28+
29+
## Project Structure
30+
31+
```
32+
.github/
33+
workflows/
34+
fetch-msrc.yml # GitHub Actions: fetches + parses MSRC API hourly
35+
data/
36+
msrc_cves.json # Parsed CVE data (auto-generated, do not edit)
37+
index.html # The standalone dashboard page
38+
README.md # Project documentation
39+
```
40+
41+
---
42+
43+
## Setup & Deployment (GitHub Pages)
44+
45+
### 1. Push files to your repository
46+
47+
Create a new GitHub repository (e.g., `github.com/faruk-guler/winsecurity`) and push the following files/directories:
48+
49+
- `.github/workflows/fetch-msrc.yml`
50+
- `data/msrc_cves.json` (initial seed data)
51+
- `index.html`
52+
- `README.md`
53+
54+
### 2. Enable GitHub Actions write permissions
55+
56+
Go to your repo → **Settings → Actions → General → Workflow permissions**
57+
→ Select **"Read and write permissions"** and check **"Allow GitHub Actions to create and approve pull requests"** → Save.
58+
59+
### 3. Enable GitHub Actions
60+
61+
Go to the **Actions** tab of your repository, select **Fetch MSRC CVE Data**, and click **Run workflow** to trigger the first fetch manually.
62+
63+
### 4. Enable GitHub Pages
64+
65+
Go to your repo → **Settings → Pages**
66+
→ Under **Build and deployment**, set **Source** to **Deploy from a branch**
67+
→ Choose your branch (e.g., `main` or `master`) and folder (`/ (root)`) → Save.
68+
69+
Your dashboard will be live at:
70+
71+
```
72+
https://faruk-guler.github.io/winsecurity/
73+
```
74+
75+
---
76+
77+
## Auto-Update Schedule
78+
79+
The GitHub Actions workflow runs **every hour**.
80+
Each month it automatically picks up the new Patch Tuesday release from Microsoft and consolidates it with the previous 11 months of data.
81+
82+
---
83+
84+
## Tech Stack
85+
86+
- **Data source**: [MSRC CVRF API v3.0](https://api.msrc.microsoft.com/cvrf/v3.0/)
87+
- **Automation**: GitHub Actions
88+
- **Frontend**: Vanilla HTML/CSS/JS (no frameworks)
89+
- **Hosting**: GitHub Pages (static site hosting)
90+
91+
---
92+
93+
## License
94+
95+
Faruk GULER /Sysadmin - free to use and modify.

0 commit comments

Comments
 (0)