{"id":"CVE-2026-118257","kind":"vulnerability","aliases":["CVE-2026-118257","GHSA-83rm-6c8r-g728"],"related":[],"title":"Vue I18n: escapeParameter does not prevent XSS through srcdoc in translation messages","description":"### Summary\n\nWhen `escapeParameter` is enabled (`escapeParameterHtml` in the Legacy API), Vue I18n passes the translated string through `sanitizeTranslatedHtml()`. The sanitizer escapes `<` and `>` in attribute values and neutralizes event handler attributes such as `onerror`, but it does not handle the `srcdoc` attribute of `<iframe>`.\n\nThe value of `srcdoc` is an HTML document: the browser decodes the character references in it and parses the result as a nested document in the same origin. The escaping and the neutralization done by the sanitizer are both character references, so they are undone inside `srcdoc`. This bypasses the fix for CVE-2025-53892 (GHSA-x8qp-wqqm-57ph).\n\n### Details\n\n`sanitizeTranslatedHtml()` ([`packages/shared/src/sanitize.ts` in v11.4.13](https://github.com/intlify/vue-i18n/blob/v11.4.13/packages/shared/src/sanitize.ts#L126-L162)) works on the final translated string:\n\n1. It escapes `<`, `>`, `\"` and `'` inside quoted attribute values.\n2. It rewrites the `on` of event handler attributes to `&#111;n`.\n3. It replaces `javascript:` URLs in `href`, `src`, `action` and `formaction` with `about:blank`.\n\nInside `srcdoc`, steps 1 and 2 only produce character references, which the browser decodes before parsing the value as a document. Markup inside `srcdoc` therefore survives the sanitizer, with escaping or without it.\n\nThis can be reached in three ways:\n\n- **A parameter inside `srcdoc`.** With a message such as `<iframe srcdoc=\"{content}\"></iframe>`, `escapeParameter` escapes `content` into character references, and the browser decodes them again.\n- **Markup in a message.** A message that contains an `<iframe srcdoc=\"...\">` keeps it after sanitizing. The documentation asks not to use HTML messages with content from users, but it also says that the sanitizer neutralizes event handlers in the final HTML.\n- **Together with GHSA-8rc2-428q-7ww3.** When a call passes a list and named values, the named values are not escaped. An `<iframe srcdoc=\"...\">` in a named value then reaches the sanitizer as markup, even with an ordinary message such as `{0} {name}`.\n\n### PoC\n\n```js\nimport { createI18n } from 'vue-i18n'\n\nconst i18n = createI18n({\n  legacy: false,\n  locale: 'en',\n  escapeParameter: true,\n  messages: {\n    en: { message: '<iframe srcdoc=\"<b>x</b>\"></iframe>' }\n  }\n})\n\nconsole.log(i18n.global.t('message'))\n// v11.4.13: <iframe srcdoc=\"&lt;b&gt;x&lt;/b&gt;\"></iframe>\n```\n\nThe browser decodes the value and renders `<b>x</b>` as a separate document inside the `<iframe>`, in the same origin as the page. Any markup in that position is parsed the same way.\n\n### Impact\n\nApplications are affected when they enable `escapeParameter`, render translations as HTML (for example with `v-html`), and one of the following applies:\n\n- an untrusted value is passed as a parameter that the message places inside `srcdoc`,\n- message content is not fully trusted (for example crowdsourced or stored translations), or\n- untrusted named values are passed in the same call as a list (GHSA-8rc2-428q-7ww3).\n\nAn attacker can then run script in the origin of the application (DOM-based XSS), despite the sanitizer.\n\nRendering through `<i18n-t>` is not affected, since it does not render translations as HTML.\n\n### Patches\n\nFixed in 11.4.14 and 12.0.0-alpha.5. The sanitizer now neutralizes the `srcdoc` attribute itself, in the same way as event handler attributes.\n\nv9 and v10 are no longer supported and will not be fixed. Please migrate to v11.\n\n### Workarounds\n\n- Do not render translations that can contain untrusted content with `v-html`. Use component interpolation (`<i18n-t>`) instead.\n- Do not put `srcdoc` in translation messages, and do not pass values into it.\n\n### References\n\n- [CWE-79](https://cwe.mitre.org/data/definitions/79.html), [CWE-116](https://cwe.mitre.org/data/definitions/116.html)\n- [GHSA-x8qp-wqqm-57ph](https://github.com/intlify/vue-i18n/security/advisories/GHSA-x8qp-wqqm-57ph) (CVE-2025-53892): the fix that added `sanitizeTranslatedHtml()`\n- [GHSA-8rc2-428q-7ww3](https://github.com/intlify/vue-i18n/security/advisories/GHSA-8rc2-428q-7ww3): named values are not escaped when a list is also passed\n- [HTML Standard: the `srcdoc` attribute](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-srcdoc)","summary":"Vue I18n's sanitizeTranslatedHtml() escapes markup inside an iframe srcdoc value but leaves the srcdoc attribute active, so browsers parse the value as a same-origin nested document. An attacker who controls translation content or parameters can run script in the page when the app uses escapeParameter and renders translations with v-html.","severity":"high","cvss":7.2,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"vue-i18n","fixed":[],"affected":"= 9.14.5","ecosystem":"npm"},{"name":"vue-i18n","fixed":[],"affected":"= 10.0.8","ecosystem":"npm"},{"name":"vue-i18n","fixed":["11.4.14"],"affected":">= 11.1.10, < 11.4.14","ecosystem":"npm"},{"name":"vue-i18n","fixed":["12.0.0-alpha.5"],"affected":"= 12.0.0-alpha.4","ecosystem":"npm"},{"name":"petite-vue-i18n","fixed":[],"affected":"= 9.14.5","ecosystem":"npm"},{"name":"petite-vue-i18n","fixed":[],"affected":"= 10.0.8","ecosystem":"npm"},{"name":"petite-vue-i18n","fixed":["11.4.14"],"affected":">= 11.1.10, < 11.4.14","ecosystem":"npm"},{"name":"petite-vue-i18n","fixed":["12.0.0-alpha.5"],"affected":"= 12.0.0-alpha.4","ecosystem":"npm"},{"name":"@intlify/core-base","fixed":[],"affected":"= 9.14.5","ecosystem":"npm"},{"name":"@intlify/core-base","fixed":[],"affected":"= 10.0.8","ecosystem":"npm"},{"name":"@intlify/core-base","fixed":["11.4.14"],"affected":">= 11.1.10, < 11.4.14","ecosystem":"npm"},{"name":"@intlify/core-base","fixed":["12.0.0-alpha.5"],"affected":"= 12.0.0-alpha.4","ecosystem":"npm"},{"name":"@intlify/core","fixed":[],"affected":"= 9.14.5","ecosystem":"npm"},{"name":"@intlify/core","fixed":[],"affected":"= 10.0.8","ecosystem":"npm"},{"name":"@intlify/core","fixed":["11.4.14"],"affected":">= 11.1.10, < 11.4.14","ecosystem":"npm"},{"name":"@intlify/core","fixed":["12.0.0-alpha.5"],"affected":"= 12.0.0-alpha.4","ecosystem":"npm"},{"name":"@intlify/vue-i18n-core","fixed":[],"affected":"= 9.14.5","ecosystem":"npm"},{"name":"@intlify/vue-i18n-core","fixed":[],"affected":"= 10.0.8","ecosystem":"npm"},{"name":"@intlify/vue-i18n-core","fixed":["11.4.14"],"affected":">= 11.1.10, < 11.4.14","ecosystem":"npm"},{"name":"@intlify/vue-i18n-core","fixed":["12.0.0-alpha.5"],"affected":"= 12.0.0-alpha.4","ecosystem":"npm"},{"name":"@intlify/shared","fixed":[],"affected":"= 9.14.5","ecosystem":"npm"},{"name":"@intlify/shared","fixed":[],"affected":"= 10.0.8","ecosystem":"npm"},{"name":"@intlify/shared","fixed":["11.4.14"],"affected":">= 11.1.10, < 11.4.14","ecosystem":"npm"},{"name":"@intlify/shared","fixed":["12.0.0-alpha.5"],"affected":"= 12.0.0-alpha.4","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["cwe-116","cwe-79","frontend","javascript","node","package","vue","vue-i18n"],"not_affected_if":["escapeparameter-off","i18n-t-only","no-v-html"],"how_to_check":"- Check the installed `vue-i18n` version in package.json and the lockfile; affected ranges are 11.1.10 to before 11.4.14, 12.0.0-alpha.4 to before 12.0.0-alpha.5, and 9.14.5, 10.0.8 and earlier (unsupported)\n- Search the codebase for `escapeParameter` and for `v-html` used on translated strings (`$t`, `t(`)","fix":"- Upgrade `vue-i18n` to `11.4.14` or later on the 11.x line, or to `12.0.0-alpha.5` or later on the 12.x alpha line\n- Versions 9.x and 10.x are no longer supported; move to a supported line\n- Until upgraded, stop rendering translated strings with v-html when escapeParameter is enabled","status":"open","published_at":"2026-10-28T19:17:30+00:00","first_seen_at":"2026-10-28T19:34:55+00:00","triaged_at":"2026-10-28T19:36:01+00:00","updated_at":"2026-10-28T21:07:44+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-28T19:36:01Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade `vue-i18n` to `11.4.14` or later on the 11.x line, or to `12.0.0-alpha.5` or later on the 12.x alpha line\n- Versions 9.x and 10.x are no longer supported; move to a supported line\n- Until upgraded, stop rendering translated strings with v-html when escapeParameter is enabled","reach":"internet","title":"Vue I18n: iframe srcdoc sanitizer bypass allows script execution via v-html","impact":"high","summary":"Vue I18n's sanitizeTranslatedHtml() escapes markup inside an iframe srcdoc value but leaves the srcdoc attribute active, so browsers parse the value as a same-origin nested document. An attacker who controls translation content or parameters can run script in the page when the app uses escapeParameter and renders translations with v-html.","category":"package","nicknames":[],"who_cares":["vue","vue-i18n","frontend","javascript","node"],"confidence":0.8,"needs_auth":"unknown","worst_case":"Attacker script runs in the victim's browser session on the app origin","analyst_note":"","how_to_check":"- Check the installed `vue-i18n` version in package.json and the lockfile; affected ranges are 11.1.10 to before 11.4.14, 12.0.0-alpha.4 to before 12.0.0-alpha.5, and 9.14.5, 10.0.8 and earlier (unsupported)\n- Search the codebase for `escapeParameter` and for `v-html` used on translated strings (`$t`, `t(`)","needs_analyst":false,"not_affected_if":["i18n-t-only","no-v-html","escapeParameter-off"],"who_is_affected":"Applications using Vue I18n in the affected ranges that set escapeParameter and render translation output as HTML through v-html. Apps that render translations as text, or use the i18n-t component, are not affected by this issue according to the sources. Teams on the unsupported 9.x and 10.x lines are affected and have no upstream fix line.","user_interaction":"yes","needs_special_setup":"yes","severity_if_unknown":"unknown"},"cost_usd":0.000716,"input_tokens":3474,"output_tokens":737,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-116':69B,660C '-118257':15A,23A '-2025':230C,673C '-2026':14A,22A '-53892':231C,674C '-79':71B,655C '/data/definitions/116.html)':663C '/data/definitions/79.html),':658C '/intlify/vue-i18n/blob/v11.4.13/packages/shared/src/sanitize.ts#l126-l162))':244C '/intlify/vue-i18n/security/advisories/ghsa-x8qp-wqqm-57ph)':671C '0':424C '1':251C,287C '11.4.14':585C '12.0.0':587C '2':259C,289C '3':270C '428q':389C,548C '57ph':236C,668C '6c8r':19A '7ww3':390C,549C '83rm':18A '8rc2':388C,547C 'action':278C 'activ':96B 'add':678C 'affect':485C,573C 'alpha.5':588C 'also':372C 'api':144C 'app':125B 'appli':505C 'applic':483C,561C 'ask':360C 'attack':110B,551C 'attribut':95B,158C,164C,175C,257C,267C,595C,604C 'base':564C 'blank':283C 'browser':98B,186C,296C,341C,452C 'bypass':225C 'call':393C,541C 'charact':189C,216C,292C,337C 'compon':636C 'console.log':446C 'const':434C 'contain':352C,628C 'content':114B,335C,367C,521C,630C 'control':112B 'createi18n':429C,436C 'crowdsourc':528C 'cve':13A,21A,229C,672C 'cwe':68B,70B,654C,659C 'cwe.mitre.org':657C,662C 'cwe.mitre.org/data/definitions/116.html)':661C 'cwe.mitre.org/data/definitions/79.html),':656C 'decod':187C,297C,342C,453C 'despit':566C 'detail':237C 'document':108B,184C,200C,304C,359C,462C 'dom':563C 'dom-bas':562C 'done':210C 'en':440C,444C 'enabl':139C,488C 'escap':84B,155C,206C,253C,313C,334C,405C 'escapeparamet':3A,127B,137C,333C,441C,489C 'escapeparameterhtml':140C 'even':417C 'event':162C,265C,378C,602C 'exampl':495C,527C 'fals':438C 'final':248C,382C 'fix':227C,583C,616C,676C 'follow':504C 'formact':280C 'frontend':72B 'fulli':524C 'g728':20A 'ghsa':17A,233C,387C,546C,665C 'ghsa-83rm-6c8r-g728':16A 'ghsa-8rc2-428q-7ww3':386C,545C 'ghsa-x8qp-wqqm-57ph':232C,664C 'github.com':243C,670C 'github.com/intlify/vue-i18n/blob/v11.4.13/packages/shared/src/sanitize.ts#l126-l162))':242C 'github.com/intlify/vue-i18n/security/advisories/ghsa-x8qp-wqqm-57ph)':669C 'handl':172C 'handler':163C,266C,379C,603C 'href':276C 'html':134B,183C,364C,383C,493C,499C,581C,634C 'i18n':2A,26A,29A,32A,35A,39A,43A,47A,51A,79B,81B,146C,433C,435C 'i18n.global':447C 'ifram':88B 'impact':482C 'import':428C 'insid':86B,222C,255C,284C,306C,326C,463C,518C 'instead':638C 'interpol':637C 'intlify/core':56A,57A,58A,59A 'intlify/core-base':52A,53A,54A,55A 'intlify/shared':64A,65A,66A,67A 'intlify/vue-i18n-core':60A,61A,62A,63A 'javascript':73B,273C 'js':427C 'keep':354C 'leav':92B 'legaci':143C,437C 'list':396C,544C 'local':439C 'longer':610C 'markup':85B,305C,345C,416C,473C 'messag':12A,330C,348C,350C,365C,421C,443C,445C,449C,516C,520C,645C 'migrat':618C 'n':269C 'name':398C,401C,409C,425C,534C 'nest':107B,199C 'neutral':161C,209C,377C,592C 'node':74B 'one':501C 'onerror':167C 'ordinari':420C 'origin':106B,204C,468C,558C 'packag':75B 'packages/shared/src/sanitize.ts':239C 'page':122B,471C 'paramet':116B,325C,513C 'pars':99B,194C,299C,478C 'pass':147C,394C,510C,537C,649C 'patch':582C 'petite':37A,41A,45A,49A 'petite-vue-i18n':36A,40A,44A,48A 'place':517C 'pleas':617C 'poc':426C 'posit':476C 'prevent':6A 'produc':291C 'put':641C 'quot':256C 'reach':320C,412C 'refer':190C,217C,293C,338C,653C 'render':129B,457C,490C,569C,578C,624C 'replac':272C 'result':196C 'rewrit':261C 'run':118B,554C 'same-origin':104B 'sanit':154C,213C,311C,357C,376C,414C,568C,590C 'sanitizetran':679C 'sanitizetranslatedhtml':83B,152C,238C 'say':373C 'script':119B,555C 'separ':461C 'sinc':574C 'src':277C 'srcdoc':9A,89B,94B,174C,180C,223C,285C,307C,327C,519C,594C,642C 'step':286C 'store':530C 'string':150C,250C 'summari':135C 'support':611C 'surviv':309C 'therefor':308C 'three':322C 'togeth':384C 'translat':11A,113B,130B,149C,249C,491C,531C,579C,625C,644C 'true':442C 'trust':525C 'undon':221C 'untrust':507C,533C,629C 'url':274C 'use':126B,363C,635C 'user':369C 'v':133B,498C,633C 'v-html':132B,497C,632C 'v10':607C 'v11':620C 'v11.4.13':241C,450C 'v9':605C 'valu':90B,101B,159C,178C,258C,301C,399C,402C,410C,455C,508C,535C,650C 'vue':1A,25A,28A,31A,34A,38A,42A,46A,50A,76B,78B,80B,145C,432C 'vue-i18n':24A,27A,30A,33A,77B,431C 'way':323C,481C,600C 'without':315C 'work':245C 'workaround':621C 'wqqm':235C,667C 'x':458C 'x8qp':234C,666C 'xss':7A,565C","nicknames":[],"mitigations":[],"who_affected":"Applications using Vue I18n in the affected ranges that set escapeParameter and render translation output as HTML through v-html. Apps that render translations as text, or use the i18n-t component, are not affected by this issue according to the sources. Teams on the unsupported 9.x and 10.x lines are affected and have no upstream fix line.","tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-28T19:17:30+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"nvd","external_id":"CVE-2026-118257","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-118257","severity":null,"published_at":"2026-10-28T19:17:30+00:00","modified_at":"2026-10-28T19:17:30+00:00","first_seen_at":"2026-10-28T19:34:55+00:00","fetched_at":"2026-10-28T19:34:55+00:00","note":null},{"source":"ghsa","external_id":"GHSA-83rm-6c8r-g728","url":"https://github.com/advisories/GHSA-83rm-6c8r-g728","severity":"high","published_at":"2026-10-28T21:06:24+00:00","modified_at":"2026-10-28T21:06:25+00:00","first_seen_at":"2026-10-28T21:07:44+00:00","fetched_at":"2026-10-28T21:07:44+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}