CVE-2025-66024
Modified Modified - Updated After Analysis

Stored XSS in XWiki Blog Post Title Enables Session Hijacking

Vulnerability report for CVE-2025-66024, including description, CVSS score, EPSS score, affected products, exploitability, helpful resources, and attack-flow context.

Publication date: 2026-03-04

Last updated on: 2026-08-05

Assigner: GitHub, Inc.

Description

The XWiki blog application allows users of the XWiki platform to create and manage blog posts. Versions starting with 9.15 and prior to 9.15.7 are vulnerable to Stored Cross-Site Scripting (XSS) via the Blog Post Title. The vulnerability arises because the post title is injected directly into the HTML tag without proper escaping. An attacker with permissions to create or edit blog posts can inject malicious JavaScript into the title field. This script will execute in the browser of any user (including administrators) who views the blog post. This leads to potential session hijacking or privilege escalation. The vulnerability has been patched in the blog application version 9.15.7 by adding missing escaping. No known workarounds are available. </div> </div> </details> </div> </div> <div id="left-row-1" class="row"> <!--CVSS SCORES--> <div id="left-2" class=" col-12 col-md-12 col-lg-7 min-vh-60"> <div class="card m-0 h-100"> <div class="card-header d-flex pb-0 pt-0 bg-transparent"> <div class="d-flex align-items-end"> <h2 class="bf-section-title">CVSS Scores</h2> </div> </div> <div class="bento-body card my-0 mx-0 h-100 bento-text"> <div id="cvss-container" class="mt-0"> <div id="button-container" class="mt-0 mb-2 mx-0"></div> <script> document.addEventListener("DOMContentLoaded", function() { var isFirst = true; var versionCount = 0; // To track the number of CVSS versions // First pass to count the number of CVSS versions versionCount++; versionCount++; // Second pass to create buttons or label var name = "cvss_4.0" + "_base_score"; if (versionCount === 1) { // Create a label instead of a button if there is only one version addLabelAndDiv( "4.0", "cfc00046-10e5-4b9f-a8ec-796338732314", "8.6", "HIGH", "AV:N/AC:L/AT:N/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", "", "" ); } else { // Create a button for multiple versions addButtonAndDiv( "4.0", "cfc00046-10e5-4b9f-a8ec-796338732314", "8.6", "HIGH", "AV:N/AC:L/AT:N/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", "", "", isFirst ); } isFirst = false; var name = "cvss_3.1" + "_base_score"; if (versionCount === 1) { // Create a label instead of a button if there is only one version addLabelAndDiv( "3.1", "cfc00046-10e5-4b9f-a8ec-796338732314", "9.0", "CRITICAL", "AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", "0.0", "0.0" ); } else { // Create a button for multiple versions addButtonAndDiv( "3.1", "cfc00046-10e5-4b9f-a8ec-796338732314", "9.0", "CRITICAL", "AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H", "0.0", "0.0", isFirst ); } isFirst = false; if (isFirst == true) { var cvssContainer = document.getElementById("cvss-container"); cvssContainer.innerHTML = "<div class='no-data-container bento-text'>Currently, no data is known</div>"; cvssContainer.style.display = 'flex'; cvssContainer.style.alignItems = "flex-start"; cvssContainer.style.justifyContent = 'center'; cvssContainer.style.height = '100%'; } }); </script> </div> </div> </div> </div> <!-- AFFECTED VENDORS & PRODUCTS + EPSS box--> <div id="left-3" class="col-12 col-md-12 col-lg-5 d-flex flex-column h-100"> <div class="row m-0"> <div class="card h-100 m-0 p-0"> <div class="card-header d-flex px-3 pb-0 pt-0 bg-transparent"> <div class="d-flex align-items-center"> <h2 class="bf-section-title">EPSS Scores</h2> </div> </div> <div class="bento-body card my-0 mx-0 h-100 bento-text"> <div class="mt-0"> <table style="width: 100%;"> <tr> <!-- Probability Header + Value --> <td class="info-header d-flex align-items-center" style="border: none; width: 80%;"> <span data-toggle="tooltip" data-placement="top" title="The probability of exploitation in the last 30 days." style="cursor: help; margin-right: 6px;"> <span class="iconify m-0 p-0" data-icon="mynaui:info-square" style="color: #386641;"> </span> </span> <span>Probability:</span> </td> <td class="epss_box text-center" style="border: none;"> <span id="epss_score" class="epssbox"></span> </td> </tr> <tr> <!-- Percentile Header + Value --> <td class="info-header d-flex align-items-center" style="border: none; width: 80%;"> <span data-toggle="tooltip" data-placement="top" title="The percentage of vulnerabilities with the same score, or smaller." style="cursor: help; margin-right: 6px;"> <span class="iconify m-0 p-0" data-icon="mynaui:info-square" style="color: #386641;"> </span> </span> <span>Percentile:</span> </td> <td class="epss_box text-center" style="border: none;"> <span id="epss_percentile" class="epssbox"></span> </td> </tr> </table> </div> </div> </div> </div> <div id="meta-summary-widget" class="row m-0 d-flex flex-column"> <div class="card h-100 m-0 p-0"> <div class="card-header d-flex flex-column px-3 pb-0 pt-0 bg-transparent"> <div class="d-flex align-items-end"> <h2 class="bf-section-title">Meta Information</h2> </div> </div> <div id="meta-summary-bento-body" class="bento-body card my-0 mx-0 h-100 bento-text"> <div class="mt-0" id="meta-summary-inner-bento"> <div class="meta-summary-grid"> <div class="meta-summary-label">Published</div> <div class="meta-summary-value">2026-03-04</div> <div class="meta-summary-label">Last Modified</div> <div class="meta-summary-value">2026-08-05</div> <div class="meta-summary-label">Generated</div> <div class="meta-summary-value">2026-08-15</div> <div class="meta-summary-label">AI Q&A</div> <div class="meta-summary-value">2026-03-05</div> <div class="meta-summary-label">EPSS Evaluated</div> <div class="meta-summary-value">2026-08-14</div> <div class="meta-summary-label">NVD</div> <div class="meta-summary-value"> <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-66024" class="meta-summary-link" data-toggle="tooltip" target="_blank" data-placement="top" title="Link to NVD report for: CVE-2025-66024"> <span class="iconify" data-icon="ph:link-bold"></span> <span>CVE-2025-66024</span> </a> </div> <div class="meta-summary-label">EUVD</div> <div class="meta-summary-value"> <a href="https://euvd.enisa.europa.eu/enisa/EUVD-2025-208293" class="meta-summary-link" data-toggle="tooltip" target="_blank" data-placement="top" title="Link to EUVD report for: EUVD-2025-208293"> <span class="iconify" data-icon="ph:link-bold"></span> <span>EUVD-2025-208293</span> </a> </div> </div> </div> </div> </div> </div> </div> </div> <!-- EXPANDED AFFECTED VENDORS & PRODUCTS --> <div id="left-cpes-expanded" class="col-12 col-md-12"> <div id="affected-vendors-expanded-card" class="card m-0"> <div class="card-header d-flex flex-column px-3 pb-0 pt-0 bg-transparent"> <div class="d-flex align-items-center justify-content-between flex-wrap" style="gap: 12px;"> <h2 class="bf-section-title">Affected Vendors & Products</h2> </div> </div> <div id="affected-vendors-expanded-body" class="bento-body card my-0 mx-0 h-100 bento-text"> <div id="affected-vendors-expanded-inner" class="mt-0"> <div class="affected-vendors-toolbar"> <div class="affected-vendors-summary"> Showing <strong id="cpe-count">1</strong> associated CPE </div> <div class="affected-vendors-search"> <input type="text" id="cpe-search-input" placeholder="Search vendor, product, version..." class="cpe-search-input" /> </div> </div> <div class="affected-vendors-scroll"> <table id="table-affected-vendors-products-expanded" class="bento-text"> <colgroup> <col class="vendor-col"> <col class="product-col"> <col class="version-col"> </colgroup> <thead> <tr> <th>Vendor</th> <th>Product</th> <th>Version / Range</th> </tr> </thead> <tbody> <tr data-vendor="xwiki" data-product="blog_application" data-version="to 9.15.7 (exc)" > <td>xwiki</td> <td>blog_application</td> <td>to 9.15.7 (exc)</td> </tr> </tbody> </table> </div> </div> </div> </div> </div> <div id="left-row-2" class="row"> <!-- HELPFUL RESOURCES --> <div id="left-5" class="col-12 col-md-12 col-lg-5"> <div class="card h-100 m-0"> <div class="card-header d-flex flex-column px-3 pb-0 pt-0 bg-transparent"> <div class="d-flex flex-row align-items-center" style="gap: 12px;"> <h2 class="bf-section-title">Helpful Resources <span class="iconify" data-icon="mynaui:info-square" data-toggle="tooltip" data-placement="top" style="margin:auto; color:#386641;" title="Content from these websites was used for reference purposes. Please refer to the source for the latest information."></span> </h2> </div> </div> <div class="bento-body card my-0 mx-0 h-100 bento-text bento-dimensions"> <div id="resource-container" class="mt-0"> <script> var links = "[{\"url\": \"https://github.com/xwiki-contrib/application-blog/commit/cca87f0a0edc2e7e049d46d51f4a4d8f78b714ba\", \"resource\": []}, {\"url\": \"https://github.com/xwiki-contrib/application-blog/security/advisories/GHSA-h2xq-h7f9-vh6c\", \"resource\": []}, {\"url\": \"https://jira.xwiki.org/browse/BLOG-245\", \"resource\": []}, {\"url\": \"https://github.com/xwiki-contrib/application-blog/commit/cdcbf2816e9b329e8f006be4391a7ffe80d4bb3f\", \"resource\": []}]"; var validJsonString = links.replace(/'/g, '"'); var postLinks = JSON.parse(validJsonString); var count = 0; postLinks.forEach(function(link) { count = count + 1; let resourceNames = link.resource.length ? link.resource.join(", ") : "Resource"; resourceNames = resourceNames + " [" + count + "]"; // Helper function to truncate text to a maximum length function truncateText(text, maxLength) { // Remove "http://", "https://" and "www." from the start of the URL text = text.replace(/^(?:https?:\/\/)?(?:www\.)?/, ''); // If text is longer than maxLength, truncate it; otherwise, leave it as is if (text.length > maxLength) { text = text.substring(0, maxLength); } // Always append the dash and spaces return text + ' - '; } // Inside your resource container rendering code let linkElement = document.createElement('span'); linkElement.classList.add('my-3'); // Truncate the URL to 25 characters let truncatedUrl = truncateText(link.url, 25); linkElement.innerHTML = ` <a href="${link.url}" target="_blank" data-toggle="tooltip" data-placement="top" title="${link.url}"> <span class="iconify" data-icon="ph:link-bold"></span> <span class="d-inline text-medium">${truncatedUrl}${resourceNames}</span> </a> `; let brElement = document.createElement('br'); brElement.style.marginTop = '5px'; let container = document.getElementById('resource-container'); container.appendChild(linkElement); container.appendChild(brElement); }); </script> </div> </div> </div> </div> <!--EXPLOITABILITY--> <div id="left-4" class="col-12 col-md-12 col-lg-7"> <div class="card m-0 h-100"> <div class="card-header d-flex flex-column px-3 pb-0 pt-0 bg-transparent"> <div class="d-flex align-items-center" style="justify-content: space-between;"> <h2 class="bf-section-title">Exploitability <span class="iconify" data-icon="mynaui:info-square" data-toggle="tooltip" data-placement="top" style="margin:auto; color:#386641;" title="Check out this table to see vulnerability types (CWEs) and their exploit potential."></span> </h2> <div id="exploit-tabs" style="display: flex; gap: 4px;"> <div id="cwe-button" style="cursor: pointer;"> <div style="padding: 8px; background: white; box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.25); border-radius: 8px; display: flex; align-items: center; gap: 4px;"> <div style="text-align: center; color: var(--Primary-color, #2C5E7D); font-size: 16px; font-family: 'Open Sans'; font-weight: 400; line-height: 20px;">CWE</div> <img style="width: 18px; height: 18px" src="/static/logo/cwe_logo.png" alt="CWE Icon" /> </div> </div> <div id="kev-button" style="cursor: pointer;"> <div style="padding: 8px; background: var(--Color-Gray-Gray-100, #F6F9FC); box-shadow: 0px 2px 5.5px rgba(0, 0, 0, 0.06); border-radius: 8px; display: flex; align-items: center; gap: 4px;"> <div style="text-align: center; color: var(--Primary-color, #2C5E7D); font-size: 16px; font-family: 'Inter'; font-weight: 700; line-height: 20px;">KEV</div> <img style="width: 18px; height: 18px" src="/static/logo/kev_logo.png" alt="KEV Icon" /> </div> </div> </div> </div> </div> <div class="bento-body card my-0 mx-0 h-100 bento-text bento-dimensions"> <div id="exploitability-inner-bento" class="mt-0"> <div id="cwe-tab" style="display: block;"> <table id="table-exploitability" class="bento-text"> <thead> <tr> <th> CWE ID <span class="iconify" data-icon="mynaui:info-square" data-toggle="tooltip" data-placement="top" style="margin-bottom: 5px;" title="Common Weakness Enumeration (CWE) identifiers and descriptions related to this vulnerability"></span> </th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>CWE-79</td> <td class="bento-text" style="max-width: 450px; white-space: normal; overflow: visible; text-overflow: clip;">The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.</td> </tr> </tbody> </table> </div> <div id="kev-tab" style="display: none;"> <div class="d-flex flex-column" style="gap: 16px;"> <div class="no-data-container bento-text"> This CVE does not appear in the KEV catalog. </div> </div> </div> </div> </div> </div> </div> </div> <div id="left-capec" class="col-12 col-md-12"> <div class="card m-0 h100"> <div class="card-header px-3 py-2 bg-transparent"> <h2 class="bf-section-title"> Attack-Flow Graph <span class="iconify" data-icon="mynaui:info-square" data-toggle="tooltip" data-placement="top" style="margin:auto; color:#386641;" title="Visual map of this CVE: CVE β†’ CWE weaknesses β†’ CAPEC attack patterns β†’ MITRE ATT&CK techniques. Hover for IDs; scroll to zoom."></span> </h2> </div> <div class="bento-body p-0 position-relative" style="height:600px;"> <!-- Always render the graph so it’s there under the veil --> <svg id="afg-svg" width="100%" height="100%"></svg> <!-- Obscuring veil with teaser reveal and CTA --> <div id="afg-veil" class="afg-obscure" aria-hidden="true"> <div class="afg-cta"> <div class="fw-semibold mb-1">Sign in to view the full Attack-Flow Graph</div> </div> </div> </div> </div> </div> <script src="https://d3js.org/d3.v7.min.js"></script> <script> ;(function(){ const nodes = [{"id": "CVE-2025-66024", "group": "cve", "title": "CVE-2025-66024", "desc": ""}, {"id": "CWE-79", "group": "cwe", "title": "CWE-79", "desc": "The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that…"}, {"id": "CAPEC-70", "group": "capec", "title": "CAPEC-70 Try Common or Default Usernames and Passwords", "desc": "An adversary may try certain common or default usernames and passwords to gain access…"}, {"id": "T1078.001", "group": "attack", "title": "Valid Accounts", "desc": "Default Accounts"}, {"id": "CAPEC-588", "group": "capec", "title": "CAPEC-588 DOM-Based XSS", "desc": "This type of attack is a form of Cross-Site Scripting (XSS) where a malicious script is…"}, {"id": "CAPEC-591", "group": "capec", "title": "CAPEC-591 Reflected XSS", "desc": "This type of attack is a form of Cross-Site Scripting (XSS) where a malicious script is…"}, {"id": "CAPEC-592", "group": "capec", "title": "CAPEC-592 Stored XSS", "desc": "An adversary utilizes a form of Cross-site Scripting (XSS) where a malicious script is…"}, {"id": "CAPEC-63", "group": "capec", "title": "CAPEC-63 Cross-Site Scripting (XSS)", "desc": "An adversary embeds malicious scripts in content that will be served to web browsers. The…"}, {"id": "CAPEC-85", "group": "capec", "title": "CAPEC-85 AJAX Footprinting", "desc": "This attack utilizes the frequent client-server roundtrips in Ajax conversation to scan a…"}]; const links = [{"source": "CVE-2025-66024", "target": "CWE-79", "label": ""}, {"source": "CWE-79", "target": "CAPEC-70", "label": ""}, {"source": "CAPEC-70", "target": "T1078.001", "label": ""}, {"source": "CWE-79", "target": "CAPEC-588", "label": ""}, {"source": "CWE-79", "target": "CAPEC-591", "label": ""}, {"source": "CWE-79", "target": "CAPEC-592", "label": ""}, {"source": "CWE-79", "target": "CAPEC-63", "label": ""}, {"source": "CWE-79", "target": "CAPEC-85", "label": ""}]; // Visual knobs const RADIUS = { cve: 18, cwe: 16, capec: 16, attack: 16 }; const TITLE_SIZE = 13, DESC_SIZE = 10; const TITLE_LINES = { cve:1, cwe:1, capec:2, attack:99 }; const DESC_LINES = { cve:0, cwe:2, capec:2, attack:2 }; const WRAP_PX = { cve:150, cwe:180, capec:220, attack:240 }; const COLUMN_GAP = { cve:22, cwe:28, capec:54, attack:42 }; const TOP = 70, BOT = 60; // a little extra space for lane headings const svg = d3.select("#afg-svg"); const { width, height } = svg.node().getBoundingClientRect(); const root = svg.append("g"); svg.call(d3.zoom().scaleExtent([0.8, 2.5]).on("zoom", e=> root.attr("transform", e.transform))); const lanes = ["cve","cwe","capec","attack"]; const laneX = { cve: width*0.12, cwe: width*0.32, capec: width*0.58, attack: width*0.82 }; // --- lane boundaries (between columns) --- const b1 = (laneX.cve + laneX.cwe )/2; // CVE | CWE const b2 = (laneX.cwe + laneX.capec)/2; // CWE | CAPEC const b3 = (laneX.capec + laneX.attack)/2; // CAPEC | ATT&CK // Column areas for headings/wrapping const colArea = { cve: { left: laneX.cve - (b1 - laneX.cve), right: b1 }, cwe: { left: b1, right: b2 }, capec: { left: b2, right: b3 }, attack: { left: b3, right: laneX.attack + (laneX.attack - b3) } }; // Visible title without codes for CAPEC/ATT&CK function visibleTitle(d){ let t = (d.title || d.id || "").trim(); if (d.group === "capec") t = t.replace(/^CAPEC-\d+\s*/i, ""); if (d.group === "attack") t = t.replace(/^T\d+(?:\.\d+)?\s*/i, ""); return t; } // Wrap helper function wrapLines(sel, widthFn, linesFn){ sel.each(function(d){ const maxWidth = (typeof widthFn === "function") ? widthFn(d) : widthFn; const maxLines = (typeof linesFn === "function") ? linesFn(d) : linesFn; const el = d3.select(this); const full = (el.text() || "").trim(); el.text(null); if (!full || !maxLines || !maxWidth) return; const words = full.split(/\s+/).reverse(); let line=[], word, lineNo=1; let tspan = el.append("tspan").attr("x",0).text(""); while ((word = words.pop())) { line.push(word); tspan.text(line.join(" ")); if (tspan.node().getComputedTextLength() > maxWidth) { line.pop(); tspan.text(line.join(" ")); line = [word]; lineNo += 1; if (lineNo > maxLines) { const prev = el.select("tspan:last-child"); const cur = prev.text(); prev.text(cur.replace(/\s+\S+$/, "") + "…"); break; } tspan = el.append("tspan").attr("x",0).attr("dy","1.2em").text(word); } } }); } // Seed positions evenly per lane const byGroup = Object.fromEntries(lanes.map(L=>[L, nodes.filter(n=>n.group===L)])); for (const L of lanes) { const list = byGroup[L], n = Math.max(list.length, 1); const step = (height - TOP - BOT) / n; list.forEach((d,i)=> { d.__x = laneX[L]; d.__y = TOP + step*(i+0.5); }); } const color = d3.scaleOrdinal().domain(lanes) .range(["#e74c3c","#3498db","#f1c40f","#2ecc71"]); // ---- Lane guides + headers ------------------------------------ const headerText = { cve: { title:"CVE", expl:"A specific vulnerability record." }, cwe: { title:"CWE", expl:"Weakness categories the CVE maps to." }, capec: { title:"CAPEC", expl:"Attack patterns that exploit the weakness." }, attack: { title:"ATT&CK", expl:"MITRE ATT&CK techniques linked to the pattern." } }; // faint dashed vertical lines at boundaries (between columns) const guideLayer = root.append("g"); [b1,b2,b3].forEach(x => { guideLayer.append("line") .attr("x1", x).attr("x2", x) .attr("y1", 0).attr("y2", height) .attr("stroke", "#e5e7eb") .attr("stroke-width", 1) .attr("stroke-dasharray", "4,6"); }); guideLayer.lower(); // Column headings centered within each column area, with wrapped explanation const headerLayer = root.append("g"); lanes.forEach(L => { const area = colArea[L]; const cx = (area.left + area.right) / 2; const maxW = Math.max(80, (area.right - area.left) - 24); // padding from borders const g = headerLayer.append("g").attr("transform", `translate(${cx}, 26)`); g.append("text") .attr("text-anchor", "middle") .attr("font-weight", 800) .attr("font-size", 14) .attr("fill", "#0b1220") .text(headerText[L].title); const expl = g.append("text") .attr("text-anchor", "middle") .attr("font-size", 11) .attr("fill", "#475569") .attr("dy", "1.4em") .text(headerText[L].expl); // wrap explanation to column width (multiple rows, no overlap) expl.call(wrapLines, maxW, 3); }); // ---- Nodes ----------------------------------------------------- const nodeLayer = root.append("g"); const nodeG = nodeLayer .selectAll("g.node") .data(nodes) .join("g") .attr("class", d => `node ${d.group}`); nodeG.append("circle") .attr("r", d=> RADIUS[d.group] || 14) .attr("fill", d=> color(d.group)) .attr("stroke","#fff").attr("stroke-width",1.5); const title = nodeG.append("text") .attr("font-size", TITLE_SIZE) .attr("font-weight", 700) .attr("text-anchor","middle") .attr("fill", "#111") .text(d => visibleTitle(d)); // simple HTML tag stripper for CAPEC/CWE/ATT&CK blurbs const clean = s => String(s ?? "") .replace(/<[^>]*>/g, " ") // drop tags .replace(/\s+/g, " ") // collapse whitespace .trim(); const desc = nodeG.append("text") .attr("font-size", DESC_SIZE) .attr("text-anchor","middle") .attr("fill", "#374151") .text(d => clean(d.desc)); title.attr("dy", d => -(RADIUS[d.group]||14) - 10); desc .attr("dy", d => (RADIUS[d.group]||14) + 16); title.call(wrapLines, d=>WRAP_PX[d.group]||180, d=>TITLE_LINES[d.group]??1); desc .call(wrapLines, d=>WRAP_PX[d.group]||200, d=>DESC_LINES[d.group]??1); // Helper: concise, de-duped tooltip text function tooltipText(d){ const id = (d.id || "").trim(); const vis = (visibleTitle(d) || "").trim(); // already strips CAPEC-/Txxxx from label const desc = (d.desc || "").trim(); const parts = []; // Prefer showing ID; add the (de-coded) title only if it adds info if (id) parts.push(id); // Only add title when it's not the same as ID and doesn't start with the ID if (vis && vis !== id && !vis.toUpperCase().startsWith(id.toUpperCase())) { parts.push(vis); } if (desc) parts.push(desc); return parts.join(" β€” "); } nodeG.append("title").text(tooltipText); nodeG.attr("transform", d => `translate(${d.__x},${d.__y})`); // Column packing to avoid overlap function packColumn(group){ const sel = nodeG.filter(d=>d.group===group); const arr = sel.nodes().map(el => { const d = d3.select(el).datum(); d3.select(el).attr("transform", `translate(${d.__x},${d.__y})`); const bb = el.getBBox(); return { el, d, bb, half: bb.height/2 }; }).sort((a,b)=> a.d.__y - b.d.__y); if (!arr.length) return; let cursor = TOP + arr[0].half; const gap = COLUMN_GAP[group] || 24; for (const it of arr) { const minY = cursor; const desired = it.d.__y; const placed = Math.max(minY, desired); it.d.__y = placed; cursor = placed + it.half + gap; } } packColumn('cwe'); packColumn('capec'); packColumn('attack'); nodeG.attr("transform", d => `translate(${d.__x},${d.__y})`); // --- Edges (shortened to circle edges, no arrows) -------------- const nodeById = Object.fromEntries(nodes.map(n=>[n.id, n])); function edgeSegment(d){ const s = nodeById[d.source], t = nodeById[d.target]; if (!s || !t) return null; let x1 = s.__x, y1 = s.__y, x2 = t.__x, y2 = t.__y; const dx = x2 - x1, dy = y2 - y1, dist = Math.hypot(dx, dy) || 1; const rs = (RADIUS[s.group]||14) + 2, rt = (RADIUS[t.group]||14) + 2; const ux = dx / dist, uy = dy / dist; x1 += ux * rs; y1 += uy * rs; x2 -= ux * rt; y2 -= uy * rt; return { x1, y1, x2, y2 }; } const linkLayer = root.append("g"); const linkPaths = linkLayer.selectAll("path") .data(links) .join("path") .attr("class","link") // <-- add class .attr("stroke", "#d6d6d6") .attr("stroke-width", 1.3) .attr("fill", "none") .attr("d", d => { const seg = edgeSegment(d); return seg ? `M${seg.x1},${seg.y1} L${seg.x2},${seg.y2}` : ""; }); // keep links behind nodes/labels linkLayer.lower(); // Edge labels (darker, centered on edge) const labelLayer = root.append("g").attr("pointer-events","none"); const labelText = labelLayer.selectAll("text") .data(links) .join("text") .attr("class","edge-label") // <-- add class .attr("font-size", 11) .attr("fill", "#0f172a") .attr("font-weight", 600) .text(d => d.label || ""); function positionEdgeLabels(){ labelText .attr("x", d => { const s = edgeSegment(d); return s ? (s.x1 + s.x2)/2 : 0; }) .attr("y", d => { const s = edgeSegment(d); return s ? (s.y1 + s.y2)/2 : 0; }); } positionEdgeLabels(); // Fit to view const bb = root.node().getBBox(); const scale = Math.min((width*0.9)/bb.width, (height*0.9)/bb.height); const tx = (width - bb.width*scale)/2 - bb.x*scale; const ty = (height - bb.height*scale)/2 - bb.y*scale; svg.call(d3.zoom().transform, d3.zoomIdentity.translate(tx,ty).scale(Math.max(1, Math.min(2, scale)))); // --- Toggle: Hide CAPECs without ATT&CK (control on header, right-aligned) --- (function setupCapecToggle(){ // Prefer a stable id on the card header; else fall back to the existing structure const header = document.querySelector("#afg-header") || document.querySelector("#left-capec .card-header"); if (!header) return; // If the toggle already exists, don't add it again if (header.querySelector('#afg-toggle-wrap')) return; // Make the header a flex row (non-destructive) header.style.display = header.style.display || "flex"; header.style.alignItems = header.style.alignItems || "center"; header.style.gap = header.style.gap || "8px"; // Controls wrapper on the far right const controls = header.querySelector(".afg-controls") || (() => { const c = document.createElement("div"); c.className = "afg-controls"; c.style.marginLeft = "auto"; c.style.display = "inline-flex"; c.style.alignItems = "center"; c.style.gap = "6px"; header.appendChild(c); return c; })(); // Build the toggle (wrapped so we can easily remove it) const wrap = document.createElement("label"); wrap.id = "afg-toggle-wrap"; wrap.style.cursor = "pointer"; wrap.style.userSelect = "none"; wrap.innerHTML = ` <input type="checkbox" id="toggle-capecs" style="vertical-align:middle;"> <span style="margin-left:6px; vertical-align:middle;">Hide CAPECs without ATT&CK</span> `; controls.appendChild(wrap); // Precompute which CAPECs have at least one ATT&CK edge const capecsWithAttack = new Set( links .filter(e => String(e.source).startsWith("CAPEC-") && String(e.target).startsWith("T")) .map(e => String(e.source)) ); function applyToggle(hide) { d3.selectAll(".node.capec").style("display", function(d){ const hasAttack = capecsWithAttack.has(String(d.id)); return (hide && !hasAttack) ? "none" : null; }); d3.selectAll(".link").style("display", function(e){ const s = String(e.source), t = String(e.target); const touchesHiddenCapec = (s.startsWith("CAPEC-") && !capecsWithAttack.has(s)) || (t.startsWith("CAPEC-") && !capecsWithAttack.has(t)); return (hide && touchesHiddenCapec) ? "none" : null; }); d3.selectAll(".edge-label").style("display", function(e){ const s = String(e.source), t = String(e.target); const touchesHiddenCapec = (s.startsWith("CAPEC-") && !capecsWithAttack.has(s)) || (t.startsWith("CAPEC-") && !capecsWithAttack.has(t)); return (hide && touchesHiddenCapec) ? "none" : null; }); } const cb = document.getElementById("toggle-capecs"); cb.addEventListener("change", () => applyToggle(cb.checked)); applyToggle(cb.checked); })(); })(); </script> </div> <div id="right-bento" class="col-12 col-md-12 col-lg-5"> <!-- AI POWERED QA & ASK OUR ASSISTANT--> <div class="row"> <div id="right-1" class="col-12 col-md-12 bf-ai-column"> <div class="card m-0 bf-ai-widget bf-ai-quick-widget"> <div class="bf-ai-widget-header"> <div class="bf-ai-main-icon"> <span class="iconify" data-icon="lucide:sparkles"></span> </div> <div class="bf-ai-heading"> <h2 class="bf-section-title">AI Quick Actions</h2> <span>Instant insights powered by AI</span> </div> <span class="iconify bf-ai-info" data-icon="mynaui:info-square" data-toggle="tooltip" data-placement="top" title="These questions were answered using content from third-party websites. Please refer to the source for the latest information."></span> </div> <div id="qa-bento" class="bf-ai-action-stack"> <details class="bf-ai-action" data-question="Can you explain this vulnerability to me?" data-question-key="can_you_explain_this_vulnerability_to_me_question" > <summary class="bf-ai-action-summary"> <span class="bf-ai-action-label"> <span class="iconify bf-ai-action-icon" data-icon="lucide:sparkles"></span> <span>Executive Summary</span> </span> <span class="bf-ai-action-controls"> <span class="iconify question-arrow" data-icon="iconamoon:arrow-down-2"></span> </span> </summary> <div class="bf-ai-answer answer-content"> <div class="answer-text"> <p style="margin:0 0 1rem 0; line-height:1.6;">The vulnerability exists in the XWiki blog application versions prior to 9.15.7. It is a Stored Cross-Site Scripting (XSS) issue that occurs because the blog post title is inserted directly into the HTML <title> tag without proper escaping.</p><p style="margin:0 0 1rem 0; line-height:1.6;">An attacker who has permission to create or edit blog posts can inject malicious JavaScript code into the title field. This malicious script will then execute in the browsers of any users who view the blog post, including administrators.</p><p style="margin:0 0 1rem 0; line-height:1.6;">This can lead to session hijacking or privilege escalation. The vulnerability was fixed in version 9.15.7 by adding the missing escaping to the title field.</p> </div> <div class="bf-ai-feedback"> <button class="feedback-button" type="button" onclick="handleClick('like', this)"> Useful <span class="iconify" data-icon="fluent:thumb-like-16-regular"></span> <span data-question="Can you explain this vulnerability to me?" data-question-key="can_you_explain_this_vulnerability_to_me_question" class="like-count">0</span> </button> <button class="feedback-button" type="button" onclick="handleClick('dislike', this)"> Not Useful <span class="iconify" data-icon="fluent:thumb-dislike-16-regular"></span> <span data-question="Can you explain this vulnerability to me?" data-question-key="can_you_explain_this_vulnerability_to_me_question" class="dislike-count">0</span> </button> <div class="thank-you-message-paragraph"></div> </div> </div> </details> <details class="bf-ai-action" data-question="How can this vulnerability be detected on my network or system? Can you suggest some commands?" data-question-key="how_can_this_vulnerability_be_detected_on_my_network_or_system_can_you_suggest_some_commands_question" > <summary class="bf-ai-action-summary"> <span class="bf-ai-action-label"> <span class="iconify bf-ai-action-icon" data-icon="lucide:search"></span> <span>Detection Guidance</span> </span> <span class="bf-ai-action-controls"> <span class="iconify question-arrow" data-icon="iconamoon:arrow-down-2"></span> </span> </summary> <div class="bf-ai-answer answer-content"> <div class="answer-text"> <p style="margin:0 0 1rem 0; line-height:1.6;">I don't know</p> </div> <div class="bf-ai-feedback"> <button class="feedback-button" type="button" onclick="handleClick('like', this)"> Useful <span class="iconify" data-icon="fluent:thumb-like-16-regular"></span> <span data-question="How can this vulnerability be detected on my network or system? Can you suggest some commands?" data-question-key="how_can_this_vulnerability_be_detected_on_my_network_or_system_can_you_suggest_some_commands_question" class="like-count">0</span> </button> <button class="feedback-button" type="button" onclick="handleClick('dislike', this)"> Not Useful <span class="iconify" data-icon="fluent:thumb-dislike-16-regular"></span> <span data-question="How can this vulnerability be detected on my network or system? Can you suggest some commands?" data-question-key="how_can_this_vulnerability_be_detected_on_my_network_or_system_can_you_suggest_some_commands_question" class="dislike-count">0</span> </button> <div class="thank-you-message-paragraph"></div> </div> </div> </details> <details class="bf-ai-action" data-question="How can this vulnerability impact me? :" data-question-key="how_can_this_vulnerability_impact_me_question" > <summary class="bf-ai-action-summary"> <span class="bf-ai-action-label"> <span class="iconify bf-ai-action-icon" data-icon="lucide:target"></span> <span>Impact Analysis</span> </span> <span class="bf-ai-action-controls"> <span class="iconify question-arrow" data-icon="iconamoon:arrow-down-2"></span> </span> </summary> <div class="bf-ai-answer answer-content"> <div class="answer-text"> <p style="margin:0 0 1rem 0; line-height:1.6;">This vulnerability can impact you by allowing attackers to execute malicious JavaScript in the browsers of users who view the affected blog posts.</p><p style="margin:0 0 1rem 0; line-height:1.6;">Such script execution can lead to session hijacking, where attackers steal user session tokens, or privilege escalation, where attackers gain higher access rights than intended.</p><p style="margin:0 0 1rem 0; line-height:1.6;">This can compromise user accounts, including administrator accounts, potentially leading to unauthorized access and control over the XWiki platform.</p> </div> <div class="bf-ai-feedback"> <button class="feedback-button" type="button" onclick="handleClick('like', this)"> Useful <span class="iconify" data-icon="fluent:thumb-like-16-regular"></span> <span data-question="How can this vulnerability impact me? :" data-question-key="how_can_this_vulnerability_impact_me_question" class="like-count">0</span> </button> <button class="feedback-button" type="button" onclick="handleClick('dislike', this)"> Not Useful <span class="iconify" data-icon="fluent:thumb-dislike-16-regular"></span> <span data-question="How can this vulnerability impact me? :" data-question-key="how_can_this_vulnerability_impact_me_question" class="dislike-count">0</span> </button> <div class="thank-you-message-paragraph"></div> </div> </div> </details> <details class="bf-ai-action" data-question="How does this vulnerability affect compliance with common standards and regulations (like GDPR, HIPAA)?:" data-question-key="how_does_this_vulnerability_affect_compliance_with_common_standards_and_regulations_like_gdpr_hipaa_question" > <summary class="bf-ai-action-summary"> <span class="bf-ai-action-label"> <span class="iconify bf-ai-action-icon" data-icon="lucide:clipboard-list"></span> <span>Compliance Impact</span> </span> <span class="bf-ai-action-controls"> <span class="iconify question-arrow" data-icon="iconamoon:arrow-down-2"></span> </span> </summary> <div class="bf-ai-answer answer-content"> <div class="answer-text"> <p style="margin:0 0 1rem 0; line-height:1.6;">I don't know</p> </div> <div class="bf-ai-feedback"> <button class="feedback-button" type="button" onclick="handleClick('like', this)"> Useful <span class="iconify" data-icon="fluent:thumb-like-16-regular"></span> <span data-question="How does this vulnerability affect compliance with common standards and regulations (like GDPR, HIPAA)?:" data-question-key="how_does_this_vulnerability_affect_compliance_with_common_standards_and_regulations_like_gdpr_hipaa_question" class="like-count">0</span> </button> <button class="feedback-button" type="button" onclick="handleClick('dislike', this)"> Not Useful <span class="iconify" data-icon="fluent:thumb-dislike-16-regular"></span> <span data-question="How does this vulnerability affect compliance with common standards and regulations (like GDPR, HIPAA)?:" data-question-key="how_does_this_vulnerability_affect_compliance_with_common_standards_and_regulations_like_gdpr_hipaa_question" class="dislike-count">0</span> </button> <div class="thank-you-message-paragraph"></div> </div> </div> </details> <details class="bf-ai-action" data-question="What immediate steps should I take to mitigate this vulnerability?" data-question-key="what_immediate_steps_should_i_take_to_mitigate_this_vulnerability_question" > <summary class="bf-ai-action-summary"> <span class="bf-ai-action-label"> <span class="iconify bf-ai-action-icon" data-icon="lucide:shield"></span> <span>Mitigation Strategies</span> </span> <span class="bf-ai-action-controls"> <span class="iconify question-arrow" data-icon="iconamoon:arrow-down-2"></span> </span> </summary> <div class="bf-ai-answer answer-content"> <div class="answer-text"> <p style="margin:0 0 1rem 0; line-height:1.6;">The vulnerability has been patched in the XWiki blog application version 9.15.7 by adding missing escaping to the blog post title.</p><p style="margin:0 0 1rem 0; line-height:1.6;">To mitigate this vulnerability, you should immediately upgrade the XWiki blog application to version 9.15.7 or later.</p><p style="margin:0 0 1rem 0; line-height:1.6;">No known workarounds are available.</p> </div> <div class="bf-ai-feedback"> <button class="feedback-button" type="button" onclick="handleClick('like', this)"> Useful <span class="iconify" data-icon="fluent:thumb-like-16-regular"></span> <span data-question="What immediate steps should I take to mitigate this vulnerability?" data-question-key="what_immediate_steps_should_i_take_to_mitigate_this_vulnerability_question" class="like-count">0</span> </button> <button class="feedback-button" type="button" onclick="handleClick('dislike', this)"> Not Useful <span class="iconify" data-icon="fluent:thumb-dislike-16-regular"></span> <span data-question="What immediate steps should I take to mitigate this vulnerability?" data-question-key="what_immediate_steps_should_i_take_to_mitigate_this_vulnerability_question" class="dislike-count">0</span> </button> <div class="thank-you-message-paragraph"></div> </div> </div> </details> </div> </div> <div class="card m-0 bf-ai-widget bf-chat-widget"> <div class="bf-ai-widget-header"> <div class="bf-ai-main-icon"> <span class="iconify" data-icon="lucide:message-square"></span> </div> <div class="bf-ai-heading"> <h2 class="bf-section-title">Chat Assistant</h2> <span>Ask questions about this CVE</span> </div> </div> <div id="question-bento" class="bf-chat-body bento-text"> <div class="bf-chat-welcome"> <span class="iconify bf-chat-welcome-icon" data-icon="lucide:sparkles"></span> <span> Hi! I’m here to help you understand CVE-2025-66024. Ask me anything about the vulnerability, its impact, or mitigation strategies. </span> </div> <div id="chat-div"></div> <div class="bf-chat-input-row"> <div class="input-wrapper bf-chat-input-wrapper"> <input type="text" id="question-input" placeholder="Ask your question here ..." maxlength="70" oninput="updateCharCount()"> <span id="char-count">0/70</span> </div> <button id="submit-question-button" class="bf-chat-submit" onclick="submitQuestion()"> <span class="iconify" data-icon="fa6-solid:paper-plane"></span> </button> </div> </div> </div> </div> </div> <div class="row"> <div id="right-3" class="col-12 col-md-12"> <div class="card m-0"> <div class="card-header d-flex flex-column px-3 pb-0 pt-0 bg-transparent"> <div class="d-flex align-items-end"> <h2 class="bf-section-title">EPSS Chart</h2> </div> </div> <div class="bento-body card my-0 mx-0 h-100 bento-text info-grid"> <canvas id="epssChart"></canvas> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> <!-- -------- START FOOTER 3 w/ COMPANY DESCRIPTION WITH LINKS & SOCIAL ICONS & COPYRIGHT ------- --> <footer id="main-footer" class="footer pt-5"> <div class="container"> <div class="row" style="gap: 30px;"> <div class="col-12 d-flex justify-content-center text-center" style="gap: 30px;"> <a href="/#features" class="link">Features</a> <a href="/#plans" class="link">Plans</a> <a href="/#contact" class="link">Contact</a> <a href="/about" class="link">About Us</a> <a href="/terms-of-service" class="link">ToS</a> </div> <div class="col-12 d-flex justify-content-center text-center mb-4 mt-2" style="gap: 24px;"> <a href="https://www.youtube.com/@BaseFortify" target="_blank" class="link" title="Visit our YouTube channel"> <img src="/static/logo/youtube-logo-2.svg" alt="YouTube logo - Subscribe to BaseFortify on YouTube" width="24" height="24"> </a> <a href="https://www.linkedin.com/showcase/basefortify/" target="_blank" class="link" title="Connect with us on LinkedIn"> <img src="/static/logo/linkedin-logo-2.svg" alt="LinkedIn logo - Connect with BaseFortify on LinkedIn" width="24" height="24"> </a> <a rel="me" href="https://infosec.exchange/@BaseFortify" target="_blank" class="link" title="Find us on Mastodon"> <img src="/static/logo/mastodon_logo.svg" alt="Mastodon logo - Connect with BaseFortify on Mastodon" width="24" height="24"> </a> <a rel="me" href="https://bsky.app/profile/basefortify.bsky.social" target="_blank" class="link" title="Visit us on Bluesky"> <img src="/static/logo/bluesky_logo.svg" alt="Bluesky logo - Connect with BaseFortify on Bluesky" width="24" height="24"> </a> <a href="https://www.instagram.com/basefortify/" target="_blank" class="me-xl-4 me-4" title="Visit us on Instagram"> <img src="/static/logo/instagram.webp" alt="Instagram logo - Connect with BaseFortify on Instagram" width="24" height="24"> </a> </div> </div> </div> </footer> <!-- -------- END FOOTER 3 w/ COMPANY DESCRIPTION WITH LINKS & SOCIAL ICONS & COPYRIGHT ------- --> <script> $(document).ready(function () { const sidebarElem = document.getElementById("sideMenuContent"); const cveReportsTab = sidebarElem.children[1]; cveReportsTab.classList.add("active"); }); let originalDimensions = null; let originalDimensionsDetermined = false; // Function to get dimensions from the first chart only function getFirstChartDimensions() { if (originalDimensionsDetermined) return originalDimensions; const charts = Object.values(Chart.instances); if (charts.length > 0) { const firstChart = charts[0]; if (firstChart) { originalDimensions = { canvasWidth: firstChart.canvas.width, canvasHeight: firstChart.canvas.height, styleWidth: firstChart.canvas.style.width, styleHeight: firstChart.canvas.style.height, responsive: firstChart.options.responsive, maintainAspectRatio: firstChart.options.maintainAspectRatio }; originalDimensionsDetermined = true; } } return originalDimensions; } function initializeDimensions() { // Wait for charts to be created if needed if (Object.keys(Chart.instances).length === 0) { // If no charts exist yet, wait a bit and try again setTimeout(initializeDimensions, 100); return; } getFirstChartDimensions(); } function adjustLayout() { const dimensions = getFirstChartDimensions(); const width = window.innerWidth; var divLeftRowOne = document.getElementById('left-row-1'); var divLeftRowTwo = document.getElementById('left-row-2'); var cvssDiv = document.getElementById('left-2'); var affectedVendorsDiv = document.getElementById('left-3'); var exploitDiv = document.getElementById('left-4'); var resourcesDiv = document.getElementById('left-5'); if (width <= 1600) { divLeftRowOne.classList.remove('row'); divLeftRowTwo.classList.remove('row'); cvssDiv.classList.remove('col-lg-7', 'h-100'); affectedVendorsDiv.classList.remove('col-lg-5'); resourcesDiv.classList.remove('col-lg-5', 'h-100'); exploitDiv.classList.remove('col-lg-7'); document.getElementById('left-3').classList.remove('d-flex', 'flex-column', 'h-100'); cvssDiv.style.marginBottom = '3rem'; resourcesDiv.style.marginBottom = '3rem'; const cvssContainers = cvssDiv.querySelectorAll('div.cvss-container'); cvssContainers.forEach(div => { div.classList.remove('col-md-12'); div.classList.add('d-flex'); }); // Get all chart instances and resize them Object.values(Chart.instances).forEach(chart => { if (chart && chart.canvas.id != 'epssChart') { chart.resize(300, 300); chart.options.responsive = false; chart.options.maintainAspectRatio = true; chart.canvas.style.width = '200px'; chart.update(); } }); var targetDiv = document.getElementById('right-bento'); targetDiv.appendChild(resourcesDiv); } else { divLeftRowOne.classList.add('row'); divLeftRowTwo.classList.add('row'); cvssDiv.classList.add('col-lg-7', 'h-100'); affectedVendorsDiv.classList.add('col-lg-5'); affectedVendorsDiv.style.maxHeight = '100%'; resourcesDiv.classList.add('col-lg-5', 'h-100'); exploitDiv.classList.add('col-lg-7', 'h-100'); const cvssContainers = cvssDiv.querySelectorAll('div.cvss-container'); cvssContainers.forEach(div => { div.classList.add('col-md-12'); div.classList.remove('d-flex'); }); Object.values(Chart.instances).forEach(chart => { if (chart) { // Reset internal chart state chart.stop(); chart.clear(); // Apply dimensions chart.canvas.width = dimensions.canvasWidth; chart.canvas.height = dimensions.canvasHeight; chart.canvas.style.width = dimensions.styleWidth; chart.canvas.style.height = dimensions.styleHeight; chart.update(); } }); divLeftRowTwo.insertBefore(resourcesDiv, divLeftRowTwo.firstChild); } } // Run on load and resize window.addEventListener('load', adjustLayout); window.addEventListener('resize', adjustLayout); $(document).ready(function () { $('[data-toggle="tooltip"]').on('click', function () { $(this).tooltip('hide'); // Hide the tooltip when clicked }); }); // Toggle the tooltips $(document).ready(function(){ $('[data-toggle="tooltip"]').tooltip(); }); $(document).ready(function () { colorCVSSBoxes(); }); function setupAFGGate(userSubscription) { const isMember = userSubscription === 'FREE_SUBSCRIPTION' || userSubscription === 'PREMIUM_SUBSCRIPTION'; const container = document.querySelector('#left-capec .bento-body'); if (!container) return; // Remove all possible veils first (covers server-rendered and JS-injected) container.querySelectorAll('#afg-veil, .afg-obscure').forEach(el => el.remove()); if (!isMember) { const veil = document.createElement('div'); veil.id = 'afg-veil'; veil.className = 'afg-obscure'; veil.setAttribute('aria-hidden', 'true'); veil.innerHTML = ` <div class="afg-cta"> <div class="cta-title">Sign in to view the full Attack-Flow Graph</div> <div class="cta-buttons"> <a href="/login" class="btn-cta btn-login">Log in</a> <a href="/register" class="btn-cta btn-register">Register now</a> </div> </div>`; container.appendChild(veil); } } var chat = false; var userId = "-"; var userSubscription = "-"; var changesToChat = false; document.addEventListener('DOMContentLoaded', function() { fetch('/cve_report_blueprint/get_user_subscription', { method: 'GET' }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { // Update with types of subscriptions userSubscription = data.subscription; if (data.subscription === 'FREE_SUBSCRIPTION' || data.subscription === 'PREMIUM_SUBSCRIPTION') { chat = true; userId = data.userId; } else if (data.subscription === 'NO_SUBSCRIPTION' || data.subscription === 'NOT_AUTHENTICATED'){ chat = false; } else { console.error('Unknown subscription level:', data); chat = false; } // ⬇️ Gate the Attack-Flow Graph here setupAFGGate(userSubscription); const isLoggedIn = ( userSubscription === 'FREE_SUBSCRIPTION' || userSubscription === 'PREMIUM_SUBSCRIPTION' ); // Show toggle only to logged-in users if (isLoggedIn) { if (window.createCapecToggle) { // Pass the same nodes/links you used to build the graph window.createCapecToggle( [{"id": "CVE-2025-66024", "group": "cve", "title": "CVE-2025-66024", "desc": ""}, {"id": "CWE-79", "group": "cwe", "title": "CWE-79", "desc": "The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that…"}, {"id": "CAPEC-70", "group": "capec", "title": "CAPEC-70 Try Common or Default Usernames and Passwords", "desc": "An adversary may try certain common or default usernames and passwords to gain access…"}, {"id": "T1078.001", "group": "attack", "title": "Valid Accounts", "desc": "Default Accounts"}, {"id": "CAPEC-588", "group": "capec", "title": "CAPEC-588 DOM-Based XSS", "desc": "This type of attack is a form of Cross-Site Scripting (XSS) where a malicious script is…"}, {"id": "CAPEC-591", "group": "capec", "title": "CAPEC-591 Reflected XSS", "desc": "This type of attack is a form of Cross-Site Scripting (XSS) where a malicious script is…"}, {"id": "CAPEC-592", "group": "capec", "title": "CAPEC-592 Stored XSS", "desc": "An adversary utilizes a form of Cross-site Scripting (XSS) where a malicious script is…"}, {"id": "CAPEC-63", "group": "capec", "title": "CAPEC-63 Cross-Site Scripting (XSS)", "desc": "An adversary embeds malicious scripts in content that will be served to web browsers. The…"}, {"id": "CAPEC-85", "group": "capec", "title": "CAPEC-85 AJAX Footprinting", "desc": "This attack utilizes the frequent client-server roundtrips in Ajax conversation to scan a…"}], [{"source": "CVE-2025-66024", "target": "CWE-79", "label": ""}, {"source": "CWE-79", "target": "CAPEC-70", "label": ""}, {"source": "CAPEC-70", "target": "T1078.001", "label": ""}, {"source": "CWE-79", "target": "CAPEC-588", "label": ""}, {"source": "CWE-79", "target": "CAPEC-591", "label": ""}, {"source": "CWE-79", "target": "CAPEC-592", "label": ""}, {"source": "CWE-79", "target": "CAPEC-63", "label": ""}, {"source": "CWE-79", "target": "CAPEC-85", "label": ""}] ); } } else { // Ensure it's hidden/removed if it was ever injected document.getElementById('afg-toggle-wrap')?.remove(); } if (userSubscription != 'FREE_SUBSCRIPTION' && userSubscription != 'PREMIUM_SUBSCRIPTION') { // Change the aspect of the questions based on user subscription const qaContainer = document.getElementById('qa-bento'); if (!qaContainer) { return; } const detailsElements = qaContainer.querySelectorAll('details'); detailsElements.forEach((details, index) => { const summary = details.querySelector('summary'); const lockIcon = summary.querySelector('.lock-icon'); const arrow = summary.querySelector('.question-arrow'); const answerContent = details.querySelector('.answer-content'); const feedbackButtons = details.querySelector('.feedback-button'); const answerText = answerContent.querySelector('.answer-text'); if (index !== 0) { summary.classList.add('locked'); lockIcon.style.display = 'inline'; arrow.classList.remove('ms-auto'); answerContent.classList.add('blur'); feedbackButtons.style.display = 'none'; // Limit the text size for hidden questions const cutText = answerText.textContent.slice(0, 400); answerText.innerHTML = cutText; const buttonContainer = document.createElement('div'); buttonContainer.className = 'd-flex justify-content-center'; buttonContainer.style.position = 'absolute'; buttonContainer.style.bottom = '10px'; buttonContainer.style.right = '10px'; buttonContainer.style.pointerEvents = 'auto'; buttonContainer.innerHTML = ` <a href="/register" class="btn mb-3" style="color: #17254D; background-color: #FF9E00;"> <span class="d-sm-inline text-medium font-weight-bold">Register now</span> </a> `; answerContent.appendChild(buttonContainer); } }); } if (userId !== '-') { const payload = JSON.stringify({ user_id: userId, }); return fetch('/get_user_components', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: payload, keepalive: true, }); } }) .then(response => { if (response) { return response.json(); } }) .then(data => { }) .catch(error => { console.error('Problem with the fetch operation:', error); }); }); (function () { const cveId = "CVE-2025-66024"; // adjust if your CVE id lives elsewhere const dateFmt = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' }); const EPS = 0.1; // 0.1% floor for log scale let _cveEpssChart = null; let _connectLines = true; // ON by default // expose a quick toggle (optional UI can call this) window.toggleCveEpssLines = function toggleCveEpssLines() { _connectLines = !_connectLines; if (_cveEpssChart) { const ds = _cveEpssChart.data.datasets[0]; ds.showLine = _connectLines; ds.borderWidth = _connectLines ? 2 : 0; ds.tension = _connectLines ? 0.25 : 0; _cveEpssChart.update(); } }; // simple median for the dashed reference line function median(values) { if (!values?.length) return 0; const s = values.slice().sort((a,b)=>a-b); const mid = Math.floor(s.length/2); return (s.length % 2) ? s[mid] : (s[mid-1] + s[mid]) / 2; } function safeDateTick(v) { const n = Number(v); const d = Number.isFinite(n) ? new Date(n) : new Date(v); if (!(d instanceof Date) || Number.isNaN(d.getTime())) { return ''; } return dateFmt.format(d); } function renderEpss(raw) { const el = document.getElementById('epssChart'); if (!el) return; const ctx = el.getContext('2d'); // Normalize backend rows β†’ [{x: ms, y: %, rawY: %}] const points = (raw || []) .map(p => { const ms = typeof p.x === 'number' ? p.x : +new Date(p.x); const yP = Number(p.y) * 100; return { x: ms, y: yP, rawY: yP }; }) .filter(p => Number.isFinite(p.x) && Number.isFinite(p.y)); const hasData = points.length > 0; // Sort by time asc points.sort((a, b) => a.x - b.x); const DAY = 24 * 60 * 60 * 1000; // Show a real date window even if only 1 datapoint exists const PRE_DAYS_SINGLE = 14; const POST_DAYS_SINGLE = 3; const PAD_DAYS_MULTI = 2; let scaleMinX, scaleMaxX; let firstX = null; let lastX = null; if (hasData) { firstX = points[0].x; lastX = points[points.length - 1].x; if (points.length === 1) { scaleMinX = lastX - PRE_DAYS_SINGLE * DAY; scaleMaxX = lastX + POST_DAYS_SINGLE * DAY; } else { scaleMinX = firstX - PAD_DAYS_MULTI * DAY; scaleMaxX = lastX + PAD_DAYS_MULTI * DAY; } } else { const now = Date.now(); scaleMinX = now - (3 * DAY); scaleMaxX = now + (3 * DAY); } /* βœ… ADD THIS BLOCK RIGHT HERE */ if (!hasData) { const now = Date.now(); scaleMinX = now - (3 * DAY); scaleMaxX = now + (3 * DAY); } // Mean (for dashed guide) const values = points.map(p => p.rawY).filter(Number.isFinite); const yMean = values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0; // Draw mean across the *visible* window, not just data span const nMean = 30; const step = (scaleMaxX - scaleMinX) / (nMean - 1); const meanLine = Array.from({ length: nMean }, (_, i) => ({ x: scaleMinX + i * step, y: yMean })); const emptyStatePlugin = { id: 'emptyStatePlugin', afterDraw(chart, args, options) { if (!options || !options.show) return; const { ctx, chartArea } = chart; if (!chartArea) return; const { left, right, top, bottom } = chartArea; ctx.save(); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#6c757d'; ctx.font = '14px Open Sans, sans-serif'; ctx.fillText( 'EPSS data not yet available.', (left + right) / 2, (top + bottom) / 2 - 8 ); ctx.font = '12px Open Sans, sans-serif'; ctx.fillStyle = '#9aa3ad'; ctx.fillText( 'Values are typically published after 24 hours.', (left + right) / 2, (top + bottom) / 2 + 12 ); ctx.restore(); } }; // (Re)create chart const existing = Chart.getChart(el); if (existing) existing.destroy(); if (_cveEpssChart) { _cveEpssChart.destroy(); _cveEpssChart = null; } _cveEpssChart = new Chart(ctx, { plugins: [emptyStatePlugin], data: { datasets: [ { label: 'EPSS', data: points.map(p => ({ x: p.x, y: Math.max(EPS, p.y), rawY: p.rawY })), type: 'line', // use line; we’ll control showing the stroke showLine: _connectLines, borderWidth: _connectLines ? 2 : 0, borderColor: '#2b83df', backgroundColor: '#2b83df', tension: _connectLines ? 0.25 : 0, // marker style: first = square, others = cross pointStyle: (ctx) => ctx.dataIndex === 0 ? 'rect' : 'crossRot', pointRadius: (ctx) => ctx.dataIndex === 0 ? 6 : 3, pointHoverRadius: (ctx) => ctx.dataIndex === 0 ? 7 : 5, pointBorderWidth: 1.5 }, { label: hasData ? `Mean (${points.length})` : 'Mean', data: hasData ? meanLine.map(p => ({ x: p.x, y: Math.max(EPS, p.y) })) : [], type: 'line', showLine: hasData, pointRadius: 0, borderDash: [8,4], borderWidth: hasData ? 2 : 0, borderColor: '#888' } ] }, options: { responsive: true, maintainAspectRatio: false, parsing: false, interaction: { mode: 'nearest', intersect: false }, plugins: { emptyStatePlugin: { show: !hasData }, legend: { labels: { usePointStyle: true } }, decimation: { enabled: true, algorithm: 'lttb', samples: 400 }, tooltip: { backgroundColor: 'rgba(0,0,0,0.7)', titleColor: '#fff', bodyColor: '#fff', padding: 10, callbacks: { title: (items) => { const ts = items?.[0]?.parsed?.x; return ts ? new Date(ts).toLocaleDateString() : ''; }, label: (ctx) => { const raw = ctx.raw?.rawY ?? ctx.parsed?.y; return `EPSS: ${Number(raw).toFixed(1)}%`; } } } }, scales: { x: { type: 'time', min: scaleMinX, max: scaleMaxX, time: { unit: 'day' }, grid: { color: 'rgba(0,0,0,.06)' }, ticks: { callback: safeDateTick }, title: { display: true, text: 'Date' } }, y: { type: 'logarithmic', min: EPS, max: 100, grace: '18%', // gives a bit more separation near the bottom/top grid: { color: 'rgba(0,0,0,.06)' }, title: { display: true, text: 'EPSS (%)' }, ticks: { callback: (v) => { // Show the floor as β€œ0%” so the bottom isn’t visually β€œstuck” if (Number(v) === EPS) return '0%'; return `${Number(v).toFixed(0)}%`; } } } }, elements: { line: { borderJoinStyle: 'round' } } } }); } // Fetch via GET (avoids CSRF) fetch(`/epss_blueprint/get_epss_timeseries_cve?cve=${encodeURIComponent(cveId)}`) .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) .then(data => { const rows = Array.isArray(data) ? data : []; renderEpss(rows); }) .catch(err => { console.error('EPSS chart error:', err); renderEpss([]); // optional: also show empty-state on fetch failure }); })(); // Function to check if the version is within the range function checkVersionInRange(version, versionRange) { if (!version || !versionRange) return false; // Parse version range const [startOp, startVersion, endOp, endVersion] = versionRange .replace(/version/g, '') .trim() .split(/\s+/); let isWithinRange = true; // Evaluate start of range if (startOp && startVersion) { if (startOp.includes("<=") && version < startVersion) isWithinRange = false; if (startOp.includes("<") && version <= startVersion) isWithinRange = false; } // Evaluate end of range if (endOp && endVersion) { if (endOp.includes("<=") && version > endVersion) isWithinRange = false; if (endOp.includes("<") && version >= endVersion) isWithinRange = false; } return isWithinRange; } document.getElementById('question-input').addEventListener('keydown', function(event) { // Check if the Enter key is pressed if (event.key === 'Enter' && this.value.trim() !== '') { event.preventDefault(); // Prevent form submission or other default behavior submitQuestion(); // Call the submit function } }); async function initializeQuestionInputAndChat() { const questionInputElem = document.getElementById('question-input'); if (questionInputElem) { questionInputElem.addEventListener('keydown', function(event) { if (event.key === 'Enter' && this.value.trim() !== '') { event.preventDefault(); submitQuestion(); } }); } const charCount = document.getElementById('char-count'); const chatContainer = document.getElementById('chat-div'); if (questionInput) { questionInput.value = ''; } if (charCount) { charCount.textContent = '0/70'; } if (!chatContainer) return; chatContainer.innerHTML = ""; if (chat !== true) return; try { const payload = JSON.stringify({ cve_id: "CVE-2025-66024", user_id: userId }); const response = await fetch('/cve_report_blueprint/retrieve_chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, keepalive: true }); const data = await response.json(); let messages = {}; if (data && data.messages) { try { messages = typeof data.messages === 'string' ? JSON.parse(data.messages) : data.messages; } catch (e) { console.warn('Chat history could not be parsed.'); messages = {}; } } for (const timestamp in messages) { const texts = messages[timestamp]; const timestampDiv = document.createElement('div'); timestampDiv.classList.add('chat-timestamp'); timestampDiv.textContent = timestamp; chatContainer.appendChild(timestampDiv); texts.forEach((text, index) => { const textarea = document.createElement('textarea'); textarea.value = text; textarea.readOnly = true; if (index % 2 === 0) { textarea.classList.add('chat-question'); } else { textarea.classList.add('chat-answer'); } textarea.dataset.timestamp = new Date().toISOString().split('T')[0]; chatContainer.appendChild(textarea); adjustTextareaRows(textarea); chatContainer.scrollTop = chatContainer.scrollHeight; }); } } catch (error) { console.error('Error:', error); } } window.addEventListener("beforeunload", () => { if (chat == true && changesToChat == true) { // Select all <textarea> elements inside the chat-div const textareas = document.querySelectorAll("#chat-div textarea"); // Collect the values into an array const messages = Array.from(textareas).reduce((acc, textarea) => { const timestamp = textarea.dataset.timestamp || new Date().toISOString(); // If the timestamp already exists, push the text to the array if (acc[timestamp]) { acc[timestamp].push(textarea.value); } else { // Otherwise, create a new array with the text acc[timestamp] = [textarea.value]; } return acc; }, {}); const payload = JSON.stringify({ cve_id: "CVE-2025-66024", user_id: userId, messages: messages }); fetch('/cve_report_blueprint/save_chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload, keepalive: true }); } }); const epssScoreElem = document.getElementById('epss_score'); const epssPercentileElem = document.getElementById('epss_percentile'); if (epssScoreElem && epssPercentileElem) { // Parse EPSS values safely let epssScore = parseFloat("0.36%"); let epssPercentile = parseFloat("29.50%"); // Function to truncate values while keeping them numbers function formatEPSS(value) { return isNaN(value) ? "N/A" : parseFloat(value.toFixed(2)).toString().slice(0, 4); } // Assign properly truncated text content epssScoreElem.textContent = formatEPSS(epssScore); epssPercentileElem.textContent = formatEPSS(epssPercentile); // Ensure correct numeric values for class assignment (range 0-10) const epssScoreClass = isNaN(epssScore) ? 'epss_score_na' : `score_${Math.round(epssScore / 10)}`; const epssPercentileClass = isNaN(epssPercentile) ? 'epss_score_na' : `score_${Math.round(epssPercentile / 10)}`; // Apply background color classes epssScoreElem.classList.add("cvssbox", epssScoreClass); epssPercentileElem.classList.add("cvssbox", epssPercentileClass); // Function to check if a color is light or dark function isLightColor(element) { const bgColor = window.getComputedStyle(element).backgroundColor; const rgb = bgColor.match(/\d+/g); // Extract RGB values if (!rgb) return false; // Fallback if color cannot be determined const [r, g, b] = rgb.map(Number); const brightness = (r * 299 + g * 587 + b * 114) / 1000; // Luminance formula return brightness > 150; // Threshold for determining if color is light } // Apply text color based on background brightness function adjustTextColor(element) { if (isLightColor(element)) { element.style.color = "#17254D"; // Dark blue text for light backgrounds } else { element.style.color = "#FFFFFF"; // White text for dark backgrounds } } // Adjust text colors dynamically setTimeout(() => { adjustTextColor(epssScoreElem); adjustTextColor(epssPercentileElem); }, 100); // Timeout ensures colors are applied after rendering } // Update character count inside the input field function updateCharCount() { const input = document.getElementById("question-input"); const charCount = document.getElementById("char-count"); charCount.textContent = `${input.value.length}/70`; } function getClassForScore(score) { return 'score_' + Math.floor(score); } function colorCVSSBoxes() { $('#cvss-table tbody tr').each(function() { var cvssCell = $(this).find('td').eq(1); // Assuming CVSS score is in the second column var cvssValue = parseFloat(cvssCell.text()); if (!isNaN(cvssValue)) { var scoreClass = getClassForScore(cvssValue); var cvssSpan = $('<span>').addClass('cvssbox ' + scoreClass).text(cvssCell.text()); cvssCell.html(cvssSpan); // Replace the cell content with the styled span } }); } async function getCWEName(cwe_id) { fetch('/cve_report_blueprint/cwe_name', { method: 'GET', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ cwe: cwe_id}) }) .then(response => response.json()) .then(data => { var name = data; return name; }) .catch(error => console.error('Error:', error)); } function addCvssTab(cvssContainer, version, data) { function parseCvssVector(vector, version) { if (!vector) return {}; const parts = vector.split('/'); const mappings = {}; parts.forEach(part => { const [key, value] = part.split(':'); mappings[key] = value; }); if (!vector.startsWith("CVSS:")) version = "2.0"; const descriptions = { "AV": { "N": "4", "A": "3", "L": "2", "P": "1" }, "AC": { "L": "4", "H": "1" }, "Au": { "N": "1", "S": "2", "M": "3" }, "PR": { "N": "4", "L": "3", "H": "1" }, "UI": { "N": "4", "R": "2", "P": "1" }, "S": { "U": "1", "C": "4" }, "C": { "N": "1", "L": "3", "H": "4" }, "I": { "N": "1", "L": "3", "H": "4" }, "A": { "N": "1", "L": "3", "H": "4" } }; if (parseFloat(version) >= 4.0) { Object.assign(descriptions, { "SC": { "N": "1", "H": "4", "L": "3" }, "SI": { "N": "1", "H": "4", "L": "3" }, "SA": { "N": "1", "H": "4", "L": "3" }, "AT": { "N": "1", "A": "4" } }); } const result = {}; Object.keys(mappings).forEach(key => { let translatedValue = descriptions[key]?.[mappings[key]]; if (translatedValue === undefined || translatedValue === "X" || translatedValue === "") { translatedValue = "1"; } result[key] = translatedValue; }); if (version === "2.0") { result["Authentication"] = result["Au"]; } return result; } const cvssValues = parseCvssVector(data.vector, version); const div = document.createElement('div'); div.id = 'version-' + version; div.className = 'content cvss-tab row col-12 w-100'; const baseScore = data.cvss; const baseScoreClass = `score_${Math.round(baseScore)}`; const exploitabilityScore = data.exploitability_score; const exploitabilityScoreClass = `score_${Math.round(exploitabilityScore)}`; const impactScoreClass = `score_${Math.round(data.impact_score)}`; const fields = [ ["Base Score:", `<span class="cvssbox ${baseScoreClass}">${baseScore}</span>`] ]; if (!version.startsWith("2")) { fields.push(["Base Severity:", data.base_severity]); } if (parseFloat(version) < 4.0) { fields.push( ["Exploitability Score:", `<span class="cvssbox ${exploitabilityScoreClass}">${exploitabilityScore}</span>`], ["Impact Score:", `<span class="cvssbox ${impactScoreClass}">${data.impact_score}</span>`] ); } let radarLabels, radarData; if (version.startsWith("4")) { radarLabels = ["Attack Vector", "Attack Complexity", "User Interaction", "Scope", "Security Availability Impact", "Security Integrity Impact", "Security Control Impact"]; radarData = [cvssValues.AV, cvssValues.AC, cvssValues.UI, cvssValues.S, cvssValues.SA, cvssValues.SI, cvssValues.SC]; } else if (version.startsWith("3")) { radarLabels = ["Attack Vector", "Attack Complexity", "Privileges Required", "User Interaction", "Scope", "Confidentiality", "Integrity", "Availability"]; radarData = [cvssValues.AV, cvssValues.AC, cvssValues.PR, cvssValues.UI, cvssValues.S, cvssValues.C, cvssValues.I, cvssValues.A]; } else if (version.startsWith("2")) { radarLabels = ["Attack Vector", "Attack Complexity", "Authentication", "Confidentiality", "Integrity", "Availability"]; radarData = [cvssValues.AV, cvssValues.AC, cvssValues.Authentication, cvssValues.C, cvssValues.I, cvssValues.A]; } function getScoreColor(score) { let scoreClass = `score_${Math.round(score)}`; let tempElement = document.createElement('div'); tempElement.className = `cvssbox ${scoreClass}`; document.body.appendChild(tempElement); let bgColor = window.getComputedStyle(tempElement).backgroundColor; document.body.removeChild(tempElement); return bgColor; } const radarChartColor = getScoreColor(baseScore); const content = ` <div class="col-md-12 px-0 mx-0"> <div class="cvss-fields-container"> ${fields.map(([label, value]) => ` <div class="cvss-field-block"> <div>${label}</div> <div>${value}</div> </div> `).join('')} </div> <div class="cvss-chart-wrap"> <canvas id="radarChart-${version}"></canvas> </div> </div> `; div.innerHTML = content; cvssContainer.appendChild(div); const ctx = document.getElementById(`radarChart-${version}`).getContext('2d'); new Chart(ctx, { type: 'radar', data: { labels: radarLabels, datasets: [{ data: radarData, backgroundColor: radarChartColor.replace("rgb", "rgba").replace(")", ", 0.2)"), borderColor: radarChartColor, borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { enabled: false, external: externalTooltipHandler } }, scales: { r: { beginAtZero: true, ticks: { display: false, stepSize: 1 }, suggestedMin: 0, suggestedMax: 4, pointLabels: { font: { size: 10 }, padding: 6, callback: function(label) { const maxLen = 14; if (label.length <= maxLen) return label; const words = label.split(' '); const lines = []; let current = ''; for (const word of words) { if ((current + ' ' + word).trim().length > maxLen) { if (current) lines.push(current); current = word; } else { current = (current + ' ' + word).trim(); } } if (current) lines.push(current); return lines; } } } }, layout: { padding: { top: 4, bottom: 4, left: 4, right: 4 } }, elements: { line: { borderWidth: 2 } } } }); } function externalTooltipHandler(context) { const cvssMappings = { AV: { 4: "N", 3: "A", 2: "L", 1: "P" }, AC: { 4: "L", 1: "H" }, PR: { 4: "N", 3: "L", 1: "H" }, UI: { 4: "N", 2: "R" }, S: { 1: "U", 4: "C" }, C: { 1: "N", 3: "L", 4: "H" }, I: { 1: "N", 3: "L", 4: "H" }, A: { 1: "N", 3: "L", 4: "H" }, SC: { 1: "N", 3: "L", 4: "H" }, // CVSS 4.0 - Security Control Impact SI: { 1: "N", 3: "L", 4: "H" }, // CVSS 4.0 - Security Integrity Impact SA: { 1: "N", 3: "L", 4: "H" } // CVSS 4.0 - Security Availability Impact }; const labelMappings = { "Attack Vector": "AV", "Attack Complexity": "AC", "Privileges Required": "PR", "User Interaction": "UI", "Scope": "S", "Confidentiality": "C", "Integrity": "I", "Availability": "A", "Security Control Impact": "SC", "Security Integrity Impact": "SI", "Security Availability Impact": "SA" }; const labelExplanation = { "Attack Vector": "How the vulnerability is exploited", "Attack Complexity": "How difficult the attack is to execute", "Privileges Required": "Level of permissions needed to exploit", "User Interaction": "Does user action play a role?", "Scope": "Does exploitation affect other components beyond the vulnerable system?", "Confidentiality": "Effect on data confidentiality", "Integrity": "Effect on data integrity", "Availability": "Effect on system availability", "Security Control Impact": "Impact on security mechanisms (e.g., authentication, logging)", "Security Integrity Impact": "Impact on system integrity controls", "Security Availability Impact": "Impact on system availability mechanisms" }; const explanations = { AV: { N: "Network - Exploitable over the internet", A: "Adjacent - Requires local network access (e.g., Wi-Fi, Bluetooth)", L: "Local - Attacker needs access to the system", P: "Physical - Requires physical access to the machine" }, AC: { L: "Low - No special conditions required; easy to exploit", H: "High - Requires extra steps (e.g., bypassing ASLR, social engineering)" }, PR: { N: "None - No privileges needed; attacker can execute immediately", L: "Low - Requires user privileges (e.g., standard user account)", H: "High - Requires administrative/root privileges" }, UI: { N: "None - No user interaction needed; attack is automatic", R: "Required - Needs user interaction (e.g., opening a malicious email)" }, S: { U: "Unchanged - Impact is limited to the vulnerable component", C: "Changed - Exploitation extends to other components (e.g., container escape, privilege escalation)" }, C: { N: "None - No data exposure", L: "Low - Partial exposure of sensitive data", H: "High - Complete loss of confidentiality (e.g., database leak)" }, I: { N: "None - No modification of data", L: "Low - Some data corruption but does not compromise the system", H: "High - Full data integrity loss (e.g., modifying financial records)" }, A: { N: "None - No impact on system availability", L: "Low - Degradation in performance but system remains functional", H: "High - Complete denial of service (DoS)" }, SC: { N: "None - No effect on security controls", L: "Low - Some impact on security mechanisms (e.g., logging disruption)", H: "High - Significant security mechanism bypass (e.g., disabling authentication)" }, SI: { N: "None - No impact on system integrity controls", L: "Low - Some loss of integrity (e.g., partial system corruption)", H: "High - Complete integrity compromise (e.g., total system takeover)" }, SA: { N: "None - No effect on availability mechanisms", L: "Low - Some impact on system reliability", H: "High - System availability heavily compromised" } }; const riskMapping = { AV: { N: "Critical", A: "High", L: "Medium", P: "Low" }, AC: { L: "High", H: "Low" }, PR: { N: "Critical", L: "Medium", H: "Low" }, UI: { N: "High", R: "Medium" }, S: { U: "Low", C: "Critical" }, C: { N: "Low", L: "Medium", H: "Critical" }, I: { N: "Low", L: "Medium", H: "Critical" }, A: { N: "Low", L: "Medium", H: "Critical" }, SC: { N: "Low", L: "Medium", H: "Critical" }, SI: { N: "Low", L: "Medium", H: "Critical" }, SA: { N: "Low", L: "Medium", H: "Critical" } }; const tooltipModel = context.tooltip; if (!tooltipModel || tooltipModel.opacity === 0) { const tooltipEl = document.getElementById('custom-tooltip'); if (tooltipEl) { tooltipEl.style.opacity = '0'; } return; } let tooltipEl = document.getElementById('custom-tooltip'); if (!tooltipEl) { tooltipEl = document.createElement('div'); tooltipEl.id = 'custom-tooltip'; tooltipEl.className = 'custom-tooltip-class'; document.body.appendChild(tooltipEl); } const label = tooltipModel.dataPoints[0].label; const value = tooltipModel.dataPoints[0].raw; const labelAbr = labelMappings[label]; const labelExp = labelExplanation[label]; const valueMap = cvssMappings[labelAbr]?.[value] || ""; const explanation = explanations[labelAbr]?.[valueMap] || "No data available"; const risk = riskMapping[labelAbr]?.[valueMap] || ""; const riskToScoreMapping = { "Critical": 10, "High": 10, "Medium": 6, "Low": 4 }; const riskLabelClass = "score_" + (riskToScoreMapping[risk] || "0"); tooltipEl.innerHTML = ` <div class="my-1 py-1"> <strong>${labelExp}</strong><br> </div> <div class="my-1 py-1"> <strong>Value:</strong> ${explanation}<br> </div> <div class="my-1 py-1"> <span class="${riskLabelClass} p-1" style="border-radius: 6px;"> ${risk} Risk </span> </div> `; const position = context.chart.canvas.getBoundingClientRect(); tooltipEl.style.opacity = '1'; tooltipEl.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px'; tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px'; } // Function to create a label if there is only one CVSS version function addLabelAndDiv(version, cvss_version, cvss, base_severity, vector, exploitability_score, impact_score) { const buttonContainer = document.getElementById('button-container'); const cvssContainer = document.getElementById('cvss-container'); const data = { version, cvss_version, cvss, base_severity, vector, exploitability_score, impact_score }; // Create the label (styled like a button) const label = document.createElement('span'); label.className = 'btn bento-button mx-0 my-0 center-text no-click'; // Same styling as a button label.textContent = 'Version ' + version; label.style.padding = '0px 12px'; label.style.marginLeft = '0'; label.style.alignSelf = 'center'; label.style.display = 'flex'; label.style.alignItems = 'center'; label.style.maxWidth = '100px'; buttonContainer.appendChild(label); addCvssTab(cvssContainer, version, data); } function addButtonAndDiv(version, cvss_version, cvss, base_severity, vector, exploitability_score, impact_score, isFirst) { const data = { version, cvss_version, cvss, base_severity, vector, exploitability_score, impact_score }; const buttonContainer = document.getElementById('button-container'); const cvssContainer = document.getElementById('cvss-container'); // Create the button const button = document.createElement('button'); button.className = 'btn bento-button mx-0 my-0'; button.textContent = 'Version ' + version; const buttonColumn = document.createElement('div'); buttonColumn.className = 'button-col col-12 col-md-4 mx-0 my-0'; // Store the button data in an array if (!buttonContainer.buttons) { buttonContainer.buttons = []; } buttonContainer.appendChild(button); // Add content div for the version addCvssTab(cvssContainer, version, data); buttonContainer.buttons.push({ version: parseFloat(version), button }); // Sort the buttons based on version numbers buttonContainer.buttons.sort((a, b) => b.version - a.version); // Sort descending // Clear the existing buttons in the container buttonContainer.innerHTML = ''; // Add the sorted buttons back to the container buttonContainer.buttons.forEach((btnObj, index) => { buttonContainer.appendChild(btnObj.button); // Highlight the button for the highest version if (index === 0) { btnObj.button.classList.add('active-button'); var content = document.getElementById(`version-${btnObj.version.toFixed(1)}`); if(content) { content.style.display = 'block'; } } else { btnObj.button.classList.remove('active-button'); var content = document.getElementById(`version-${btnObj.version.toFixed(1)}`); if(content) { content.style.display = 'none'; } } }); // Add event listener to button button.addEventListener('click', function () { showContent(button, 'version-' + version); }); } function showContent(button, version) { const buttons = document.querySelectorAll('.bento-button'); buttons.forEach(btn => btn.classList.remove('active-button')); // Add 'active-button' class to the clicked button button.classList.add('active-button'); // Hide all content divs const contentDivs = document.querySelectorAll('.content'); contentDivs.forEach(div => { if(div.id === version) { div.style.display = 'block'; } else { div.style.display = 'none'; } }); } // Function to dynamically adjust textarea rows function adjustTextareaRows(textarea) { const MAX_PX = 160; // must match CSS max-height textarea.style.height = 'auto'; const next = Math.min(textarea.scrollHeight, MAX_PX); textarea.style.height = next + 'px'; // only show an internal scrollbar when needed textarea.style.overflowY = (textarea.scrollHeight > MAX_PX) ? 'auto' : 'hidden'; } // Function to simulate typing effect with dynamic resizing function typeText(text, element, speed = 50) { let index = 0; var chatDiv = element.parentElement; function type() { if (index < text.length) { element.value += text.charAt(index); index++; adjustTextareaRows(element); // Adjust rows dynamically as text is typed setTimeout(type, speed); // Call the function again with a delay chatDiv.scrollTop = chatDiv.scrollHeight; } } // Start typing type(); } // Modified submitQuestion function function submitQuestion() { const askButton = document.getElementById('submit-question-button'); const questionInput = document.getElementById('question-input'); const charCount = document.getElementById('char-count'); const chatDiv = document.getElementById('chat-div'); const questionString = questionInput.value.trim(); charCount.textContent = "0/70"; questionInput.value = ""; const questionTextarea = document.createElement("textarea"); questionTextarea.dataset.timestamp = new Date().toISOString().split('T')[0]; questionTextarea.readOnly = true; // Make it read-only questionTextarea.classList.add('chat-question'); questionTextarea.value = questionString; chatDiv.appendChild(questionTextarea); adjustTextareaRows(questionTextarea); const answerTextarea = document.createElement("textarea"); answerTextarea.dataset.timestamp = new Date().toISOString().split('T')[0]; answerTextarea.readOnly = true; // Make it read-only answerTextarea.classList.add('chat-answer'); chatDiv.appendChild(answerTextarea); // Function to animate the dots let dots = 0; const maxDots = 3; // Maximum number of dots function animateDots() { dots = (dots + 1) % (maxDots + 1); // Cycle dots between 0 and maxDots answerTextarea.value = "Loading" + ".".repeat(dots); } // Start the animation (update every 500ms) const loadingInterval = setInterval(animateDots, 500); changesToChat = true; const cveIdElem = document.getElementById('cve_id'); const cveId = cveIdElem ? cveIdElem.textContent : null; if (!cveId) { alert('CVE ID not found'); return; } fetch('/cve_report_blueprint/ask_question', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ cve: cveId, question: questionString }) }) .then(response => response.json()) .then(data => { clearInterval(loadingInterval); // Stop the animation answerTextarea.value = ""; // Show the response text area and simulate typing effect typeText(data.answer, answerTextarea, 30); }) .catch(error => { console.error('Error:', error); }); chatDiv.scrollTop = chatDiv.scrollHeight; } // Function to dynamically adjust textarea rows function adjustTextareaRows(textarea) { textarea.rows = 1; const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight); const rows = Math.floor(textarea.scrollHeight / lineHeight); textarea.rows = rows; } function handleClick(feedbackType, clickedButton) { const feedbackContainer = clickedButton.closest('.bf-ai-feedback'); const detailsElement = clickedButton.closest('details'); if (!feedbackContainer || !detailsElement) { console.error('Feedback container or details element not found.'); return; } const likeButton = feedbackContainer.querySelector('.feedback-button .like-count')?.closest('.feedback-button'); const dislikeButton = feedbackContainer.querySelector('.feedback-button .dislike-count')?.closest('.feedback-button'); if (!likeButton || !dislikeButton) { console.error('Like or dislike button not found.'); return; } const likeCountElement = likeButton.querySelector('.like-count'); const dislikeCountElement = dislikeButton.querySelector('.dislike-count'); const question = detailsElement.dataset.question || likeCountElement.dataset.question || ''; const questionKey = detailsElement.dataset.questionKey || likeCountElement.dataset.questionKey || ''; let likeCount = parseInt(likeCountElement.textContent, 10) || 0; let dislikeCount = parseInt(dislikeCountElement.textContent, 10) || 0; let changes = "none"; if (feedbackType === 'like') { if (!likeButton.classList.contains('selected')) { likeCount++; if (dislikeButton.classList.contains('selected')) { changes = "change"; dislikeCount = Math.max(0, dislikeCount - 1); dislikeButton.classList.remove('selected'); } likeButton.classList.add('selected'); } else { changes = "remove"; likeCount = Math.max(0, likeCount - 1); likeButton.classList.remove('selected'); } } if (feedbackType === 'dislike') { if (!dislikeButton.classList.contains('selected')) { dislikeCount++; if (likeButton.classList.contains('selected')) { changes = "change"; likeCount = Math.max(0, likeCount - 1); likeButton.classList.remove('selected'); } dislikeButton.classList.add('selected'); } else { changes = "remove"; dislikeCount = Math.max(0, dislikeCount - 1); dislikeButton.classList.remove('selected'); } } updateButtonStyles(likeButton, dislikeButton); likeCountElement.textContent = likeCount; dislikeCountElement.textContent = dislikeCount; const cveIdElem = document.getElementById('cve_id'); const cveId = cveIdElem ? cveIdElem.textContent.trim() : ""; fetch('/cve_report_blueprint/record_user_feedback', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ cveIdentifier: cveId, question: question, questionKey: questionKey, newFeedback: feedbackType, changes: changes }), }) .then(response => { if (!response.ok) { throw new Error('Error ' + response.statusText); } }) .catch(error => console.error('Error:', error)); } // Helper function to update button styles function updateButtonStyles(likeButton, dislikeButton) { // Adjust styles based on selection if (likeButton.classList.contains('selected')) { likeButton.style.transform = 'scale(1.2)'; // Enlarge selected button likeButton.style.opacity = '1'; dislikeButton.style.transform = 'scale(0.8)'; // Shrink unselected button dislikeButton.style.opacity = '0.5'; // Grey out unselected button } else if (dislikeButton.classList.contains('selected')) { dislikeButton.style.transform = 'scale(1.2)'; dislikeButton.style.opacity = '1'; likeButton.style.transform = 'scale(0.8)'; likeButton.style.opacity = '0.5'; } else { // no button is selected dislikeButton.style.transform = 'scale(1)'; dislikeButton.style.opacity = '1'; likeButton.style.transform = 'scale(1)'; likeButton.style.opacity = '1'; } } // Activate the submit button if there is text in the input box document.getElementById('question-input').addEventListener('keydown', function(event) { var questionButton = document.getElementById('submit-question-button'); var inputFieldValue = document.getElementById('question-input').value; // Get the input field value if (inputFieldValue.trim().length > 0) { // Check if the input field is not empty questionButton.style.color = "white"; questionButton.style.backgroundColor = "#2C5E7D"; } else { // If the input field is empty questionButton.style.color = ""; // Reset color to initial value questionButton.style.backgroundColor = "#ADB5BD"; // Reset background color to initial value } }); // retrieve like and dislike counts document.addEventListener('DOMContentLoaded', function () { var cveIdElem = document.getElementById('cve_id'); var cveId = cveIdElem ? cveIdElem.textContent.trim() : ""; function normalizeQuestion(text) { if (!text) return ""; return String(text) .normalize("NFKC") .replace(/\u00A0/g, " ") .replace(/\u200B/g, "") .replace(/\s+/g, " ") .trim(); } function setLikeDislikeCounts() { fetch('/cve_report_blueprint/like_dislike_count', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ cveIdentifier: cveId }), }) .then(response => response.json()) .then(data => { if (typeof data === 'string') { data = JSON.parse(data); } const likeElements = document.querySelectorAll('.like-count'); const dislikeElements = document.querySelectorAll('.dislike-count'); Object.keys(data).forEach(question => { const normalizedQuestion = normalizeQuestion(question); const likeCount = data[question]['like-count']; const dislikeCount = data[question]['dislike-count']; likeElements.forEach(el => { if (normalizeQuestion(el.dataset.question) === normalizedQuestion) { el.textContent = likeCount; } }); dislikeElements.forEach(el => { if (normalizeQuestion(el.dataset.question) === normalizedQuestion) { el.textContent = dislikeCount; } }); }); }) .catch(error => { console.error('Error fetching like and dislike counts:', error); }); } setLikeDislikeCounts(); }); document.addEventListener("DOMContentLoaded", function() { var qaBento = document.getElementById("qa-bento"); if (!qaBento) return; var details = qaBento.getElementsByTagName("details"); if (details.length > 0) { details[0].setAttribute("open", "open"); } }); document.addEventListener("DOMContentLoaded", function() { const askButton = document.getElementById('submit-question-button'); const questionInput = document.getElementById('question-input'); const placeholderText = "Explain this CVE in greater detail please"; // The placeholder text // Initially disable the ask button, but keep the blue color askButton.disabled = true; askButton.style.backgroundColor = "var(--Color-Gray-Gray-500, #ADB5BD)"; // Enable the ask button if the input is not the placeholder questionInput.addEventListener('input', function() { if (questionInput.value.trim() !== "" && questionInput.value.trim() !== placeholderText) { askButton.disabled = false; // Enable the button askButton.style.backgroundColor = "var(--Button-bg-primary, #2C5E7D)"; } else { askButton.disabled = true; // Disable the button again if it's empty or contains only the placeholder askButton.style.backgroundColor = "var(--Color-Gray-Gray-500, #ADB5BD)"; } }); }); document.getElementById('question-input').addEventListener('focus', function() { // Re-enable the input field when it is focused this.disabled = false; }); document.addEventListener("DOMContentLoaded", function() { var badge = document.getElementById('postCategoryBadge'); if (!badge) { // no badge on this page, nothing to do return; } var category = badge.getAttribute('data-category'); switch (category) { case 'WARNING': badge.classList.add('badge-warning'); break; case 'TIPS': badge.classList.add('badge-success'); break; default: badge.classList.add('badge-info'); } }); document.addEventListener("DOMContentLoaded", function() { const cweButton = document.getElementById("cwe-button"); const kevButton = document.getElementById("kev-button"); const cweTab = document.getElementById("cwe-tab"); const kevTab = document.getElementById("kev-tab"); // When CWE is clicked cweButton.addEventListener("click", function() { // Activate CWE: update its inner styles to active state (white background, bold text using Inter) cweButton.firstElementChild.style.background = "white"; cweButton.firstElementChild.style.boxShadow = "0px 2px 6px rgba(0, 0, 0, 0.25)"; cweButton.firstElementChild.style.borderRadius = "8px"; cweButton.firstElementChild.querySelector("div").style.fontFamily = "Inter"; cweButton.firstElementChild.querySelector("div").style.fontWeight = "700"; // Deactivate KEV: set to inactive state (grey background, normal weight using Open Sans) kevButton.firstElementChild.style.background = "var(--Color-Gray-Gray-100, #F6F9FC)"; kevButton.firstElementChild.style.boxShadow = "0px 2px 5.5px rgba(0, 0, 0, 0.06)"; kevButton.firstElementChild.querySelector("div").style.fontFamily = "Open Sans"; kevButton.firstElementChild.querySelector("div").style.fontWeight = "400"; // Toggle content panels cweTab.style.display = "block"; kevTab.style.display = "none"; }); // When KEV is clicked kevButton.addEventListener("click", function() { // Activate KEV: update its inner styles to active state kevButton.firstElementChild.style.background = "white"; kevButton.firstElementChild.style.boxShadow = "0px 2px 6px rgba(0, 0, 0, 0.25)"; kevButton.firstElementChild.style.borderRadius = "8px"; kevButton.firstElementChild.querySelector("div").style.fontFamily = "Inter"; kevButton.firstElementChild.querySelector("div").style.fontWeight = "700"; // Deactivate CWE: revert its styling to inactive state cweButton.firstElementChild.style.background = "var(--Color-Gray-Gray-100, #F6F9FC)"; cweButton.firstElementChild.style.boxShadow = "0px 2px 5.5px rgba(0, 0, 0, 0.06)"; cweButton.firstElementChild.querySelector("div").style.fontFamily = "Open Sans"; cweButton.firstElementChild.querySelector("div").style.fontWeight = "400"; // Toggle content panels kevTab.style.display = "block"; cweTab.style.display = "none"; }); }); async function loadEPSS(cveId) { try { const res = await fetch("/get_epss", { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify({ cve: cveId }) }); const { score, percentile } = await res.json(); // Update your boxes (adjust selectors to your DOM) const sEl = document.querySelector('#epss-score'); const pEl = document.querySelector('#epss-percentile'); sEl.textContent = (score ?? 'N/A'); pEl.textContent = (percentile ?? 'N/A'); } catch (e) { console.error("EPSS load failed", e); } } document.addEventListener("DOMContentLoaded", function () { const input = document.getElementById("question-input"); const fullText = "Ask your question here ..."; let index = 0; function typePlaceholder() { if (index <= fullText.length) { input.setAttribute("placeholder", fullText.substring(0, index)); index++; setTimeout(typePlaceholder, 80); // typing speed } } // small delay so users notice it setTimeout(typePlaceholder, 800); }); (function () { const pills = document.querySelectorAll('.bf-vuln-pill'); pills.forEach(pill => { const vs = pill.getAttribute('data-vs') || 'unknown'; // Prefer CVE id from template; fallback to pathname const rawPageKey = pill.getAttribute('data-cve') || window.location.pathname || 'page'; const pageKey = String(rawPageKey).replace(/[^a-zA-Z0-9_-]/g, '_'); const storageKey = `bf_vuln_pill_seen_${pageKey}_${vs}`; // If already seen for THIS CVE page, hide dot if (localStorage.getItem(storageKey) === '1') { pill.classList.add('bf-vuln-pill--seen'); } pill.addEventListener('mouseenter', () => { if (!pill.classList.contains('bf-vuln-pill--seen')) { pill.classList.add('bf-vuln-pill--seen'); try { localStorage.setItem(storageKey, '1'); } catch (e) {} } }, { once: true }); }); })(); document.addEventListener("DOMContentLoaded", function () { const input = document.getElementById("cpe-search-input"); const table = document.getElementById("table-affected-vendors-products-expanded"); if (!input || !table) return; const rows = table.querySelectorAll("tbody tr"); const countElem = document.getElementById("cpe-count"); const total = rows.length; input.addEventListener("input", function () { const query = input.value.toLowerCase().trim(); let visibleCount = 0; rows.forEach(row => { const vendor = row.dataset.vendor?.toLowerCase() || ""; const product = row.dataset.product?.toLowerCase() || ""; const version = row.dataset.version?.toLowerCase() || ""; const match = vendor.includes(query) || product.includes(query) || version.includes(query); if (match) { row.style.display = ""; visibleCount++; } else { row.style.display = "none"; } }); // Update count dynamically if (countElem) { countElem.textContent = query ? `${visibleCount} / ${total}` : total; } }); }); </script> <script> document.addEventListener("DOMContentLoaded", function () { const qaContainer = document.getElementById("qa-bento"); if (!qaContainer) return; const QUESTION_CONFIG = [ { match: "What immediate steps should I take to mitigate this vulnerability?", display: "What immediate steps should I take to mitigate this vulnerability?", order: 2 }, { match: "How can this vulnerability be detected on my network or system? Can you suggest some commands?", display: "How can this vulnerability be detected on my network or system?", order: 3 }, { match: "Can you explain this vulnerability to me?", display: "Can you explain this vulnerability to me?", order: 1 }, { match: "How can this vulnerability impact me? :", display: "How can this vulnerability impact me?", order: 4 }, { match: "How does this vulnerability affect compliance with common standards and regulations (like GDPR, HIPAA)?:", display: "How does this vulnerability affect compliance?", order: 5 } ]; const detailsElements = Array.from(qaContainer.querySelectorAll("details")); function normalizeText(text) { return (text || "") .replace(/\s+/g, " ") .replace(/\s+:\s*$/, ":") .trim(); } function findQuestionTextNode(summary) { for (const node of summary.childNodes) { if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== "") { return node; } } return null; } const mappedItems = detailsElements.map((details, originalIndex) => { const summary = details.querySelector("summary"); if (!summary) { return { details, originalIndex, order: 999 }; } const fullQuestion = normalizeText(summary.textContent); const config = QUESTION_CONFIG.find(item => normalizeText(item.match) === fullQuestion); if (config) { const textNode = findQuestionTextNode(summary); if (textNode) { textNode.textContent = config.display + " "; } else { summary.prepend(document.createTextNode(config.display + " ")); } return { details, originalIndex, order: config.order }; } return { details, originalIndex, order: 999 }; }); mappedItems .sort((a, b) => { if (a.order !== b.order) return a.order - b.order; return a.originalIndex - b.originalIndex; }) .forEach(item => qaContainer.appendChild(item.details)); }); </script> </main> <!-- Bottom fixed sidebar Only for mobile --> <!-- Bottom fixed sidebar Only for mobile --> <nav id="navbarMobile" class="navbarMobile navbar shadow-none hidden-md hidden-lg" data-scroll="true"> <ul id="navbarMobileList" class="navbar-nav d-flex justify-content-between"> <!-- Content 1: Public Content --> <li class="nav-item" id="nav_item_home"> <a href="/home" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center"> <span class="iconify sidebar-icon" data-icon="carbon:home"></span> </div> <span class="nav-link-text">Homepage</span> </a> </li> <li class="nav-item" id="nav_item_reports"> <a href="/cve_reports" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center" style="width: 44px"> <span class="iconify sidebar-icon" data-icon="iconoir:reports"></span> </div> <span class="nav-link-text">CVE Reports</span> </a> </li> <li class="nav-item" id="nav_item_vulnerabilities"> <a href="/vulnerabilities" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center" style="width: 70px"> <span class="iconify sidebar-icon" data-icon="fluent:vehicle-truck-cube-20-regular"></span> </div> <span class="nav-link-text">Vendors & Products</span> </a> </li> <li class="nav-item" id="nav_item_news"> <a href="/news" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center"> <span class="iconify sidebar-icon" data-icon="tabler:news"></span> </div> <span class="nav-link-text">News</span> </a> </li> <!-- Content 2: Protected Content --> <li class="nav-item" id="nav_item_more" style="min-width: 32px !important;"> <div class="dropup d-xl-block"> <!-- Menu button --> <a href="#" class="nav-link" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center" style="width: 32px"> <span class="iconify sidebar-icon" data-icon="uiw:more"></span> </div> <span class="nav-link-text">More</span> </a> <!-- Menu dropdown --> <ul id="navbarMobileDropdownList" class="dropdown-menu dropdown-menu-end" style="position: absolute;"></ul> </div> </li> </ul> </nav> <div id="feedback-popup" class="feedback-popup"> <button id="close-feedback" class="close-btn" aria-label="Close feedback form">×</button> <div class="feedback-title mt-2"> Did you find what you were looking for? </div> <!-- rating --> <div class="feedback-buttons"> <button id="feedback-box-yes" class="feedback-option" data-value="yes"> <div class="feedback-box"> <img data-src="https://basefortify.eu/static/assets/img/feedback-popup/grey-feedback-yes.svg" alt="yes icon" width="128" height="128" loading="lazy" decoding="async" inert> </div> </button> <button id="feedback-box-neutral" class="feedback-option" data-value="partially"> <div class="feedback-box"> <img data-src="https://basefortify.eu/static/assets/img/feedback-popup/grey-feedback-neutral.svg" alt="partially icon" width="128" height="128" loading="lazy" decoding="async" inert> </div> </button> <button id="feedback-box-no" class="feedback-option" data-value="no"> <div class="feedback-box"> <img data-src="https://basefortify.eu/static/assets/img/feedback-popup/grey-feedback-no.svg" alt="no icon" width="128" height="128" loading="lazy" decoding="async" inert> </div> </button> </div> <!-- comment --> <div class="feedback-comment"> <textarea id="feedback-comment" placeholder="Add a comment…" spellcheck="false"></textarea> <button id="submit-comment">Submit</button> </div> </div> <div id="thank-you-screen" class="thank-you-screen"> <img data-src="/static/assets/img/feedback-popup/thank-you.svg" width="96" height="167" alt="Thank you illustration" loading="lazy" decoding="async" inert> <div class="thank-you-text"> <div class="feedback-title">Thank you for your feedback!</div> <div class="subtitle"> Your input helps us improve and create a better experience for you. We appreciate your time! </div> </div> </div> <!-- Keep only ONE Hotjar snippet on the page (keep this one, delete head copy) --> <script> (function(h,o,t,j,a,r){ h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)}; h._hjSettings={hjid:6423743,hjsv:6}; a=o.getElementsByTagName('head')[0]; r=o.createElement('script'); r.async=1; r.crossOrigin='anonymous'; r.src='https://static.hotjar.com/c/hotjar-'+h._hjSettings.hjid+'.js?sv='+h._hjSettings.hjsv; a.appendChild(r); })(window,document); </script> <script id="hs-script-loader" async defer src="https://js-eu1.hs-scripts.com/27242826.js" crossorigin="anonymous"></script> <script> document.addEventListener('DOMContentLoaded', function () { try { loadSideBar && loadSideBar(); } catch(e) { console.warn(e); } try { highlightMenuItem && highlightMenuItem(); } catch(e) { console.warn(e); } }); function highlightMenuItem() { // Get the current page URL const currentPage = window.location.pathname; // Find the nav-link that matches the current URL document.querySelectorAll('.sidenav .nav-item .nav-link').forEach(function(link) { if (link.getAttribute('href') === currentPage) { // Add 'active' class to its parent nav-item link.parentElement.classList.add('active'); } }); // Find the nav-link that matches the current URL document.querySelectorAll('.navbarMobile .nav-item .nav-link').forEach(function(link) { if (link.getAttribute('href') === window.location.pathname) { // Add 'active' class to its parent nav-item link.parentElement.classList.add('active'); } }); } </script> <script> function toggleAccordion() { const elementIds = [ "welcomeBannerSubtitle", "welcomeBannerFeaturesList", "welcomeBannerRegisterButton" ]; // Get the button's icon element const buttonIcon = document.getElementById("welcomeBannerToggle"); // Determine if elements are currently expanded or collapsed const isHidden = document.getElementById(elementIds[0]).classList.contains("hidden-element"); // Toggle the .hidden-element class on each target element elementIds.forEach(id => { const element = document.getElementById(id); element.classList.toggle("hidden-element"); }); // Toggle the icon between up and down buttonIcon.innerHTML = isHidden ? '<span class="iconify" data-icon="iconamoon:arrow-up-2-bold"></span>' : '<span class="iconify" data-icon="iconamoon:arrow-down-2-bold"></span>'; // Set min-height when visible document.querySelector('.welcome-bg-card.page-header').classList.toggle("min-vh-35"); } </script> <script src="/static/js/feedback-popup.js" defer></script> <!-- JavaScript --> <script> function initializeSearchBox() { var searchInput = document.getElementById('searchInput'); var clearButton = document.querySelector('.clear-btn'); // Only if there is a search box if (searchInput && clearButton) { // Hide the clear button initially clearButton.style.display = 'none'; // Listen for input events searchInput.addEventListener('input', function () { if (this.value.trim().length > 0) { clearButton.style.display = 'block'; } else { clearButton.style.display = 'none'; } }); } } document.addEventListener("DOMContentLoaded", function () { // Initialize search box visibility initializeSearchBox(); }); </script> <script> function loadSideBar() { var manageMyComponentsUrl; var manageOsintSourcesUrl; var manageCVES; var manageCanonicalNames; var manageNewsArticles; var manageAllComponents; var manageAllCPEs; var manageEpss; var nvdFeeds; var manageCwe; var manageAssigners; var manageReleaseScraper; var logout; // Fetch URLs from the server fetch('/cve_report_blueprint/get_flask_urls') .then(response => response.json()) .then(data => { manageMyComponentsUrl = data.manageMyComponentsUrl; manageOsintSourcesUrl = data.manageOsintSourcesUrl; manageCVES = data.manageCVES; manageCanonicalNames = data.manageCanonicalNames; manageNewsArticles = data.manageNewsArticles; manageAllComponents = data.manageAllComponents; manageAllCPEs = data.manageAllCPEs; manageEpss = data.manageEpss; nvdFeeds = data.nvdFeeds; manageCwe = data.manageCwe; manageAssigners = data.manageAssigners; manageReleaseScraper = data.manageReleaseScraper; logout = data.logout; }); const menuItemTemplate = document.createElement('li'); menuItemTemplate.className = 'nav-item'; menuItemTemplate.innerHTML = `<a id="menuItemBtn" href="/" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center"> <span id="menuItemIcon" class="iconify sidebar-icon" data-icon=""></span> </div> <span id="menuItemText" class="nav-link-text"></span> </a>`; // Check login status and dynamically load the navbar/sidebar content fetch('/cve_report_blueprint/check_login_status') .then(response => response.json()) .then(data => { // Get side menu const sidebarElement = document.getElementById('sidenav-main'); const sidebarStaticContent = document.getElementById('sideMenuContent'); const dynamicContentElement = document.getElementById('sidenav-collapse-main3'); // Get mobile static menu const navbarMobile = document.getElementById('navbarMobileList'); const navbarDropdownMobile = document.getElementById('navbarMobileDropdownList'); const topMenuMobile = document.getElementById('topMenuMobile'); const navbarDropdownMenu = document.getElementById('navbarDropdownMenu'); const userMenuItems = ` <li><a class="dropdown-item" href="/startpage">My Dashboard</a></li> <li><a class="dropdown-item" href="/manage_my_components_v2">My Components</a></li> <li><a class="dropdown-item" href="/threat_blueprint_v3/manage_my_threats_v3">My Threats</a></li> <li><a class="dropdown-item" href="/node_blueprint/manage_my_nodes">My Nodes</a></li> `; // Get menu items const menuMyDashboard = document.getElementById('sideMenuMyDashboard'); const menuMyComponents = document.getElementById('sideMenuMyComponents'); const menuMyThreats = document.getElementById('sideMenuMyThreats'); const menuMyNodes = document.getElementById('sideMenuMyNodes'); // Get dynamic footer const dynamicFooter = document.getElementById('dynamicPageFooter'); if (data.is_logged_in) { sidebarElement.classList.add('logged-in'); sidebarElement.classList.remove('not-logged-in'); // Hide welcome banner const welcomeBanner = document.getElementById('welcome-banner'); if (welcomeBanner) { welcomeBanner.remove(); } // Hide footer when user is logged in const mainFooter = document.getElementById('main-footer') if (mainFooter) { mainFooter.remove(); } // Unlock menu items menuMyDashboard.href = '/startpage'; menuMyComponents.href = '/manage_my_components_v2'; menuMyThreats.href = '/threat_blueprint_v3/manage_my_threats_v3'; menuMyNodes.href = '/node_blueprint/manage_my_nodes'; document.getElementById('sideMenuMyDashboardIcon').dataset.icon = 'material-symbols:dashboard-outline'; document.getElementById('sideMenuMyComponentsIcon').dataset.icon = 'tdesign:system-components'; document.getElementById('sideMenuMyThreatsIcon').dataset.icon = 'grommet-icons:threats'; document.getElementById('sideMenuMyNodesIcon').dataset.icon = 'clarity:nodes-line'; document.getElementById('sideMenuMyDashboard').classList.remove("nav-link-locked"); document.getElementById('sideMenuMyComponents').classList.remove("nav-link-locked"); document.getElementById('sideMenuMyThreats').classList.remove("nav-link-locked"); document.getElementById('sideMenuMyNodes').classList.remove("nav-link-locked"); // Add menu items to Mobile navbar const menuMyDashboardCloned = menuItemTemplate.cloneNode(true); menuMyDashboardCloned.querySelector('#menuItemIcon').dataset.icon = 'material-symbols:dashboard-outline'; menuMyDashboardCloned.querySelector('#menuItemText').textContent = 'My Dashboard'; menuMyDashboardCloned.querySelector('#menuItemBtn').href = '/startpage'; const menuMyComponentsCloned = menuItemTemplate.cloneNode(true); menuMyComponentsCloned.querySelector('#menuItemIcon').dataset.icon = 'tdesign:system-components'; menuMyComponentsCloned.querySelector('#menuItemText').textContent = 'My Components'; menuMyComponentsCloned.querySelector('#menuItemBtn').href = '/manage_my_components_v2'; const menuMyThreatsCloned = menuItemTemplate.cloneNode(true); menuMyThreatsCloned.querySelector('#menuItemIcon').dataset.icon = 'grommet-icons:threats'; menuMyThreatsCloned.querySelector('#menuItemText').textContent = 'My Threats'; menuMyThreatsCloned.querySelector('#menuItemBtn').href = '/threat_blueprint_v3/manage_my_threats_v3'; const menuMyNodesCloned = menuItemTemplate.cloneNode(true); menuMyNodesCloned.querySelector('#menuItemIcon').dataset.icon = 'clarity:nodes-line'; menuMyNodesCloned.querySelector('#menuItemText').textContent = 'My Nodes'; menuMyNodesCloned.querySelector('#menuItemBtn').href = '/node_blueprint/manage_my_nodes'; // Insert logged-in pages navbarMobile.insertAdjacentElement('afterbegin', menuMyNodesCloned); navbarMobile.insertAdjacentElement('afterbegin', menuMyThreatsCloned); navbarMobile.insertAdjacentElement('afterbegin', menuMyComponentsCloned); navbarMobile.insertAdjacentElement('afterbegin', menuMyDashboardCloned); // Get the elements const menuHome = document.getElementById('nav_item_home'); const menuReports = document.getElementById('nav_item_reports'); const menuVulnerabilities = document.getElementById('nav_item_vulnerabilities'); const menuNews = document.getElementById('nav_item_news'); // Append the elements to the end of the parent container navbarDropdownMobile.appendChild(menuHome); navbarDropdownMobile.appendChild(menuReports); navbarDropdownMobile.appendChild(menuVulnerabilities); navbarDropdownMobile.appendChild(menuNews); // Remove login item form mobile navbar topMenuMobile.querySelector('#navbarLoginItem').remove(); // Replace dropdown icon menu from mobile navbar topMenuMobile.querySelector('#navbarDropdownIcon').innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <circle cx="12" cy="12" r="11.5" stroke="#17254D"/> <g transform="translate(6, 6) scale(1.0)"> <path d="M8.23244 7.01221C7.71395 7.31671 7.07318 7.50166 6.40208 7.50166C5.73097 7.50166 5.0902 7.31671 4.57171 7.01221C2.44846 7.13193 0.801758 8.64866 0.801758 10.5006V11.3657L1.34946 11.4772C1.41645 11.4982 3.02291 12.0006 6.40208 12.0006C9.78125 12.0006 11.3877 11.4982 11.4547 11.4772L12.0024 11.3657V10.5006C12.0024 8.64866 10.3557 7.13193 8.23244 7.01221Z" fill="#17254D"/> <path d="M6.402 6.00001C8.202 6.00001 9.66667 4.61867 9.66667 3.00001C9.66667 1.39734 8.26933 0 6.66667 0C5.064 0 3.66667 1.39734 3.66667 3.00001C3.66667 4.61867 5.13133 6.00001 6.402 6.00001Z" fill="#17254D"/> </g> </svg>`; topMenuMobile.querySelector('#navbarDropdownIcon').classList.add('dropdown-toggle', 'font-color'); // Add logout button into mobile navbar const logoutMobile = document.createElement('li'); logoutMobile.classList.add('nav-item', 'd-flex', 'align-items-start', 'justify-content-center'); logoutMobile.innerHTML = `<a href="${logout}" class="nav-link font-weight-bold p-0 mx-2"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <path d="M6.002 2H18.002C18.7586 2 19.4841 2.21023 20.002 2.72822C20.5199 3.24622 20.7301 3.97174 20.7301 4.72822V6C20.7301 6.23973 20.6512 6.46938 20.509 6.64815C20.3669 6.82691 20.1681 6.94462 19.9551 6.97778C19.7421 7.01094 19.5275 6.95679 19.3525 6.82615C19.1775 6.69551 19.059 6.49976 19.002 6.28244V4.72822H6.002V20.7282H18.002V19.7282C18.002 19.4901 18.1205 19.2915 18.2955 19.1608C18.4705 19.0301 18.6851 18.9757 18.9021 19.0089C19.1191 19.042 19.3178 19.1597 19.46 19.3384C19.6022 19.5172 19.6811 19.7469 19.6811 19.9872V21.2576C19.6811 22.0141 19.4709 22.7396 18.953 23.2576C18.435 23.7755 17.7094 23.9858 16.953 23.9858H6.002C5.24552 23.9858 4.51998 23.7755 4.002 23.2576C3.48402 22.7396 3.27378 22.0141 3.27378 21.2576V4.72822C3.27378 3.97174 3.48402 3.24622 4.002 2.72822C4.51998 2.21023 5.24552 2 6.002 2Z" fill="#17254D"/> <path d="M18.252 15.2959C18.6681 15.7119 19.3426 15.7119 19.7586 15.2959L22.7934 12.2611C22.9591 12.0954 23.046 11.8732 23.046 11.6402C23.046 11.4071 22.9591 11.1849 22.7934 11.0191L19.7586 7.98431C19.5886 7.82448 19.3613 7.75588 19.1293 7.77216C18.8973 7.78844 18.6772 7.88873 18.5232 8.05591C18.3692 8.2231 18.2959 8.44353 18.303 8.67067C18.3101 8.89781 18.3964 9.11156 18.546 9.28089L20.6708 11.4057H13.3333C13.0007 11.4057 12.681 11.5483 12.4484 11.7808C12.2159 12.0134 12.0733 12.333 12.0733 12.6657C12.0733 12.9983 12.2159 13.318 12.4484 13.5506C12.681 13.7831 13.0007 13.9257 13.3333 13.9257H20.6708L18.546 16.0505C18.3831 16.2135 18.2824 16.4397 18.2825 16.6736C18.2826 16.9075 18.3838 17.1336 18.546 17.2969C18.7083 17.4602 18.9345 17.5609 19.1684 17.561C19.4023 17.5612 19.6284 17.46 19.7916 17.297C19.9548 17.134 20.0554 16.9077 20.0555 16.6737C20.0556 16.4398 19.9544 16.2137 19.7914 16.0505L18.252 15.2959Z" fill="#17254D"/> </svg> </a>`; topMenuMobile.appendChild(logoutMobile); // Check if the user is an Admin if (data.role === 'Admin') { const innerMenuContent = `<li><a href="/management" class="dropdown-item" title="management settings page">Management Settings</a></li> <li><a href="${manageNewsArticles}" class="dropdown-item" title="news articles page">News Articles</a></li> <li><a href="${manageOsintSourcesUrl}" class="dropdown-item" title="osint source page">OSINT Source</a></li> <li><a href="${manageEpss}" class="dropdown-item" title="manage epss">Manage EPSS</a></li> <li><a href="${nvdFeeds}" class="dropdown-item" title="nvd feeds">NVD Feeds</a></li> <li><a href="${manageAllComponents}" class="dropdown-item" title="manage all user components">All User Components</a></li> <li><a href="${manageAllCPEs}" class="dropdown-item" title="manage all cpes">All CPEs</a></li> <li><a href="${manageCVES}" class="dropdown-item" title="manage cve reports">CVE Reports</a></li> <li><a href="${manageCwe}" class="dropdown-item" title="manage cwes">Manage CWEs</a></li> <li><a href="${manageAssigners}" class="dropdown-item" title="manage assigners">Manage Assigners</a></li> <li><a href="${manageReleaseScraper}" class="dropdown-item" title="manage release scraper">Manage Release Scraper</a></li> <li><a href="${manageCanonicalNames}" class="dropdown-item" title="manage canonical names">Canonical Names</a></li>`; navbarDropdownMenu.innerHTML = ''; document.getElementById('navbar-dynamic-load').innerHTML = `<div class="d-flex align-items-center"> <div class="dropdown d-xl-block"> <a class="nav-link text-white p-0 mx-2" role="button" data-bs-toggle="dropdown" aria-expanded="false" title="dropdown page"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FDFDFD"> <g fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="12" cy="12" r="3"/> <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83a2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33a1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2a2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0a2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2a2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83a2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2a2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0a2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2a2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/> </g> </svg> <ul class="dropdown-menu dropdown-menu-end">` + innerMenuContent + `</ul> </a> </div> <a class="nav-link text-white p-0 mx-2" href="/user_settings"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <circle cx="12" cy="12" r="11.5" stroke="white"/> <g transform="translate(6, 6) scale(1.0)"> <path d="M8.23244 7.01221C7.71395 7.31671 7.07318 7.50166 6.40208 7.50166C5.73097 7.50166 5.0902 7.31671 4.57171 7.01221C2.44846 7.13193 0.801758 8.64866 0.801758 10.5006V11.3657L1.34946 11.4772C1.41645 11.4982 3.02291 12.0006 6.40208 12.0006C9.78125 12.0006 11.3877 11.4982 11.4547 11.4772L12.0024 11.3657V10.5006C12.0024 8.64866 10.3557 7.13193 8.23244 7.01221Z" fill="white"/> <path d="M6.402 6.00001C8.202 6.00001 9.66667 4.61867 9.66667 3.00001C9.66667 1.39734 8.26933 0 6.66667 0C5.064 0 3.66667 1.39734 3.66667 3.00001C3.66667 4.61867 5.13133 6.00001 6.402 6.00001Z" fill="white"/> </g> </svg> </a> <a href="${logout}" class="nav-link text-white font-weight-bold p-0 ms-1"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8.00065 2.66797H20.0007C20.7079 2.66797 21.3862 2.94892 21.8863 3.44902C22.3864 3.94911 22.6673 4.62739 22.6673 5.33464V6.66797C22.6673 7.02159 22.5268 7.36073 22.2768 7.61078C22.0267 7.86083 21.6876 8.0013 21.334 8.0013C20.9804 8.0013 20.6412 7.86083 20.3912 7.61078C20.1411 7.36073 20.0007 7.02159 20.0007 6.66797V5.33464H8.00065V26.668H20.0007V25.3346C20.0007 24.981 20.1411 24.6419 20.3912 24.3918C20.6412 24.1418 20.9804 24.0013 21.334 24.0013C21.6876 24.0013 22.0267 24.1418 22.2768 24.3918C22.5268 24.6419 22.6673 24.981 22.6673 25.3346V26.668C22.6673 27.3752 22.3864 28.0535 21.8863 28.5536C21.3862 29.0537 20.7079 29.3346 20.0007 29.3346H8.00065C7.29341 29.3346 6.61513 29.0537 6.11503 28.5536C5.61494 28.0535 5.33398 27.3752 5.33398 26.668V5.33464C5.33398 4.62739 5.61494 3.94911 6.11503 3.44902C6.61513 2.94892 7.29341 2.66797 8.00065 2.66797Z" fill="#FDFDFD"/> <path d="M22.3933 21.7268C22.9133 22.2468 23.7533 22.2468 24.2733 21.7268L29.0573 16.9428C29.3073 16.6928 29.4477 16.3537 29.4477 16.0002C29.4477 15.6466 29.3073 15.3075 29.0573 15.0575L24.2733 10.2735C24.0199 10.0444 23.6881 9.92149 23.3466 9.93011C23.0051 9.93873 22.6799 10.0782 22.4383 10.3198C22.1967 10.5614 22.0572 10.8865 22.0486 11.2281C22.04 11.5696 22.1629 11.9014 22.392 12.1548L24.8933 14.6668H13.3333C12.9797 14.6668 12.6406 14.8073 12.3905 15.0574C12.1405 15.3074 12 15.6465 12 16.0002C12 16.3538 12.1405 16.6929 12.3905 16.943C12.6406 17.193 12.9797 17.3335 13.3333 17.3335H24.8933L22.392 19.8455C22.1431 20.0954 22.0035 20.4338 22.0037 20.7864C22.004 21.1391 22.1441 21.4773 22.3933 21.7268Z" fill="#FDFDFD"/> </svg> </a> </div>`; if (dynamicContentElement) { dynamicContentElement.innerHTML = ``; } if (dynamicFooter) { dynamicFooter.innerHTML = ``; } // ADD just below where you create and insert the Guide item // ───────── Guide item (already present) ───────── const items = document.createElement('li'); items.className = 'nav-item'; items.innerHTML = `<a href="/guide" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center"> <span class="iconify sidebar-icon" data-icon="material-symbols:menu-book-outline"></span> </div> <span class="nav-link-text">Guide & Documentation</span> </a>`; sidebarStaticContent.insertAdjacentElement('beforeend', items); const itemsCloned = items.cloneNode(true); navbarDropdownMobile.insertAdjacentElement('beforeend', itemsCloned); // ─────────► NEW Support item ───────── const support = document.createElement('li'); support.className = 'nav-item'; support.innerHTML = `<a href="/support" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center"> <span class="iconify sidebar-icon" data-icon="material-symbols:help-center-outline-rounded"></span> </div> <span class="nav-link-text">Support</span> </a>`; sidebarStaticContent.insertAdjacentElement('beforeend', support); // keep mobile and desktop in sync const supportClone = support.cloneNode(true); navbarDropdownMobile.insertAdjacentElement('beforeend', supportClone); // Editor: only Management Settings + News Articles in the dropdown } else if (data.role === 'Editor') { const innerMenuContent = ` <li><a href="/management" class="dropdown-item" title="management settings page">Management Settings</a></li> <li><a href="${manageNewsArticles}" class="dropdown-item" title="news articles page">News Articles</a></li> `; navbarDropdownMenu.innerHTML = ''; document.getElementById('navbar-dynamic-load').innerHTML = `<div class="d-flex align-items-center"> <div class="dropdown d-xl-block"> <a class="nav-link text-white p-0 mx-2" role="button" data-bs-toggle="dropdown" aria-expanded="false" title="dropdown page"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FDFDFD"> <g fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> <circle cx="12" cy="12" r="3"/> <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83a2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33a1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2a2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0a2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2a2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83a2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2a2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0a2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2a2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/> </g> </svg> <ul class="dropdown-menu dropdown-menu-end"> ${innerMenuContent} </ul> </a> </div> <a class="nav-link text-white p-0 mx-2" href="/user_settings"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <circle cx="12" cy="12" r="11.5" stroke="white"/> <g transform="translate(6, 6) scale(1.0)"> <path d="M8.23244 7.01221C7.71395 7.31671 7.07318 7.50166 6.40208 7.50166C5.73097 7.50166 5.0902 7.31671 4.57171 7.01221C2.44846 7.13193 0.801758 8.64866 0.801758 10.5006V11.3657L1.34946 11.4772C1.41645 11.4982 3.02291 12.0006 6.40208 12.0006C9.78125 12.0006 11.3877 11.4982 11.4547 11.4772L12.0024 11.3657V10.5006C12.0024 8.64866 10.3557 7.13193 8.23244 7.01221Z" fill="white"/> <path d="M6.402 6.00001C8.202 6.00001 9.66667 4.61867 9.66667 3.00001C9.66667 1.39734 8.26933 0 6.66667 0C5.064 0 3.66667 1.39734 3.66667 3.00001C3.66667 4.61867 5.13133 6.00001 6.402 6.00001Z" fill="white"/> </g> </svg> </a> <a href="${logout}" class="nav-link text-white font-weight-bold p-0 ms-1"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8.00065 2.66797H20.0007C20.7079 2.66797 21.3862 2.94892 21.8863 3.44902C22.3864 3.94911 22.6673 4.62739 22.6673 5.33464V6.66797C22.6673 7.02159 22.5268 7.36073 22.2768 7.61078C22.0267 7.86083 21.6876 8.0013 21.334 8.0013C20.9804 8.0013 20.6412 7.86083 20.3912 7.61078C20.1411 7.36073 20.0007 7.02159 20.0007 6.66797V5.33464H8.00065V26.668H20.0007V25.3346C20.0007 24.981 20.1411 24.6419 20.3912 24.3918C20.6412 24.1418 20.9804 24.0013 21.334 24.0013C21.6876 24.0013 22.0267 24.1418 22.2768 24.3918C22.5268 24.6419 22.6673 24.981 22.6673 25.3346V26.668C22.6673 27.3752 22.3864 28.0535 21.8863 28.5536C21.3862 29.0537 20.7079 29.3346 20.0007 29.3346H8.00065C7.29341 29.3346 6.61513 29.0537 6.11503 28.5536C5.61494 28.0535 5.33398 27.3752 5.33398 26.668V5.33464C5.33398 4.62739 5.61494 3.94911 6.11503 3.44902C6.61513 2.94892 7.29341 2.66797 8.00065 2.66797Z" fill="#FDFDFD"/> <path d="M22.3933 21.7268C22.9133 22.2468 23.7533 22.2468 24.2733 21.7268L29.0573 16.9428C29.3073 16.6928 29.4477 16.3537 29.4477 16.0002C29.4477 15.6466 29.3073 15.3075 29.0573 15.0575L24.2733 10.2735C24.0199 10.0444 23.6881 9.92149 23.3466 9.93011C23.0051 9.93873 22.6799 10.0782 22.4383 10.3198C22.1967 10.5614 22.0572 10.8865 22.0486 11.2281C22.04 11.5696 22.1629 11.9014 22.392 12.1548L24.8933 14.6668H13.3333C12.9797 14.6668 12.6406 14.8073 12.3905 15.0574C12.1405 15.3074 12 15.6465 12 16.0002C12 16.3538 12.1405 16.6929 12.3905 16.943C12.6406 17.193 12.9797 17.3335 13.3333 17.3335H24.8933L22.392 19.8455C22.1431 20.0954 22.0035 20.4338 22.0037 20.7864C22.004 21.1391 22.1441 21.4773 22.3933 21.7268Z" fill="#FDFDFD"/> </svg> </a> </div>`; if (dynamicContentElement) { dynamicContentElement.innerHTML = ``; } if (dynamicFooter) { dynamicFooter.innerHTML = ``; } } else { // Update mobile navbar menu navbarDropdownMenu.innerHTML = ` ${userMenuItems} <li><hr class="dropdown-divider"></li> <li><a href="/user_settings" class="dropdown-item" title="user settings page">User Settings</a></li> `; document.getElementById('navbar-dynamic-load').innerHTML = `<div class="d-flex align-items-center"> <div class="dropdown d-xl-block"> <a class="nav-link dropdown-toggle text-white p-0 mx-2" role="button" data-bs-toggle="dropdown" aria-expanded="false"" title="dropdown page"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <circle cx="12" cy="12" r="11.5" stroke="white"/> <g transform="translate(6, 6) scale(1.0)"> <path d="M8.23244 7.01221C7.71395 7.31671 7.07318 7.50166 6.40208 7.50166C5.73097 7.50166 5.0902 7.31671 4.57171 7.01221C2.44846 7.13193 0.801758 8.64866 0.801758 10.5006V11.3657L1.34946 11.4772C1.41645 11.4982 3.02291 12.0006 6.40208 12.0006C9.78125 12.0006 11.3877 11.4982 11.4547 11.4772L12.0024 11.3657V10.5006C12.0024 8.64866 10.3557 7.13193 8.23244 7.01221Z" fill="white"/> <path d="M6.402 6.00001C8.202 6.00001 9.66667 4.61867 9.66667 3.00001C9.66667 1.39734 8.26933 0 6.66667 0C5.064 0 3.66667 1.39734 3.66667 3.00001C3.66667 4.61867 5.13133 6.00001 6.402 6.00001Z" fill="white"/> </g> </svg> </a> <ul class="dropdown-menu dropdown-menu-end"> ${userMenuItems} <li><hr class="dropdown-divider"></li> <li><a href="/user_settings" class="dropdown-item" title="user settings page">User Settings</a></li> </ul> </div> <a href="${logout}" class="nav-link text-white font-weight-bold p-0 ms-1"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <path d="M6.002 2H18.002C18.7586 2 19.4841 2.21023 20.002 2.72822C20.5199 3.24622 20.7301 3.97174 20.7301 4.72822V6C20.7301 6.23973 20.6512 6.46938 20.509 6.64815C20.3669 6.82691 20.1681 6.94462 19.9551 6.97778C19.7421 7.01094 19.5275 6.95679 19.3525 6.82615C19.1775 6.69551 19.059 6.49976 19.002 6.28244V4.72822H6.002V20.7282H18.002V19.7282C18.002 19.4901 18.1205 19.2915 18.2955 19.1608C18.4705 19.0301 18.6851 18.9757 18.9021 19.0089C19.1191 19.042 19.3178 19.1597 19.46 19.3384C19.6022 19.5172 19.6811 19.7469 19.6811 19.9872V21.2576C19.6811 22.0141 19.4709 22.7396 18.953 23.2576C18.435 23.7755 17.7094 23.9858 16.953 23.9858H6.002C5.24552 23.9858 4.51998 23.7755 4.002 23.2576C3.48402 22.7396 3.27378 22.0141 3.27378 21.2576V4.72822C3.27378 3.97174 3.48402 3.24622 4.002 2.72822C4.51998 2.21023 5.24552 2 6.002 2Z" fill="#FDFDFD"/> <path d="M18.252 15.2959C18.6681 15.7119 19.3426 15.7119 19.7586 15.2959L22.7934 12.2611C22.9591 12.0954 23.046 11.8732 23.046 11.6402C23.046 11.4071 22.9591 11.1849 22.7934 11.0191L19.7586 7.98431C19.5886 7.82448 19.3613 7.75588 19.1293 7.77216C18.8973 7.78844 18.6772 7.88873 18.5232 8.05591C18.3692 8.2231 18.2959 8.44353 18.303 8.67067C18.3101 8.89781 18.3964 9.11156 18.546 9.28089L20.6708 11.4057H13.3333C13.0007 11.4057 12.681 11.5483 12.4484 11.7808C12.2159 12.0134 12.0733 12.333 12.0733 12.6657C12.0733 12.9983 12.2159 13.318 12.4484 13.5506C12.681 13.7831 13.0007 13.9257 13.3333 13.9257H20.6708L18.546 16.0505C18.3831 16.2135 18.2824 16.4397 18.2825 16.6736C18.2826 16.9075 18.3838 17.1336 18.546 17.2969C18.7083 17.4602 18.9345 17.5609 19.1684 17.561C19.4023 17.5612 19.6284 17.46 19.7916 17.297C19.9548 17.134 20.0554 16.9077 20.0555 16.6737C20.0556 16.4398 19.9544 16.2137 19.7914 16.0505L18.252 15.2959Z" fill="#FDFDFD"/> </svg> </a> </div>`; if (dynamicContentElement) { dynamicContentElement.innerHTML = ``; } if (dynamicFooter) { dynamicFooter.innerHTML = ``; } // Add Guide item const items = document.createElement('li'); items.className = 'nav-item'; items.innerHTML = `<a href="/guide" class="nav-link"> <div class="icon icon-shape i-sm text-center d-flex align-items-center justify-content-center"> <span class="iconify sidebar-icon" data-icon="material-symbols:menu-book-outline"></span> </div> <span class="nav-link-text">Guide & Documentation</span> </a>`; // Insert the elements after the last element. In this case the horizontal divider sidebarStaticContent.insertAdjacentElement('beforeend', items); // Append it to the mobile menu const itemsCloned = items.cloneNode(true); navbarDropdownMobile.insertAdjacentElement('beforeend', itemsCloned); } //document.getElementById('navbar-dynamic-load').classList.add('nav-item', 'dropdown'); sidebarElement.classList.add('nav-item', 'dropdown'); // Make collapse menu option visible document.getElementById('collapse-menu-icon').classList.remove('hidden'); document.getElementById('collapse-menu-icon').classList.add('d-flex'); } else { // mark sidebar state sidebarElement.classList.add('not-logged-in'); sidebarElement.classList.remove('logged-in'); // 1) clone your four β€œdemo” items const makeDemoItem = (icon, text, href) => { const li = menuItemTemplate.cloneNode(true); li.querySelector('#menuItemIcon').dataset.icon = icon; li.querySelector('#menuItemText').textContent = text; li.querySelector('#menuItemBtn').href = href; return li; }; const menuMyDashboardCloned = makeDemoItem('majesticons:lock-line','My Dashboard','/my_dashboard_demo'); const menuMyComponentsCloned = makeDemoItem('majesticons:lock-line','My Components','/my_components_demo'); const menuMyThreatsCloned = makeDemoItem('majesticons:lock-line','My Threats','/my_threats_demo'); const menuMyNodesCloned = makeDemoItem('majesticons:lock-line','My Nodes','/my_nodes_demo'); // 2) grab your four public <li>s const menuHome = document.getElementById('nav_item_home'); const menuReports = document.getElementById('nav_item_reports'); const menuVulnerabilities = document.getElementById('nav_item_vulnerabilities'); const menuNews = document.getElementById('nav_item_news'); // 3) mobile navbar refs const navbarMobile = document.getElementById('navbarMobileList'); const navbarDropdownMobile = document.getElementById('navbarMobileDropdownList'); const moreItem = document.getElementById('nav_item_more'); // 4) detect demo paths const current = window.location.pathname.replace(/\/+$/, ''); const demoPaths = [ '/my_dashboard_demo', '/my_components_demo', '/my_threats_demo', '/my_nodes_demo' ]; if (demoPaths.includes(current)) { // a) move public items into More dropdown [menuHome, menuReports, menuVulnerabilities, menuNews] .forEach(li => { navbarDropdownMobile.appendChild(li); }); // b) prepend demo items [ menuMyNodesCloned, menuMyThreatsCloned, menuMyComponentsCloned, menuMyDashboardCloned ].forEach(li => { navbarMobile.insertBefore(li, navbarMobile.firstChild); }); } else { [menuMyDashboardCloned, menuMyComponentsCloned, menuMyThreatsCloned, menuMyNodesCloned] .forEach(li => { navbarDropdownMobile.appendChild(li); }); } // 5) desktop-login UI (as before) document.getElementById('navbar-dynamic-load').innerHTML = `<a href="/" class="nav-link d-flex align-items-center text-white font-weight-bold mx-2"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> <path d="M7.625 12.5L3.125 8L7.625 3.5M3.75 8H12.875" stroke="#FDFDFD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> <span class="d-sm-inline text-white ms-1">Back to welcome page</span> </a> <a href="/login" class="nav-link text-white font-weight-bold px-0"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <circle cx="12" cy="12" r="11.5" stroke="white"/> <g transform="translate(6, 6) scale(1.0)"> <path d="M8.23244 7.01221C7.71395 7.31671 7.07318 7.50166 6.40208 7.50166C5.73097 7.50166 5.0902 7.31671 4.57171 7.01221C2.44846 7.13193 0.801758 8.64866 0.801758 10.5006V11.3657L1.34946 11.4772C1.41645 11.4982 3.02291 12.0006 6.40208 12.0006C9.78125 12.0006 11.3877 11.4982 11.4547 11.4772L12.0024 11.3657V10.5006C12.0024 8.64866 10.3557 7.13193 8.23244 7.01221Z" fill="white"/> <path d="M6.402 6.00001C8.202 6.00001 9.66667 4.61867 9.66667 3.00001C9.66667 1.39734 8.26933 0 6.66667 0C5.064 0 3.66667 1.39734 3.66667 3.00001C3.66667 4.61867 5.13133 6.00001 6.402 6.00001Z" fill="white"/> </g> </svg> <span class="d-sm-inline text-white ms-1">Log In</span> </a>`; document.getElementById('navbar-dynamic-load') .classList.add('nav-item','d-flex','align-items-center'); // 6) registration prompt (only if the helper functions exist) if (typeof generatePromptToRegister === 'function' && dynamicContentElement) { dynamicContentElement.innerHTML = generatePromptToRegister(); } if (typeof generatePromptToRegister === 'function' && typeof generateCastleLogoAndPhrase === 'function' && dynamicFooter) { dynamicFooter.innerHTML = generatePromptToRegister() + '\n' + generateCastleLogoAndPhrase(); } // Make collapse menu option hidden document.getElementById('collapse-menu-icon').classList.add('hidden'); document.getElementById('collapse-menu-icon').classList.remove('d-flex'); } highlightMenuItem(); }) .catch(error => { console.error('Error fetching login status:', error); }); } // Function that returns the castle logo\ function generateCastleLogoAndPhrase() { const label = `<div class="col d-flex flex-column" style="gap: 20px; margin-top: 20px;"> <div class="row d-flex justify-content-center"> <img class="w-35" src="/static/logo/sidebar/bf_castle.webp" alt="BaseFortify castle logo"> </div> <div class="row text-center m-2"> <p class="font-weight-bold mb-0 pt-0 w-100 text-small font-weight-bold">Your Fortified Frontier</p> </div> </div>`; return label; } // Function that returns the prompt box 'Register for free to use all features!' function generatePromptToRegister() { const label = `<ul class="d-flex flex-column flex-grow align-content-center navbar-nav"> <li class="nav-item"> <div class="d-flex flex-column"> <!-- Redirect to registration page --> <div class="d-flex flex-column justify-content-center"> <div style="text-align: center;"> <svg xmlns="http://www.w3.org/2000/svg" width="77" height="77" viewBox="0 0 77 77" fill="none"> <g clip-path="url(#clip0_6804_17819)"> <g filter="url(#filter0_d_6804_17819)"> <path d="M69.9355 38.5564C69.8489 48.0017 69.727 60.1308 69.3115 69.4976C51.4282 68.9922 32.4959 68.0265 14.7377 66.0887C15.3329 60.0891 16.0676 53.889 16.9868 47.9279H17.6285L17.683 63.6231L63.6327 63.4595C63.5525 51.048 63.5461 26.0744 63.6215 13.6661L17.6942 13.5009L17.6285 32.0258H16.9868C15.9024 25.1199 15.0939 17.7375 14.4297 10.7851C32.4846 8.78466 51.186 8.0692 69.3548 7.50293C69.8954 17.8017 69.8569 28.2576 69.9355 38.5564ZM32.1446 37.6853C38.8943 37.4991 45.5609 39.2104 51.3865 42.6246L38.0351 44.6185L38.0848 44.9346C44.4116 46.635 50.947 45.8233 57.346 45.1078C56.2343 38.7729 55.2606 32.2745 51.9031 26.6422L51.5887 26.692L53.2875 39.952C42.6695 31.3697 26.0295 30.2677 14.8677 38.4698C10.0552 41.9412 6.23245 48.0899 7.2158 54.1008L7.69064 54.1697C9.65895 43.0882 21.8667 37.7014 32.1446 37.6853Z" fill="url(#paint0_linear_6804_17819)"/> </g> </g> <defs> <filter id="filter0_d_6804_17819" x="3.06055" y="7.50293" width="70.875" height="69.9946" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> <feFlood flood-opacity="0" result="BackgroundImageFix"/> <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> <feOffset dy="4"/> <feGaussianBlur stdDeviation="2"/> <feComposite in2="hardAlpha" operator="out"/> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14 0"/> <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6804_17819"/> <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6804_17819" result="shape"/> </filter> <linearGradient id="paint0_linear_6804_17819" x1="38.4971" y1="69.4976" x2="38.4971" y2="7.50293" gradientUnits="userSpaceOnUse"> <stop stop-color="#FF9E00"/> <stop offset="1" stop-color="#737F93"/> </linearGradient> <clipPath id="clip0_6804_17819"> <rect width="77" height="77" fill="white"/> </clipPath> </defs> </svg> </div> <p class="text-medium text-center font-weight-bold text-to-register">Register for free to use all features!</p> <div class="d-flex justify-content-center"> <a href="/register" class="btn btn-tertiary mb-3"> <span class="d-sm-inline text-small font-weight-bold">Register now</span> <span class="iconify ms-sm-2" data-icon="teenyicons:arrow-right-outline"></span> </a> </div> </div> <!-- Redirect to login page --> <div class="d-flex flex-column justify-content-center"> <p class="text-medium text-center text-to-login">Already have an account?</p> <div class="d-flex justify-content-center"> <a href="/login" class="btn btn-light mb-3"> <span class="d-sm-inline text-small font-weight-bold">Login</span> </a> </div> </div> </div> </li> </ul>`; return label; } </script> </body> </html>