VYPR
Moderate severityNVD Advisory· Published Nov 25, 2025· Updated Nov 26, 2025

Formwork CMS Has a Stored Cross-Site Scripting (XSS) Vulnerability in Blog Tags

CVE-2025-65956

Description

Formwork is a flat file-based Content Management System (CMS). Prior to version 2.2.0, inserting unsanitized data into the blog tag field results in stored cross‑site scripting (XSS). Any user with credentials to the Formwork CMS who accesses or edits an affected blog post will have attacker‑controlled script executed in their browser. The issue is persistent and impacts privileged administrative workflows. This issue has been patched in version 2.2.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Stored XSS in Formwork CMS through unsanitized blog tag fields allows persistent script execution for authenticated users, fixed in v2.2.0.

Vulnerability

Overview CVE-2025-65956 is a stored cross-site scripting (XSS) vulnerability in Formwork, a flat-file CMS. The root cause is the use of innerHTML to insert untrusted data into the Document Object Model (DOM) without sanitization, specifically in the blog tag field and other input components. The official description indicates that unsanitized data inserted into the blog tag field results in stored XSS [2]. The advisory notes that the fix introduces an escapeHtml function and replaces many innerHTML assignments with innerText to prevent HTML injection [3]. The commit shown in Reference [1] demonstrates the change from innerHTML to innerText for a filename display and the addition of escapeHtml for search input handling, confirming the sanitization approach [1].

Attack

Vector and Prerequisites An attacker with valid credentials to the Formwork administration panel can store malicious JavaScript in the blog tag field. Because the input is not sanitized, the payload persists on the server. Any authenticated user who subsequently views or edits the affected blog post will have the attacker-controlled script executed in their browser. The vulnerability impacts privileged administrative workflows [2]. No special network position or additional user interaction beyond accessing the blog post is required for exploitation, as the script executes automatically upon rendering the tags.

Impact and

Mitigation Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the victim's session within the Formwork admin panel. This can lead to theft of session cookies, defacement of the admin interface, or performance of administrative actions on behalf of the victim, potentially compromising the entire CMS installation. The issue has been patched in Formwork version 2.2.0 [2]. Administrators are strongly advised to upgrade to this patched version immediately. The commit and pull request provide the full remediation details [1][3].

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
getformwork/formworkPackagist
< 2.2.02.2.0

Affected products

2

Patches

1
4abcd60ae769

Merge pull request #791 from getformwork/fix/escape-html-js

