Created
September 9, 2025 09:41
-
-
Save mjervis-mo/a8bcddf5b94155ee1cdfc53b873a0408 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name CVE to Ubuntu Link Converter | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1 | |
| // @description Finds CVE numbers in brackets, like [CVE-2023-12345], and converts them to links to the Ubuntu CVE tracker. | |
| // @author Gemini | |
| // @match https://scanner.appcheck-ng.com/scans/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=appcheck-ng.com | |
| // @grant none | |
| // @run-at document-end | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Regex looks for CVEs inside parentheses, e.g., (CVE-2024-12345) | |
| const cveRegex = /\((CVE-\d{4}-\d{4,})\)/gi; | |
| const ubuntuUrlBase = 'https://ubuntu.com/security/'; | |
| /** | |
| * Traverses a given DOM node, finds text nodes containing a CVE pattern, | |
| * and replaces them with a styled, clickable link. | |
| * @param {Node} node - The DOM node to process. | |
| */ | |
| function linkifyCVEsInNode(node) { | |
| // Use a TreeWalker to efficiently find all text nodes within the given node. | |
| const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT); | |
| const nodesToProcess = []; | |
| // Collect all matching text nodes first to avoid modifying the DOM while iterating. | |
| let currentNode; | |
| while ((currentNode = walker.nextNode())) { | |
| // Ignore text inside scripts, styles, or existing links. | |
| if (currentNode.parentElement.tagName.match(/^(A|SCRIPT|STYLE)$/i)) { | |
| continue; | |
| } | |
| if (cveRegex.test(currentNode.nodeValue)) { | |
| nodesToProcess.push(currentNode); | |
| } | |
| } | |
| // Now, process each of the collected text nodes. | |
| nodesToProcess.forEach(textNode => { | |
| // Ensure the node is still attached to the DOM before processing. | |
| const parent = textNode.parentNode; | |
| if (!parent) return; | |
| const fragment = document.createDocumentFragment(); | |
| const text = textNode.nodeValue; | |
| let lastIndex = 0; | |
| let match; | |
| cveRegex.lastIndex = 0; // Reset regex state before exec loop. | |
| while ((match = cveRegex.exec(text)) !== null) { | |
| // Add the text that comes before the CVE match. | |
| fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index))); | |
| const cveId = match[1]; // Captured CVE ID, e.g., "CVE-2023-12345" | |
| const fullMatch = match[0]; // Full string match, e.g., "(CVE-2023-12345)" | |
| // Create the new <a> element. | |
| const link = document.createElement('a'); | |
| link.href = `${ubuntuUrlBase}${cveId}`; | |
| link.textContent = fullMatch; | |
| link.target = '_blank'; | |
| link.rel = 'noopener noreferrer'; | |
| link.style.color = '#E95420'; | |
| link.style.fontWeight = 'bold'; | |
| link.style.textDecoration = 'underline'; | |
| // Stop the click from triggering parent UI events. | |
| link.addEventListener('click', (e) => e.stopPropagation()); | |
| fragment.appendChild(link); | |
| lastIndex = cveRegex.lastIndex; | |
| } | |
| // Add any remaining text that came after the last CVE match. | |
| fragment.appendChild(document.createTextNode(text.substring(lastIndex))); | |
| // Replace the original text node with our new fragment. | |
| parent.replaceChild(fragment, textNode); | |
| }); | |
| } | |
| // --- Main execution --- | |
| // 1. Run an initial scan on the whole page for any content present at load time. | |
| linkifyCVEsInNode(document.body); | |
| // 2. Create a single, robust observer to watch for any nodes added to the page later. | |
| const observer = new MutationObserver((mutationsList) => { | |
| for (const mutation of mutationsList) { | |
| // We only care about mutations where nodes were added. | |
| if (mutation.type === 'childList') { | |
| mutation.addedNodes.forEach(node => { | |
| // Only process element nodes (their text nodes will be found by the walker). | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| linkifyCVEsInNode(node); | |
| } | |
| }); | |
| } | |
| } | |
| }); | |
| // 3. Start observing the entire document body for additions to its subtree. | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment