{"containers":{"cna":{"affected":[{"collectionURL":"https://repo.hex.pm","cpes":["cpe:2.3:a:phoenixframework:phoenix_html:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["'Elixir.Phoenix.HTML'","'Elixir.Phoenix.HTML.Tag'"],"packageName":"phoenix_html","packageURL":"pkg:hex/phoenix_html","product":"phoenix_html","programFiles":["lib/phoenix_html.ex","lib/phoenix_html/tag.ex"],"programRoutines":[{"name":"'Elixir.Phoenix.HTML':attributes_escape/1"},{"name":"'Elixir.Phoenix.HTML':build_attrs/1"},{"name":"'Elixir.Phoenix.HTML':nested_attrs/3"},{"name":"'Elixir.Phoenix.HTML':key_escape/1"},{"name":"'Elixir.Phoenix.HTML.Tag':tag/2"},{"name":"'Elixir.Phoenix.HTML.Tag':content_tag/3"},{"name":"'Elixir.Phoenix.HTML.Tag':attributes_escape/1"},{"name":"'Elixir.Phoenix.HTML.Tag':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":["'Elixir.Phoenix.HTML'","'Elixir.Phoenix.HTML.Tag'"],"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":"'Elixir.Phoenix.HTML':attributes_escape/1"},{"name":"'Elixir.Phoenix.HTML':build_attrs/1"},{"name":"'Elixir.Phoenix.HTML':nested_attrs/3"},{"name":"'Elixir.Phoenix.HTML':key_escape/1"},{"name":"'Elixir.Phoenix.HTML.Tag':tag/2"},{"name":"'Elixir.Phoenix.HTML.Tag':content_tag/3"},{"name":"'Elixir.Phoenix.HTML.Tag':attributes_escape/1"},{"name":"'Elixir.Phoenix.HTML.Tag':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ännchen / EEF"}],"dateAssigned":"2026-10-30T12:20:04.000Z","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>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 <code>Phoenix.HTML.attributes_escape/1</code>.</p>\n<p><code>attributes_escape/1</code> in <code>lib/phoenix_html.ex</code> escapes attribute values but not attribute names. <code>key_escape/1</code> passes a binary key through <code>html_escape</code>, which neutralizes quotes and angle brackets but not whitespace, so a key such as <code>x onmouseover</code> ends the attribute and starts a new one. An atom key is only dasherized, so any character passes, including quotes and <code>&gt;</code>. Nested <code>data</code>, <code>aria</code> and <code>phx</code> keys take the same paths. An application is affected when it builds attribute keys from untrusted input and renders them through HEEx <code>&lt;tag {@attrs}&gt;</code>, <code>content_tag/3</code>, or a direct call. Before 3.0.0 the same code lived in <code>Phoenix.HTML.Tag</code>.</p>\n<p>This issue affects phoenix_html: from 1.0.0 before 3.3.5 and from 4.0.0 before 4.4.0.</p>"},{"base64":false,"type":"text/markdown","value":"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`.\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 `>`. 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`.\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 ('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.\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 >. 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.\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":"<p>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.</p>"},{"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 ('Cross-site Scripting')","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":"<p>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 <code>^[a-zA-Z][a-zA-Z0-9:._-]*$</code>, before passing the map to <code>attributes_escape/1</code> or a HEEx <code>{@attrs}</code> expression. Do not create atoms from request data.</p>"},{"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":"<ol>\n<li>Install a vulnerable release, for example <code>Mix.install([{:phoenix_html, \"== 4.3.0\"}])</code>.</li>\n<li>Render a binary name containing a space: <code>Phoenix.HTML.attributes_escape(%{\"x onmouseover\" =&gt; \"alert(1)\"}) |&gt; Phoenix.HTML.safe_to_string()</code> returns <code> x onmouseover=\"alert(1)\"</code>.</li>\n<li>Render an atom name containing a quote: <code>Phoenix.HTML.attributes_escape([{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]) |&gt; Phoenix.HTML.safe_to_string()</code> returns <code> x\" onclick=\"alert(1)=\"y\"</code>.</li>\n<li>Render a nested atom name: <code>Phoenix.HTML.attributes_escape(aria: [{:\"x\\\"&gt;&lt;script&gt;\", \"y\"}]) |&gt; Phoenix.HTML.safe_to_string()</code> returns <code> aria-x\"&gt;&lt;script&gt;=\"y\"</code>.</li>\n<li>Place any of these maps in a HEEx template as <code>&lt;div {@attrs}&gt;</code> to deliver the injected attribute to a browser.</li>\n</ol>"},{"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\" => \"alert(1)\"}) |> 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\"}]) |> Phoenix.HTML.safe_to_string()` returns ` x\" onclick=\"alert(1)=\"y\"`.\n4. Render a nested atom name: `Phoenix.HTML.attributes_escape(aria: [{:\"x\\\"><script>\", \"y\"}]) |> Phoenix.HTML.safe_to_string()` returns ` aria-x\"><script>=\"y\"`.\n5. Place any of these maps in a HEEx template as `<div {@attrs}>` 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\" => \"alert(1)\"}) |> 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\"}]) |> Phoenix.HTML.safe_to_string() returns  x\" onclick=\"alert(1)=\"y\".\n* Render a nested atom name: Phoenix.HTML.attributes_escape(aria: [{:\"x\\\"><script>\", \"y\"}]) |> Phoenix.HTML.safe_to_string() returns  aria-x\"><script>=\"y\".\n* Place any of these maps in a HEEx template as <div {@attrs}> to deliver the injected attribute to a browser."}],"x_technicalAnalysis":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p><strong>1. Attribute rendering.</strong> <code>Phoenix.HTML.attributes_escape/1</code> walks the key-value pairs through <code>build_attrs/1</code> and <code>nested_attrs/3</code> and emits each as <code>key_escape(k)=\"attr_escape(v)\"</code>. Values are escaped. Names are not checked against the HTML attribute-name grammar.</p>\n<p><strong>2. Binary names.</strong> <code>key_escape/1</code> sends a binary through <code>Phoenix.HTML.Engine.html_escape/1</code>, which replaces <code>&amp;</code>, <code>&lt;</code>, <code>&gt;</code>, <code>\"</code> and <code>'</code>. Whitespace passes. HTML uses whitespace to separate attributes, so <code>%{\"x onmouseover\" =&gt; \"alert(1)\"}</code> renders <code> x onmouseover=\"alert(1)\"</code>: a harmless <code>x</code> attribute followed by an attacker-chosen event handler.</p>\n<p><strong>3. Atom names.</strong> <code>key_escape/1</code> converts an atom with <code>Atom.to_string/1</code> and replaces <code>_</code> with <code>-</code>. Nothing is escaped, so <code>[{:\"x\\\" onclick=\\\"alert(1)\", \"y\"}]</code> renders <code> x\" onclick=\"alert(1)=\"y\"</code> and <code>aria: [{:\"x\\\"&gt;&lt;script&gt;\", \"y\"}]</code> renders <code> aria-x\"&gt;&lt;script&gt;=\"y\"</code>, which leaves the tag entirely. This path needs the application to create atoms from untrusted input, for example <code>Jason.decode!(body, keys: :atoms)</code> or <code>String.to_atom/1</code> on a parameter.</p>\n<p><strong>4. Reachability.</strong> HEEx compiles <code>&lt;div {@attrs}&gt;</code> to a call to <code>Phoenix.HTML.attributes_escape/1</code> through <code>Phoenix.LiveView.HTMLEngine</code> and <code>Phoenix.LiveView.TagEngine</code>. <code>tag/2</code> and <code>content_tag/3</code> in <code>Phoenix.HTML.Tag</code> (phoenix_html 1.x to 3.x) and in <code>PhoenixHTMLHelpers.Tag</code> (4.x) render attributes the same way. Versions before 3.0.0 emitted the dasherized name without any escaping at all.</p>"},{"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 `&`, `<`, `>`, `\"` and `'`. Whitespace passes. HTML uses whitespace to separate attributes, so `%{\"x onmouseover\" => \"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\\\"><script>\", \"y\"}]` renders ` aria-x\"><script>=\"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 `<div {@attrs}>` 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 &, <, >, \" and '. Whitespace passes. HTML uses whitespace to separate attributes, so %{\"x onmouseover\" => \"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\\\"><script>\", \"y\"}] renders  aria-x\"><script>=\"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 <div {@attrs}> 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"}