From 066486aec19f1490a7ec20855e82270f88f56884 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 18:37:03 +0000 Subject: [PATCH] feat: add workflow and script to auto-update README when CVE files change Agent-Logs-Url: https://github.com/Stalin-143/CVE/sessions/23d694ad-2e83-44ad-b08e-57dd48698eac Co-authored-by: Stalin-143 <161853795+Stalin-143@users.noreply.github.com> --- .github/workflows/update-readme.yml | 41 ++++++++++ README.md | 12 +-- scripts/update_readme.py | 111 ++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/update-readme.yml create mode 100644 scripts/update_readme.py diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml new file mode 100644 index 0000000..e9a6c9e --- /dev/null +++ b/.github/workflows/update-readme.yml @@ -0,0 +1,41 @@ +name: Auto-update README on CVE changes + +on: + push: + branches: + - main + paths: + - "reported/**" + - "patches/**" + +jobs: + update-readme: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Update README.md + run: python scripts/update_readme.py + + - name: Commit and push if README changed + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + if git diff --cached --quiet; then + echo "No changes to README.md — skipping commit." + else + git commit -m "docs: auto-update README CVE tables" + git push + fi diff --git a/README.md b/README.md index 7bc4b0b..506780e 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ CVEs that were discovered and reported by Stalin S. | CVE | Description | Severity | |-----|-------------|----------| -| [CVE-2026-29905](reported/CVE-2026-29905.md) | Kirby CMS — Persistent DoS via Malformed Image Upload | Medium | -| [CVE-2026-41037](reported/CVE-2026-41037.md) | Quantum Networks Router — Missing Rate Limiting | High | -| [CVE-2026-41039](reported/CVE-2026-41039.md) | Quantum Networks Router — Information Disclosure | High | +| [CVE-2026-29905](reported/CVE-2026-29905.md) | Kirby CMS Persistent DoS via Malformed Image Upload | Medium | +| [CVE-2026-41037](reported/CVE-2026-41037.md) | Missing Rate Limiting (Quantum Networks Router) | High (8.7) | +| [CVE-2026-41039](reported/CVE-2026-41039.md) | Information Disclosure (Quantum Networks Router) | High (8.7) | --- @@ -25,6 +25,6 @@ CVEs where Stalin S fixed the security issue. | CVE | Description | Severity | |-----|-------------|----------| -| [CVE-2026-32138](patches/CVE-2026-32138.md) | Nexulean Website — API Key Exposure | High | -| [CVE-2026-41575](patches/CVE-2026-41575.md) | IP Reputation Checker — DOM-Based XSS | Moderate | -| [CVE-2026-41900](patches/CVE-2026-41900.md) | OpenLearnX — RCE via Sandbox Escape | High | +| [CVE-2026-32138](patches/CVE-2026-32138.md) | API Key Exposure (Nexulean Website) | High | +| [CVE-2026-41575](patches/CVE-2026-41575.md) | DOM-Based XSS (IP Reputation Checker) | Moderate | +| [CVE-2026-41900](patches/CVE-2026-41900.md) | RCE via Sandbox Escape (OpenLearnX) | High | diff --git a/scripts/update_readme.py b/scripts/update_readme.py new file mode 100644 index 0000000..06beb45 --- /dev/null +++ b/scripts/update_readme.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Scans the reported/ and patches/ directories for CVE markdown files, +extracts CVE ID, description, and severity, then rewrites the +Reported and Patched tables in README.md automatically. +""" + +import os +import re +import sys + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +README_PATH = os.path.join(REPO_ROOT, "README.md") +REPORTED_DIR = os.path.join(REPO_ROOT, "reported") +PATCHES_DIR = os.path.join(REPO_ROOT, "patches") + + +def extract_info(filepath: str) -> dict: + """Return {cve_id, description, severity} parsed from a CVE markdown file.""" + with open(filepath, encoding="utf-8") as f: + content = f.read() + + # CVE ID from filename (most reliable source) + filename = os.path.basename(filepath) + cve_id = re.sub(r"\.md$", "", filename, flags=re.IGNORECASE) + + # Description: grab from the first level-1 heading, strip the CVE prefix + description = "" + heading_match = re.search(r"^#\s+(?:CVE-[\d-]+\s*[—–-]+\s*)?(.*)", content, re.MULTILINE) + if heading_match: + description = heading_match.group(1).strip() + + # Severity: look for bold label **Severity:** value (same line or next word) + severity = "N/A" + sev_match = re.search( + r"\*\*Severity:\*\*\s*([^\n|]+)", content, re.IGNORECASE + ) + if sev_match: + severity = sev_match.group(1).strip().rstrip("\\").strip() + + return {"cve_id": cve_id, "description": description, "severity": severity} + + +def build_table(entries: list, folder: str) -> str: + """Build a markdown table string from a list of entry dicts.""" + lines = [ + "| CVE | Description | Severity |", + "|-----|-------------|----------|", + ] + for e in entries: + cve_link = f"[{e['cve_id']}]({folder}/{e['cve_id']}.md)" + lines.append(f"| {cve_link} | {e['description']} | {e['severity']} |") + # trailing newline preserves the blank line before the next `---` separator + return "\n".join(lines) + "\n" + + +def collect_entries(directory: str) -> list: + """Return sorted list of entry dicts for all CVE .md files in a directory.""" + entries = [] + if not os.path.isdir(directory): + return entries + for fname in sorted(os.listdir(directory)): + if re.match(r"CVE-\d+-\d+\.md$", fname, re.IGNORECASE): + fpath = os.path.join(directory, fname) + entries.append(extract_info(fpath)) + return entries + + +def update_readme(reported_entries: list, patched_entries: list) -> None: + """Replace the Reported and Patched tables in README.md.""" + with open(README_PATH, encoding="utf-8") as f: + readme = f.read() + + reported_table = build_table(reported_entries, "reported") + patched_table = build_table(patched_entries, "patches") + + # Pattern: from the table header row up to (but not including) the next heading or end + table_pattern = r"(\| CVE \| Description \| Severity \|.*?)(?=\n---|\n##|\Z)" + + tables_found = re.findall(table_pattern, readme, flags=re.DOTALL) + if len(tables_found) < 2: + print("ERROR: Could not locate both tables in README.md", file=sys.stderr) + sys.exit(1) + + # Replace first table occurrence (Reported), then second (Patched) + new_readme = readme + # Use a counter-based replacement to replace occurrences independently + count = [0] + + def replacer(m): + count[0] += 1 + if count[0] == 1: + return reported_table + return patched_table + + new_readme = re.sub(table_pattern, replacer, readme, flags=re.DOTALL) + + with open(README_PATH, "w", encoding="utf-8") as f: + f.write(new_readme) + + print(f"README.md updated: {len(reported_entries)} reported, {len(patched_entries)} patched.") + + +def main(): + reported = collect_entries(REPORTED_DIR) + patched = collect_entries(PATCHES_DIR) + update_readme(reported, patched) + + +if __name__ == "__main__": + main()