CVE-2026-117645 (GCVE-0-2026-117645)
Vulnerability from cvelistv5 – Published: 2026-10-30 20:25 – Updated: 2026-10-30 20:25
VLAI
EPSS
VEX
Title
Phoenix.HTML vulnerable to attribute injection via unvalidated attribute names in attributes_escape/1
Summary
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') vulnerability in phoenixframework phoenix_html allows an attacker who controls attribute names to inject attributes and script into rendered HTML via Phoenix.HTML.attributes_escape/1.
attributes_escape/1 in lib/phoenix_html.ex escapes attribute values but not attribute names. key_escape/1 passes a binary key through html_escape, which neutralizes quotes and angle brackets but not whitespace, so a key such as x onmouseover ends the attribute and starts a new one. An atom key is only dasherized, so any character passes, including quotes and >. Nested data, aria and phx keys take the same paths. An application is affected when it builds attribute keys from untrusted input and renders them through HEEx <tag {@attrs}>, content_tag/3, or a direct call. Before 3.0.0 the same code lived in Phoenix.HTML.Tag.
This issue affects phoenix_html: from 1.0.0 before 3.3.5 and from 4.0.0 before 4.4.0.
Severity
CWE
- CWE-79 - Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://cna.erlef.org/cves/CVE-2026-117645.html | relatedthird-party-advisory |
| https://osv.dev/vulnerability/EEF-CVE-2026-117645 | related |
| https://github.com/phoenixframework/phoenix_html/… | related |
| https://github.com/phoenixframework/phoenix_html/… | patch |
| https://github.com/phoenixframework/phoenix_html/… | patch |
Impacted products
2 products
| Vendor | Product | Version | CPE status | |
|---|---|---|---|---|
| phoenixframework | phoenix_html |
Affected:
1.0.0 , < 3.3.5
(semver)
Affected: 4.0.0 , < 4.4.0 (semver) cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:* |
||
| phoenixframework | phoenix_html |
Affected:
6ad3a1d7960891a468d54636054a98e11dfb5111 , < *
(git)
cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:* |
{
"containers": {
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Phoenix.HTML\u0027",
"\u0027Elixir.Phoenix.HTML.Tag\u0027"
],
"packageName": "phoenix_html",
"packageURL": "pkg:hex/phoenix_html",
"product": "phoenix_html",
"programFiles": [
"lib/phoenix_html.ex",
"lib/phoenix_html/tag.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:build_attrs/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:nested_attrs/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:key_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag/2"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:content_tag/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag_attrs/1"
}
],
"repo": "https://github.com/phoenixframework/phoenix_html",
"vendor": "phoenixframework",
"versions": [
{
"lessThan": "3.3.5",
"status": "affected",
"version": "1.0.0",
"versionType": "semver"
},
{
"lessThan": "4.4.0",
"status": "affected",
"version": "4.0.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Phoenix.HTML\u0027",
"\u0027Elixir.Phoenix.HTML.Tag\u0027"
],
"packageName": "phoenixframework/phoenix_html",
"packageURL": "pkg:github/phoenixframework/phoenix_html",
"product": "phoenix_html",
"programFiles": [
"lib/phoenix_html.ex",
"lib/phoenix_html/tag.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:build_attrs/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:nested_attrs/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:key_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag/2"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:content_tag/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag_attrs/1"
}
],
"repo": "https://github.com/phoenixframework/phoenix_html",
"vendor": "phoenixframework",
"versions": [
{
"changes": [
{
"at": "d9e4e6b2d9be49e25fb6d6b4fdbb2008174f01a6",
"status": "unaffected"
},
{
"at": "a47dc8944a773b7e9273d9691dc6744ec793f1de",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "6ad3a1d7960891a468d54636054a98e11dfb5111",
"versionType": "git"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*",
"versionEndExcluding": "3.3.5",
"versionStartIncluding": "1.0.0",
"vulnerable": true
},
{
"criteria": "cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*",
"versionEndExcluding": "4.4.0",
"versionStartIncluding": "4.0.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Andrew Moore"
},
{
"lang": "en",
"type": "reporter",
"value": "Andrew Moore"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Steffen Deusch"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-30T12:20:04.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027) vulnerability in phoenixframework phoenix_html allows an attacker who controls attribute names to inject attributes and script into rendered HTML via \u003ccode\u003ePhoenix.HTML.attributes_escape/1\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eattributes_escape/1\u003c/code\u003e in \u003ccode\u003elib/phoenix_html.ex\u003c/code\u003e escapes attribute values but not attribute names. \u003ccode\u003ekey_escape/1\u003c/code\u003e passes a binary key through \u003ccode\u003ehtml_escape\u003c/code\u003e, which neutralizes quotes and angle brackets but not whitespace, so a key such as \u003ccode\u003ex onmouseover\u003c/code\u003e ends the attribute and starts a new one. An atom key is only dasherized, so any character passes, including quotes and \u003ccode\u003e\u0026gt;\u003c/code\u003e. Nested \u003ccode\u003edata\u003c/code\u003e, \u003ccode\u003earia\u003c/code\u003e and \u003ccode\u003ephx\u003c/code\u003e keys take the same paths. An application is affected when it builds attribute keys from untrusted input and renders them through HEEx \u003ccode\u003e\u0026lt;tag {@attrs}\u0026gt;\u003c/code\u003e, \u003ccode\u003econtent_tag/3\u003c/code\u003e, or a direct call. Before 3.0.0 the same code lived in \u003ccode\u003ePhoenix.HTML.Tag\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThis issue affects phoenix_html: from 1.0.0 before 3.3.5 and from 4.0.0 before 4.4.0.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027) vulnerability in phoenixframework phoenix_html allows an attacker who controls attribute names to inject attributes and script into rendered HTML via `Phoenix.HTML.attributes_escape/1`.\n\n`attributes_escape/1` in `lib/phoenix_html.ex` escapes attribute values but not attribute names. `key_escape/1` passes a binary key through `html_escape`, which neutralizes quotes and angle brackets but not whitespace, so a key such as `x onmouseover` ends the attribute and starts a new one. An atom key is only dasherized, so any character passes, including quotes and `\u003e`. Nested `data`, `aria` and `phx` keys take the same paths. An application is affected when it builds attribute keys from untrusted input and renders them through HEEx `\u003ctag {@attrs}\u003e`, `content_tag/3`, or a direct call. Before 3.0.0 the same code lived in `Phoenix.HTML.Tag`.\n\nThis issue affects phoenix_html: from 1.0.0 before 3.3.5 and from 4.0.0 before 4.4.0."
}
],
"value": "Improper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027) vulnerability in phoenixframework phoenix_html allows an attacker who controls attribute names to inject attributes and script into rendered HTML via Phoenix.HTML.attributes_escape/1.\n\nattributes_escape/1 in lib/phoenix_html.ex escapes attribute values but not attribute names. key_escape/1 passes a binary key through html_escape, which neutralizes quotes and angle brackets but not whitespace, so a key such as x onmouseover ends the attribute and starts a new one. An atom key is only dasherized, so any character passes, including quotes and \u003e. Nested data, aria and phx keys take the same paths. An application is affected when it builds attribute keys from untrusted input and renders them through HEEx \u003ctag {@attrs}\u003e, content_tag/3, or a direct call. Before 3.0.0 the same code lived in Phoenix.HTML.Tag.\n\nThis issue affects phoenix_html: from 1.0.0 before 3.3.5 and from 4.0.0 before 4.4.0."
}
],
"impacts": [
{
"capecId": "CAPEC-63",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eAn attacker who can influence the attribute names an application renders can add event-handler attributes or close the tag and run script in the browser of every user who views the page.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "An attacker who can influence the attribute names an application renders can add event-handler attributes or close the tag and run script in the browser of every user who views the page."
}
],
"value": "An attacker who can influence the attribute names an application renders can add event-handler attributes or close the tag and run script in the browser of every user who views the page."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"baseScore": 5.3,
"baseSeverity": "MEDIUM",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "PASSIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "LOW",
"vulnIntegrityImpact": "LOW",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-79",
"description": "CWE-79 Improper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027)",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-30T20:25:06.758Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "EEF CNA record for CVE-2026-117645",
"tags": [
"related",
"third-party-advisory"
],
"url": "https://cna.erlef.org/cves/CVE-2026-117645.html"
},
{
"name": "OSV record EEF-CVE-2026-117645",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-117645"
},
{
"name": "Introducing commit 6ad3a1d in phoenixframework/phoenix_html",
"tags": [
"related"
],
"url": "https://github.com/phoenixframework/phoenix_html/commit/6ad3a1d7960891a468d54636054a98e11dfb5111"
},
{
"name": "Fix commit d9e4e6b in phoenixframework/phoenix_html",
"tags": [
"patch"
],
"url": "https://github.com/phoenixframework/phoenix_html/commit/d9e4e6b2d9be49e25fb6d6b4fdbb2008174f01a6"
},
{
"name": "Fix commit a47dc89 in phoenixframework/phoenix_html",
"tags": [
"patch"
],
"url": "https://github.com/phoenixframework/phoenix_html/commit/a47dc8944a773b7e9273d9691dc6744ec793f1de"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Phoenix.HTML vulnerable to attribute injection via unvalidated attribute names in attributes_escape/1",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eDo not derive attribute names from untrusted input. Where dynamic attribute keys are needed, check each key against an allow list, or against a pattern such as \u003ccode\u003e^[a-zA-Z][a-zA-Z0-9:._-]*$\u003c/code\u003e, before passing the map to \u003ccode\u003eattributes_escape/1\u003c/code\u003e or a HEEx \u003ccode\u003e{@attrs}\u003c/code\u003e expression. Do not create atoms from request data.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Do not derive attribute names from untrusted input. Where dynamic attribute keys are needed, check each key against an allow list, or against a pattern such as `^[a-zA-Z][a-zA-Z0-9:._-]*$`, before passing the map to `attributes_escape/1` or a HEEx `{@attrs}` expression. Do not create atoms from request data."
}
],
"value": "Do not derive attribute names from untrusted input. Where dynamic attribute keys are needed, check each key against an allow list, or against a pattern such as ^[a-zA-Z][a-zA-Z0-9:._-]*$, before passing the map to attributes_escape/1 or a HEEx {@attrs} expression. Do not create atoms from request data."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eInstall a vulnerable release, for example \u003ccode\u003eMix.install([{:phoenix_html, \"== 4.3.0\"}])\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRender a binary name containing a space: \u003ccode\u003ePhoenix.HTML.attributes_escape(%{\"x onmouseover\" =\u0026gt; \"alert(1)\"}) |\u0026gt; Phoenix.HTML.safe_to_string()\u003c/code\u003e returns \u003ccode\u003e x onmouseover=\"alert(1)\"\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRender an atom name containing a quote: \u003ccode\u003ePhoenix.HTML.attributes_escape([{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]) |\u0026gt; Phoenix.HTML.safe_to_string()\u003c/code\u003e returns \u003ccode\u003e x\" onclick=\"alert(1)=\"y\"\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRender a nested atom name: \u003ccode\u003ePhoenix.HTML.attributes_escape(aria: [{:\"x\\\"\u0026gt;\u0026lt;script\u0026gt;\", \"y\"}]) |\u0026gt; Phoenix.HTML.safe_to_string()\u003c/code\u003e returns \u003ccode\u003e aria-x\"\u0026gt;\u0026lt;script\u0026gt;=\"y\"\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003ePlace any of these maps in a HEEx template as \u003ccode\u003e\u0026lt;div {@attrs}\u0026gt;\u003c/code\u003e to deliver the injected attribute to a browser.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. Install a vulnerable release, for example `Mix.install([{:phoenix_html, \"== 4.3.0\"}])`.\n2. Render a binary name containing a space: `Phoenix.HTML.attributes_escape(%{\"x onmouseover\" =\u003e \"alert(1)\"}) |\u003e Phoenix.HTML.safe_to_string()` returns ` x onmouseover=\"alert(1)\"`.\n3. Render an atom name containing a quote: `Phoenix.HTML.attributes_escape([{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]) |\u003e Phoenix.HTML.safe_to_string()` returns ` x\" onclick=\"alert(1)=\"y\"`.\n4. Render a nested atom name: `Phoenix.HTML.attributes_escape(aria: [{:\"x\\\"\u003e\u003cscript\u003e\", \"y\"}]) |\u003e Phoenix.HTML.safe_to_string()` returns ` aria-x\"\u003e\u003cscript\u003e=\"y\"`.\n5. Place any of these maps in a HEEx template as `\u003cdiv {@attrs}\u003e` to deliver the injected attribute to a browser."
}
],
"value": "* Install a vulnerable release, for example Mix.install([{:phoenix_html, \"== 4.3.0\"}]).\n* Render a binary name containing a space: Phoenix.HTML.attributes_escape(%{\"x onmouseover\" =\u003e \"alert(1)\"}) |\u003e Phoenix.HTML.safe_to_string() returns x onmouseover=\"alert(1)\".\n* Render an atom name containing a quote: Phoenix.HTML.attributes_escape([{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]) |\u003e Phoenix.HTML.safe_to_string() returns x\" onclick=\"alert(1)=\"y\".\n* Render a nested atom name: Phoenix.HTML.attributes_escape(aria: [{:\"x\\\"\u003e\u003cscript\u003e\", \"y\"}]) |\u003e Phoenix.HTML.safe_to_string() returns aria-x\"\u003e\u003cscript\u003e=\"y\".\n* Place any of these maps in a HEEx template as \u003cdiv {@attrs}\u003e to deliver the injected attribute to a browser."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Attribute rendering.\u003c/strong\u003e \u003ccode\u003ePhoenix.HTML.attributes_escape/1\u003c/code\u003e walks the key-value pairs through \u003ccode\u003ebuild_attrs/1\u003c/code\u003e and \u003ccode\u003enested_attrs/3\u003c/code\u003e and emits each as \u003ccode\u003ekey_escape(k)=\"attr_escape(v)\"\u003c/code\u003e. Values are escaped. Names are not checked against the HTML attribute-name grammar.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Binary names.\u003c/strong\u003e \u003ccode\u003ekey_escape/1\u003c/code\u003e sends a binary through \u003ccode\u003ePhoenix.HTML.Engine.html_escape/1\u003c/code\u003e, which replaces \u003ccode\u003e\u0026amp;\u003c/code\u003e, \u003ccode\u003e\u0026lt;\u003c/code\u003e, \u003ccode\u003e\u0026gt;\u003c/code\u003e, \u003ccode\u003e\"\u003c/code\u003e and \u003ccode\u003e\u0027\u003c/code\u003e. Whitespace passes. HTML uses whitespace to separate attributes, so \u003ccode\u003e%{\"x onmouseover\" =\u0026gt; \"alert(1)\"}\u003c/code\u003e renders \u003ccode\u003e x onmouseover=\"alert(1)\"\u003c/code\u003e: a harmless \u003ccode\u003ex\u003c/code\u003e attribute followed by an attacker-chosen event handler.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Atom names.\u003c/strong\u003e \u003ccode\u003ekey_escape/1\u003c/code\u003e converts an atom with \u003ccode\u003eAtom.to_string/1\u003c/code\u003e and replaces \u003ccode\u003e_\u003c/code\u003e with \u003ccode\u003e-\u003c/code\u003e. Nothing is escaped, so \u003ccode\u003e[{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]\u003c/code\u003e renders \u003ccode\u003e x\" onclick=\"alert(1)=\"y\"\u003c/code\u003e and \u003ccode\u003earia: [{:\"x\\\"\u0026gt;\u0026lt;script\u0026gt;\", \"y\"}]\u003c/code\u003e renders \u003ccode\u003e aria-x\"\u0026gt;\u0026lt;script\u0026gt;=\"y\"\u003c/code\u003e, which leaves the tag entirely. This path needs the application to create atoms from untrusted input, for example \u003ccode\u003eJason.decode!(body, keys: :atoms)\u003c/code\u003e or \u003ccode\u003eString.to_atom/1\u003c/code\u003e on a parameter.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4. Reachability.\u003c/strong\u003e HEEx compiles \u003ccode\u003e\u0026lt;div {@attrs}\u0026gt;\u003c/code\u003e to a call to \u003ccode\u003ePhoenix.HTML.attributes_escape/1\u003c/code\u003e through \u003ccode\u003ePhoenix.LiveView.HTMLEngine\u003c/code\u003e and \u003ccode\u003ePhoenix.LiveView.TagEngine\u003c/code\u003e. \u003ccode\u003etag/2\u003c/code\u003e and \u003ccode\u003econtent_tag/3\u003c/code\u003e in \u003ccode\u003ePhoenix.HTML.Tag\u003c/code\u003e (phoenix_html 1.x to 3.x) and in \u003ccode\u003ePhoenixHTMLHelpers.Tag\u003c/code\u003e (4.x) render attributes the same way. Versions before 3.0.0 emitted the dasherized name without any escaping at all.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Attribute rendering.** `Phoenix.HTML.attributes_escape/1` walks the key-value pairs through `build_attrs/1` and `nested_attrs/3` and emits each as `key_escape(k)=\"attr_escape(v)\"`. Values are escaped. Names are not checked against the HTML attribute-name grammar.\n\n**2. Binary names.** `key_escape/1` sends a binary through `Phoenix.HTML.Engine.html_escape/1`, which replaces `\u0026`, `\u003c`, `\u003e`, `\"` and `\u0027`. Whitespace passes. HTML uses whitespace to separate attributes, so `%{\"x onmouseover\" =\u003e \"alert(1)\"}` renders ` x onmouseover=\"alert(1)\"`: a harmless `x` attribute followed by an attacker-chosen event handler.\n\n**3. Atom names.** `key_escape/1` converts an atom with `Atom.to_string/1` and replaces `_` with `-`. Nothing is escaped, so `[{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]` renders ` x\" onclick=\"alert(1)=\"y\"` and `aria: [{:\"x\\\"\u003e\u003cscript\u003e\", \"y\"}]` renders ` aria-x\"\u003e\u003cscript\u003e=\"y\"`, which leaves the tag entirely. This path needs the application to create atoms from untrusted input, for example `Jason.decode!(body, keys: :atoms)` or `String.to_atom/1` on a parameter.\n\n**4. Reachability.** HEEx compiles `\u003cdiv {@attrs}\u003e` to a call to `Phoenix.HTML.attributes_escape/1` through `Phoenix.LiveView.HTMLEngine` and `Phoenix.LiveView.TagEngine`. `tag/2` and `content_tag/3` in `Phoenix.HTML.Tag` (phoenix_html 1.x to 3.x) and in `PhoenixHTMLHelpers.Tag` (4.x) render attributes the same way. Versions before 3.0.0 emitted the dasherized name without any escaping at all."
}
],
"value": "1. Attribute rendering. Phoenix.HTML.attributes_escape/1 walks the key-value pairs through build_attrs/1 and nested_attrs/3 and emits each as key_escape(k)=\"attr_escape(v)\". Values are escaped. Names are not checked against the HTML attribute-name grammar.\n\n2. Binary names. key_escape/1 sends a binary through Phoenix.HTML.Engine.html_escape/1, which replaces \u0026, \u003c, \u003e, \" and \u0027. Whitespace passes. HTML uses whitespace to separate attributes, so %{\"x onmouseover\" =\u003e \"alert(1)\"} renders x onmouseover=\"alert(1)\": a harmless x attribute followed by an attacker-chosen event handler.\n\n3. Atom names. key_escape/1 converts an atom with Atom.to_string/1 and replaces _ with -. Nothing is escaped, so [{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}] renders x\" onclick=\"alert(1)=\"y\" and aria: [{:\"x\\\"\u003e\u003cscript\u003e\", \"y\"}] renders aria-x\"\u003e\u003cscript\u003e=\"y\", which leaves the tag entirely. This path needs the application to create atoms from untrusted input, for example Jason.decode!(body, keys: :atoms) or String.to_atom/1 on a parameter.\n\n4. Reachability. HEEx compiles \u003cdiv {@attrs}\u003e to a call to Phoenix.HTML.attributes_escape/1 through Phoenix.LiveView.HTMLEngine and Phoenix.LiveView.TagEngine. tag/2 and content_tag/3 in Phoenix.HTML.Tag (phoenix_html 1.x to 3.x) and in PhoenixHTMLHelpers.Tag (4.x) render attributes the same way. Versions before 3.0.0 emitted the dasherized name without any escaping at all."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-117645",
"datePublished": "2026-10-30T20:25:06.758Z",
"dateReserved": "2026-10-30T12:15:02.726Z",
"dateUpdated": "2026-10-30T20:25:06.758Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2",
"vulnerability-lookup:meta": {
"epss": {
"cve": "CVE-2026-117645",
"date": "2026-10-31",
"epss": "0.00315",
"percentile": "0.24445"
},
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Phoenix.HTML\u0027",
"\u0027Elixir.Phoenix.HTML.Tag\u0027"
],
"packageName": "phoenix_html",
"packageURL": "pkg:hex/phoenix_html",
"product": "phoenix_html",
"programFiles": [
"lib/phoenix_html.ex",
"lib/phoenix_html/tag.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:build_attrs/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:nested_attrs/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:key_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag/2"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:content_tag/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag_attrs/1"
}
],
"repo": "https://github.com/phoenixframework/phoenix_html",
"vendor": "phoenixframework",
"versions": [
{
"lessThan": "3.3.5",
"status": "affected",
"version": "1.0.0",
"versionType": "semver"
},
{
"lessThan": "4.4.0",
"status": "affected",
"version": "4.0.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Phoenix.HTML\u0027",
"\u0027Elixir.Phoenix.HTML.Tag\u0027"
],
"packageName": "phoenixframework/phoenix_html",
"packageURL": "pkg:github/phoenixframework/phoenix_html",
"product": "phoenix_html",
"programFiles": [
"lib/phoenix_html.ex",
"lib/phoenix_html/tag.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:build_attrs/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:nested_attrs/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML\u0027:key_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag/2"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:content_tag/3"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:attributes_escape/1"
},
{
"name": "\u0027Elixir.Phoenix.HTML.Tag\u0027:tag_attrs/1"
}
],
"repo": "https://github.com/phoenixframework/phoenix_html",
"vendor": "phoenixframework",
"versions": [
{
"changes": [
{
"at": "d9e4e6b2d9be49e25fb6d6b4fdbb2008174f01a6",
"status": "unaffected"
},
{
"at": "a47dc8944a773b7e9273d9691dc6744ec793f1de",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "6ad3a1d7960891a468d54636054a98e11dfb5111",
"versionType": "git"
}
]
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "Improper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027) vulnerability in phoenixframework phoenix_html allows an attacker who controls attribute names to inject attributes and script into rendered HTML via Phoenix.HTML.attributes_escape/1.\n\nattributes_escape/1 in lib/phoenix_html.ex escapes attribute values but not attribute names. key_escape/1 passes a binary key through html_escape, which neutralizes quotes and angle brackets but not whitespace, so a key such as x onmouseover ends the attribute and starts a new one. An atom key is only dasherized, so any character passes, including quotes and \u003e. Nested data, aria and phx keys take the same paths. An application is affected when it builds attribute keys from untrusted input and renders them through HEEx \u003ctag {@attrs}\u003e, content_tag/3, or a direct call. Before 3.0.0 the same code lived in Phoenix.HTML.Tag.\n\nThis issue affects phoenix_html: from 1.0.0 before 3.3.5 and from 4.0.0 before 4.4.0."
}
],
"id": "CVE-2026-117645",
"lastModified": "2026-10-30T21:16:51.170",
"metrics": {
"cvssMetricV40": [
{
"cvssData": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"availabilityRequirement": "NOT_DEFINED",
"baseScore": 5.3,
"baseSeverity": "MEDIUM",
"confidentialityRequirement": "NOT_DEFINED",
"exploitMaturity": "NOT_DEFINED",
"integrityRequirement": "NOT_DEFINED",
"modifiedAttackComplexity": "NOT_DEFINED",
"modifiedAttackRequirements": "NOT_DEFINED",
"modifiedAttackVector": "NOT_DEFINED",
"modifiedPrivilegesRequired": "NOT_DEFINED",
"modifiedSubAvailabilityImpact": "NOT_DEFINED",
"modifiedSubConfidentialityImpact": "NOT_DEFINED",
"modifiedSubIntegrityImpact": "NOT_DEFINED",
"modifiedUserInteraction": "NOT_DEFINED",
"modifiedVulnAvailabilityImpact": "NOT_DEFINED",
"modifiedVulnConfidentialityImpact": "NOT_DEFINED",
"modifiedVulnIntegrityImpact": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "PASSIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/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",
"version": "4.0",
"vulnAvailabilityImpact": "NONE",
"vulnConfidentialityImpact": "LOW",
"vulnIntegrityImpact": "LOW",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
},
"published": "2026-10-30T21:16:51.170",
"references": [
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://cna.erlef.org/cves/CVE-2026-117645.html"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/phoenixframework/phoenix_html/commit/6ad3a1d7960891a468d54636054a98e11dfb5111"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/phoenixframework/phoenix_html/commit/a47dc8944a773b7e9273d9691dc6744ec793f1de"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/phoenixframework/phoenix_html/commit/d9e4e6b2d9be49e25fb6d6b4fdbb2008174f01a6"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-117645"
}
],
"sourceIdentifier": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"vulnStatus": "Received",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-79"
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
}
}
}
}
Loading…
Loading…
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Experimental. This forecast is provided for visualization only and may change without notice. Do not use it for operational decisions.
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.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
Loading…
Loading…
The MITRE ATT&CK techniques below are AI-generated suggestions, inferred from the description of the
vulnerability by the CIRCL/vulnerability-attack-technique-classification-roberta-base
model, served locally by ML-Gateway.
They have not been verified by an analyst and are provided for guidance only.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
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.