GHSA-X8QP-WQQM-57PH

Vulnerability from github – Published: 2025-07-16 19:32 – Updated: 2025-07-17 20:58
VLAI
Summary
vue-i18n's escapeParameterHtml does not prevent DOM-based XSS through its tag attributes
Details

Summary

The escapeParameterHtml: true option in Vue I18n is designed to protect against HTML/script injection by escaping interpolated parameters. However, this setting fails to prevent execution of certain tag-based payloads, such as <img src=x onerror=...>, if the interpolated value is inserted inside an HTML context using v-html.

This may lead to a DOM-based XSS vulnerability, even when using escapeParameterHtml: true, if a translation string includes minor HTML and is rendered via v-html.

Details

When escapeParameterHtml: true is enabled, it correctly escapes common injection points.

However, it does not sanitize entire attribute contexts, which can be used as XSS vectors via:

<img src=x onerror=alert(1)>

PoC

In your Vue I18n configuration:

const i18n = createI18n({
  escapeParameterHtml: true,
  messages: {
    en: {
      vulnerable: 'Caution: <img src=x onerror="{payload}">'
    }
  }
});

Use this interpolated payload:

const payload = '<script>alert("xss")</script>'; Render the translation using v-html (even not using v-html):

<p v-html="$t('vulnerable', { payload })"></p> Expected: escaped content should render as text, not execute.

Actual: script executes in some environments (or the payload is partially parsed as HTML).

Impact

This creates a DOM-based Cross-Site Scripting (XSS) vulnerability despite enabling a security option (escapeParameterHtml) .

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "vue-i18n"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.0.0"
            },
            {
              "fixed": "9.14.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "vue-i18n"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.0.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "vue-i18n"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.0"
            },
            {
              "fixed": "11.1.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.0.0"
            },
            {
              "fixed": "9.14.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.0.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.0"
            },
            {
              "fixed": "11.1.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/core-base"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.0.0"
            },
            {
              "fixed": "9.14.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/core-base"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.0.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/core-base"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.0"
            },
            {
              "fixed": "11.1.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/vue-i18n-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.2.0"
            },
            {
              "fixed": "9.14.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/vue-i18n-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.0.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@intlify/vue-i18n-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.0"
            },
            {
              "fixed": "11.1.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "petite-vue-i18n"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.0.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "petite-vue-i18n"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.0"
            },
            {
              "fixed": "11.1.10"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-53892"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-07-16T19:32:48Z",
    "nvd_published_at": "2025-07-16T14:15:28Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\nThe escapeParameterHtml: true option in Vue I18n is designed to protect against HTML/script injection by escaping interpolated parameters. However, this setting fails to prevent execution of certain tag-based payloads, such as `\u003cimg src=x onerror=...\u003e`, if the interpolated value is inserted inside an HTML context using v-html.\n\nThis may lead to a DOM-based XSS vulnerability, even when using escapeParameterHtml: true, if a translation string includes minor HTML and is rendered via v-html.\n\n### Details\n\nWhen escapeParameterHtml: true is enabled, it correctly escapes common injection points.\n\nHowever, it does not sanitize entire attribute contexts, which can be used as XSS vectors via:\n\n`\u003cimg src=x onerror=alert(1)\u003e\n`\n### PoC\nIn your Vue I18n configuration:\n\n```\nconst i18n = createI18n({\n  escapeParameterHtml: true,\n  messages: {\n    en: {\n      vulnerable: \u0027Caution: \u003cimg src=x onerror=\"{payload}\"\u003e\u0027\n    }\n  }\n});\n```\nUse this interpolated payload:\n\n`const payload = \u0027\u003cscript\u003ealert(\"xss\")\u003c/script\u003e\u0027;`\nRender the translation using v-html (even not using v-html):\n\n`\u003cp v-html=\"$t(\u0027vulnerable\u0027, { payload })\"\u003e\u003c/p\u003e\n`\nExpected: escaped content should render as text, not execute.\n\nActual: script executes in some environments (or the payload is partially parsed as HTML).\n\n### Impact\n\nThis creates a DOM-based Cross-Site Scripting (XSS) vulnerability despite enabling a security option (escapeParameterHtml) .",
  "id": "GHSA-x8qp-wqqm-57ph",
  "modified": "2025-07-17T20:58:49Z",
  "published": "2025-07-16T19:32:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/security/advisories/GHSA-x8qp-wqqm-57ph"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-53892"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/pull/2229"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/pull/2230"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/commit/49f982443ab8fd94ecc427b265ce97d57df94d7e"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/commit/a47099619fb9b256e86341a8658ebe72e92ab099"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/intlify/vue-i18n"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/releases/tag/v10.0.8"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/releases/tag/v11.1.10"
    },
    {
      "type": "WEB",
      "url": "https://github.com/intlify/vue-i18n/releases/tag/v9.14.5"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "vue-i18n\u0027s escapeParameterHtml does not prevent DOM-based XSS through its tag attributes"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…