https://github.com/getformwork/formworkGiuseppe CriscioneNov 22, 2025via ghsa
11 files changed · +167 192
  • panel/src/ts/components/fileslist.ts+7 7 modified
    @@ -1,5 +1,5 @@
     import { $, $$ } from "../utils/selectors";
    -import { escapeRegExp, makeDiacriticsRegExp } from "../utils/validation";
    +import { escapeHtml, escapeRegExp, makeDiacriticsRegExp } from "../utils/validation";
     import { app } from "../app";
     import { debounce } from "../utils/events";
     import { Form } from "./form";
    @@ -35,7 +35,7 @@ export class FilesList {
     
         private initFileList() {
             const toggle = $(".form-togglegroup.files-list-view-as", this.element);
    -        const searchInput = $(".files-search", this.element);
    +        const searchInput = $(".files-search", this.element) as HTMLInputElement;
     
             if (toggle) {
                 const formName = this.element.closest("form")?.dataset.form;
    @@ -120,21 +120,21 @@ export class FilesList {
             });
     
             if (searchInput) {
    -            const handleSearch = (event: Event) => {
    -                const value = (event.target as HTMLInputElement).value;
    +            const handleSearch = () => {
    +                const value = escapeHtml(searchInput.value);
                     ($(".files-item") as HTMLElement).classList.toggle("is-filtered", value.length > 0);
     
                     $$(".files-item").forEach((element) => {
                         let matches = 0;
     
    -                    for (const selector of [".file-name", ".file-parent-title"]) {
    +                    for (const selector of [".file-name a", ".file-parent-title"]) {
                             const item = $(selector, element) as HTMLElement;
     
                             if (!item) {
                                 continue;
                             }
     
    -                        const text = item.textContent as string;
    +                        const text = escapeHtml(item.textContent);
     
                             const regexp = value ? new RegExp(`${makeDiacriticsRegExp(escapeRegExp(value))}`, "gi") : null;
     
    @@ -211,7 +211,7 @@ export class FilesList {
                                 (item as HTMLElement).dataset.filename = response.data.filename;
     
                                 const anchor = $(".file-name a", item as HTMLElement) as HTMLAnchorElement;
    -                            anchor.innerHTML = response.data.filename;
    +                            anchor.innerText = response.data.filename;
                                 anchor.href = response.data.uri;
     
                                 ($("[data-command=infoFile]", item as HTMLElement) as HTMLAnchorElement).href = response.data.actions.info;
    
  • panel/src/ts/components/inputs/color-input.ts+1 1 modified
    @@ -30,7 +30,7 @@ export class ColorInput {
     
             if (outputElement) {
                 const updateValueLabel = (element: HTMLInputElement) => {
    -                outputElement.innerHTML = element.value;
    +                outputElement.innerText = element.value;
                 };
     
                 this.element.addEventListener("change", () => updateValueLabel(this.element));
    
  • panel/src/ts/components/inputs/date-input.ts+3 3 modified
    @@ -591,9 +591,9 @@ class Calendar {
             ($(".calendar-table", this.element) as HTMLElement).innerHTML = this.getInnerHTML();
     
             if (this.input.options.time) {
    -            ($(".calendar-hours", this.element) as HTMLElement).innerHTML = `${this.has12HourFormat(this.input.format) ? mod(this.hours, 12) || 12 : this.hours}`.padStart(2, "0");
    -            ($(".calendar-minutes", this.element) as HTMLElement).innerHTML = `${this.minutes}`.padStart(2, "0");
    -            ($(".calendar-meridiem", this.element) as HTMLElement).innerHTML = this.has12HourFormat(this.input.format) ? (this.hours < 12 ? "AM" : "PM") : "";
    +            ($(".calendar-hours", this.element) as HTMLElement).innerText = `${this.has12HourFormat(this.input.format) ? mod(this.hours, 12) || 12 : this.hours}`.padStart(2, "0");
    +            ($(".calendar-minutes", this.element) as HTMLElement).innerText = `${this.minutes}`.padStart(2, "0");
    +            ($(".calendar-meridiem", this.element) as HTMLElement).innerText = this.has12HourFormat(this.input.format) ? (this.hours < 12 ? "AM" : "PM") : "";
             }
     
             $$(".calendar-day", this.element).forEach((element) => {
    
  • panel/src/ts/components/inputs/duration-input.ts+1 1 modified
    @@ -129,7 +129,7 @@ export class DurationInput {
     
         private updateLabels() {
             Object.entries(this.innerInputs).forEach(([i, input]: [TimeInterval, HTMLInputElement]) => {
    -            (this.labels[i] as HTMLLabelElement).innerHTML = this.options.labels[i][parseInt(input.value) === 1 ? 0 : 1];
    +            (this.labels[i] as HTMLLabelElement).innerText = this.options.labels[i][parseInt(input.value) === 1 ? 0 : 1];
             });
         }
     
    
  • panel/src/ts/components/inputs/range-input.ts+1 1 modified
    @@ -30,7 +30,7 @@ export class RangeInput {
                 element.style.setProperty("--progress", `${Math.round((parseInt(element.value) / (parseInt(element.max) - parseInt(element.min))) * 100)}%`);
                 const outputElement = $(`output[for="${element.id}"]`);
                 if (outputElement) {
    -                outputElement.innerHTML = element.value;
    +                outputElement.innerText = element.value;
                 }
             };
     
    
  • panel/src/ts/components/inputs/tags-input.ts+3 3 modified
    @@ -63,7 +63,7 @@ export class TagsInput {
         set value(value: string) {
             this.element.value = value;
             this.tags = value.split(", ").map((tag) => tag.trim());
    -        this.list.innerHTML = "";
    +        this.list.innerText = "";
             this.tags.forEach((tag) => this.insertTag(tag, this.list));
             this.updatePlaceholder();
             this.updateDropdown();
    @@ -170,7 +170,7 @@ export class TagsInput {
             const item = document.createElement("div");
     
             item.className = "dropdown-item";
    -        item.innerHTML = option.label;
    +        item.innerText = option.label;
             item.dataset.value = option.value;
     
             if (option.thumb) {
    @@ -415,7 +415,7 @@ export class TagsInput {
             const tag = document.createElement("span");
             const tagRemove = document.createElement("i");
             tag.className = "tag";
    -        tag.innerHTML = value;
    +        tag.innerText = value;
             tag.style.marginRight = ".25rem";
             parent.appendChild(tag);
     
    
  • panel/src/ts/components/inputs/upload-input.ts+2 1 modified
    @@ -7,6 +7,7 @@ import { Notification } from "../notification";
     import { Request } from "../../utils/request";
     import { SelectInput } from "./select-input";
     import { TagsInput } from "./tags-input";
    +import { escapeHtml } from "../../utils/validation";
     
     export class UploadInput {
         readonly element: HTMLInputElement;
    @@ -186,7 +187,7 @@ export class UploadInput {
             if (this.element.files && this.element.files.length > 0) {
                 const filenames: string[] = [];
                 for (const file of Array.from(this.element.files)) {
    -                filenames.push(`${file.name} <span class="file-size-inline">(${this.formatFileSize(file.size)})</span>`);
    +                filenames.push(`${escapeHtml(file.name)} <span class="file-size-inline">(${this.formatFileSize(file.size)})</span>`);
                 }
                 this.dropTargetLabel.innerHTML = filenames.join(", ");
     
    
  • panel/src/ts/components/views/backups.ts+5 5 modified
    @@ -22,7 +22,7 @@ export class Backups {
                         }
     
                         spinner.className = "spinner";
    -                    spinner.innerHTML = "";
    +                    spinner.innerText = "";
     
                         return spinner;
                     };
    @@ -51,13 +51,13 @@ export class Backups {
                                     const node = template.content.cloneNode(true) as HTMLElement;
     
                                     ($(".backup-uri", node) as HTMLAnchorElement).href = response.data.uri;
    -                                ($(".backup-uri", node) as HTMLElement).innerHTML = response.data.filename;
    +                                ($(".backup-uri", node) as HTMLElement).innerText = response.data.filename;
     
    -                                ($(".backup-date", node) as HTMLElement).innerHTML = response.data.date;
    -                                ($(".backup-size", node) as HTMLElement).innerHTML = response.data.size;
    +                                ($(".backup-date", node) as HTMLElement).innerText = response.data.date;
    +                                ($(".backup-size", node) as HTMLElement).innerText = response.data.size;
                                     ($(".backup-delete", node) as HTMLElement).dataset.modalAction = response.data.deleteUri;
     
    -                                ($(".backup-last-time") as HTMLElement).innerHTML = app.config.Backups.labels.now;
    +                                ($(".backup-last-time") as HTMLElement).innerText = app.config.Backups.labels.now;
     
                                     ($("tbody", table) as HTMLElement).prepend(node);
     
    
  • panel/src/ts/components/views/pages.ts+6 6 modified
    @@ -1,5 +1,5 @@
     import { $, $$ } from "../../utils/selectors";
    -import { escapeRegExp, makeDiacriticsRegExp, makeSlug } from "../../utils/validation";
    +import { escapeHtml, escapeRegExp, makeDiacriticsRegExp, makeSlug } from "../../utils/validation";
     import { app } from "../../app";
     import { debounce } from "../../utils/events";
     import { Form } from "../form";
    @@ -15,7 +15,7 @@ export class Pages {
             const commandReorderPages = $("[data-command=reorder-pages]") as HTMLButtonElement;
             const commandPreview = $("[data-command=preview]") as HTMLButtonElement;
     
    -        const searchInput = $(".page-search");
    +        const searchInput = $(".page-search") as HTMLInputElement;
     
             const newPageModal = app.modals["newPageModal"];
             const deletePageItemModal = app.modals["deletePageItemModal"];
    @@ -73,14 +73,14 @@ export class Pages {
                     });
                 });
     
    -            const handleSearch = (event: Event) => {
    -                const value = (event.target as HTMLInputElement).value;
    +            const handleSearch = () => {
    +                const value = escapeHtml(searchInput.value);
                     if (value.length === 0) {
                         ($(".pages-tree-root") as HTMLElement).classList.remove("is-filtered");
     
                         $$(".pages-tree-item").forEach((element) => {
                             const title = $(".page-title a", element) as HTMLElement;
    -                        title.innerHTML = title.textContent as string;
    +                        title.innerText = title.textContent;
                             ($(".pages-tree-row", element) as HTMLElement).style.display = "";
                             element.classList.toggle("is-expanded", element.dataset.expanded === "true");
                         });
    @@ -91,7 +91,7 @@ export class Pages {
     
                         $$(".pages-tree-item").forEach((element) => {
                             const title = $(".page-title a", element) as HTMLElement;
    -                        const text = title.textContent as string;
    +                        const text = escapeHtml(title.textContent);
                             const pagesItem = $(".pages-tree-row", element) as HTMLElement;
     
                             if (text.match(regexp) !== null) {
    
  • panel/src/ts/components/views/updates.ts+5 5 modified
    @@ -20,7 +20,7 @@ export class Updates {
                 const showNewVersion = (name: string) => {
                     spinner.classList.add("spinner-info");
                     insertIcon("info", spinner);
    -                newVersionName.innerHTML = name;
    +                newVersionName.innerText = name;
                     newVersion.style.display = "block";
                 };
     
    @@ -33,7 +33,7 @@ export class Updates {
                 const showInstalledVersion = () => {
                     spinner.classList.add("spinner-success");
                     insertIcon("check", spinner);
    -                currentVersionName.innerHTML = newVersionName.innerHTML;
    +                currentVersionName.innerText = newVersionName.innerText;
                     currentVersion.style.display = "block";
                 };
     
    @@ -47,7 +47,7 @@ export class Updates {
                             data: data,
                         },
                         (response) => {
    -                        updateStatus.innerHTML = response.message;
    +                        updateStatus.innerText = response.message;
     
                             if (response.status === "success") {
                                 if (response.data.uptodate === false) {
    @@ -67,7 +67,7 @@ export class Updates {
                     newVersion.style.display = "none";
                     spinner.classList.remove("spinner-info");
                     $(".icon", spinner)?.remove();
    -                updateStatus.innerHTML = updateStatus.dataset.installingText as string;
    +                updateStatus.innerText = updateStatus.dataset.installingText as string;
     
                     new Request(
                         {
    @@ -79,7 +79,7 @@ export class Updates {
                             const notification = new Notification(response.message, response.status, { icon: "check-circle" });
                             notification.show();
     
    -                        updateStatus.innerHTML = response.data.status;
    +                        updateStatus.innerText = response.data.status;
     
                             if (response.status === "success") {
                                 showInstalledVersion();
    
  • panel/src/ts/utils/validation.ts+133 159 modified
    @@ -1,175 +1,149 @@
    +const slugMap: Record<string, string> = {
    +    "'": "-",
    +    "’": "-",
    +    "‘": "-",
    +    '"': "-",
    +    "“": "-",
    +    "”": "-",
    +    "-": "-",
    +    "–": "-",
    +    "—": "-",
    +    "/": "-",
    +    "\\": "-",
    +    _: "-",
    +    "~": "-",
    +    À: "A",
    +    Á: "A",
    +    Â: "A",
    +    Ã: "A",
    +    Ä: "A",
    +    Å: "A",
    +    Æ: "Ae",
    +    Ç: "C",
    +    Ð: "D",
    +    È: "E",
    +    É: "E",
    +    Ê: "E",
    +    Ë: "E",
    +    Ì: "I",
    +    Í: "I",
    +    Î: "I",
    +    Ï: "I",
    +    Ñ: "N",
    +    Ò: "O",
    +    Ó: "O",
    +    Ô: "O",
    +    Õ: "O",
    +    Ö: "O",
    +    Ø: "O",
    +    Œ: "Oe",
    +    Š: "S",
    +    Þ: "Th",
    +    Ù: "U",
    +    Ú: "U",
    +    Û: "U",
    +    Ü: "U",
    +    Ý: "Y",
    +    à: "a",
    +    á: "a",
    +    â: "a",
    +    ã: "a",
    +    ä: "ae",
    +    å: "a",
    +    æ: "ae",
    +    "¢": "c",
    +    ç: "c",
    +    ð: "d",
    +    è: "e",
    +    é: "e",
    +    ê: "e",
    +    ë: "e",
    +    ì: "i",
    +    í: "i",
    +    î: "i",
    +    ï: "i",
    +    ñ: "n",
    +    ò: "o",
    +    ó: "o",
    +    ô: "o",
    +    õ: "o",
    +    ö: "oe",
    +    ø: "o",
    +    œ: "oe",
    +    š: "s",
    +    ß: "ss",
    +    þ: "th",
    +    ù: "u",
    +    ú: "u",
    +    û: "u",
    +    ü: "ue",
    +    ý: "y",
    +    ÿ: "y",
    +    Ÿ: "y",
    +};
    +
    +const diacriticsMap: Record<string, string> = {
    +    a: "[aáàăâǎåäãȧąāảȁạ]",
    +    b: "[bḃḅ]",
    +    c: "[cćĉčċç]",
    +    d: "[dďḋḑḍ]",
    +    e: "[eéèĕêěëẽėȩęēẻȅẹ]",
    +    g: "[gǵğĝǧġģḡ]",
    +    h: "[hĥȟḧḣḩḥ]",
    +    i: "[iiíìĭîǐïĩįīỉȉịı]",
    +    j: "[jĵǰ]",
    +    k: "[kḱǩķḳ]",
    +    l: "[lĺľļḷ]",
    +    m: "[mḿṁṃ]",
    +    n: "[nńǹňñṅņṇ]",
    +    o: "[oóòŏôǒöőõȯǿǫōỏȍơọ]",
    +    p: "[pṕṗ]",
    +    r: "[rŕřṙŗȑṛ]",
    +    s: "[sśŝšṡşṣș]",
    +    t: "[tťẗṫţṭț]",
    +    u: "[uúùŭûǔůüűũųūủȕưụ]",
    +    v: "[vṽṿ]",
    +    w: "[wẃẁŵẘẅẇẉ]",
    +    x: "[xẍẋ]",
    +    y: "[yýỳŷẙÿỹẏȳỷỵ]",
    +    z: "[zźẑžżẓ]",
    +};
    +
    +const htmlEntityMap: Record<string, string> = {
    +    "&": "&amp;",
    +    "<": "&lt;",
    +    ">": "&gt;",
    +    '"': "&quot;",
    +    "'": "&#39;",
    +    "/": "&#x2F;",
    +    "`": "&#x60;",
    +    "=": "&#x3D;",
    +};
    +
     export function escapeRegExp(string: string) {
         return string.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
     }
     
     export function makeDiacriticsRegExp(string: string) {
    -    const diacritics: Record<string, string> = {
    -        a: "[aáàăâǎåäãȧąāảȁạ]",
    -        b: "[bḃḅ]",
    -        c: "[cćĉčċç]",
    -        d: "[dďḋḑḍ]",
    -        e: "[eéèĕêěëẽėȩęēẻȅẹ]",
    -        g: "[gǵğĝǧġģḡ]",
    -        h: "[hĥȟḧḣḩḥ]",
    -        i: "[iiíìĭîǐïĩįīỉȉịı]",
    -        j: "[jĵǰ]",
    -        k: "[kḱǩķḳ]",
    -        l: "[lĺľļḷ]",
    -        m: "[mḿṁṃ]",
    -        n: "[nńǹňñṅņṇ]",
    -        o: "[oóòŏôǒöőõȯǿǫōỏȍơọ]",
    -        p: "[pṕṗ]",
    -        r: "[rŕřṙŗȑṛ]",
    -        s: "[sśŝšṡşṣș]",
    -        t: "[tťẗṫţṭț]",
    -        u: "[uúùŭûǔůüűũųūủȕưụ]",
    -        v: "[vṽṿ]",
    -        w: "[wẃẁŵẘẅẇẉ]",
    -        x: "[xẍẋ]",
    -        y: "[yýỳŷẙÿỹẏȳỷỵ]",
    -        z: "[zźẑžżẓ]",
    -    };
    -    for (const char in diacritics) {
    -        string = string.split(char).join(diacritics[char]);
    -        string = string.split(char.toUpperCase()).join(diacritics[char].toUpperCase());
    -    }
    -    return string;
    +    return string.replace(/./g, (match) => diacriticsMap[match] || diacriticsMap[match.toLowerCase()]?.toUpperCase() || match);
     }
     
     export function makeSlug(string: string) {
    -    const translate: Record<string, string> = {
    -        "\t": "",
    -        "\r": "",
    -        "!": "",
    -        '"': "",
    -        "#": "",
    -        $: "",
    -        "%": "",
    -        "'": "-",
    -        "(": "",
    -        ")": "",
    -        "*": "",
    -        "+": "",
    -        ",": "",
    -        ".": "",
    -        ":": "",
    -        ";": "",
    -        "<": "",
    -        "=": "",
    -        ">": "",
    -        "?": "",
    -        "@": "",
    -        "[": "",
    -        "]": "",
    -        "^": "",
    -        "`": "",
    -        "{": "",
    -        "|": "",
    -        "}": "",
    -        "¡": "",
    -        "£": "",
    -        "¤": "",
    -        "¥": "",
    -        "¦": "",
    -        "§": "",
    -        "«": "",
    -        "°": "",
    -        "»": "",
    -        "‘": "",
    -        "’": "",
    -        "“": "",
    -        "”": "",
    -        "\n": "-",
    -        " ": "-",
    -        "-": "-",
    -        "–": "-",
    -        "—": "-",
    -        "/": "-",
    -        "\\": "-",
    -        _: "-",
    -        "~": "-",
    -        À: "A",
    -        Á: "A",
    -        Â: "A",
    -        Ã: "A",
    -        Ä: "A",
    -        Å: "A",
    -        Æ: "Ae",
    -        Ç: "C",
    -        Ð: "D",
    -        È: "E",
    -        É: "E",
    -        Ê: "E",
    -        Ë: "E",
    -        Ì: "I",
    -        Í: "I",
    -        Î: "I",
    -        Ï: "I",
    -        Ñ: "N",
    -        Ò: "O",
    -        Ó: "O",
    -        Ô: "O",
    -        Õ: "O",
    -        Ö: "O",
    -        Ø: "O",
    -        Œ: "Oe",
    -        Š: "S",
    -        Þ: "Th",
    -        Ù: "U",
    -        Ú: "U",
    -        Û: "U",
    -        Ü: "U",
    -        Ý: "Y",
    -        à: "a",
    -        á: "a",
    -        â: "a",
    -        ã: "a",
    -        ä: "ae",
    -        å: "a",
    -        æ: "ae",
    -        "¢": "c",
    -        ç: "c",
    -        ð: "d",
    -        è: "e",
    -        é: "e",
    -        ê: "e",
    -        ë: "e",
    -        ì: "i",
    -        í: "i",
    -        î: "i",
    -        ï: "i",
    -        ñ: "n",
    -        ò: "o",
    -        ó: "o",
    -        ô: "o",
    -        õ: "o",
    -        ö: "oe",
    -        ø: "o",
    -        œ: "oe",
    -        š: "s",
    -        ß: "ss",
    -        þ: "th",
    -        ù: "u",
    -        ú: "u",
    -        û: "u",
    -        ü: "ue",
    -        ý: "y",
    -        ÿ: "y",
    -        Ÿ: "y",
    -    };
    -    string = string.toLowerCase();
    -    for (const char in translate) {
    -        string = string.split(char).join(translate[char]);
    -    }
         return string
    -        .replace(/[^a-z0-9-]/g, "")
    -        .replace(/^-+|-+$/g, "")
    -        .replace(/-+/g, "-");
    +        .toLowerCase()
    +        .replace(/./g, (match) => slugMap[match] || match)
    +        .replace(/\s+/g, "-")
    +        .replace(/-+/g, "-")
    +        .replace(/^-+|-+$|[^a-z0-9-]/g, "");
    +}
    +
    +export function escapeHtml(string: string) {
    +    return string.replace(/[&<>"'`=/]/g, (match) => htmlEntityMap[match]);
     }
     
     export function validateSlug(slug: string) {
         return slug
             .toLowerCase()
    -        .replace(" ", "-")
    +        .replace(/\s+/g, "-")
             .replace(/[^a-z0-9-]/g, "");
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.