GitHub Advisoryadvisory · npm
GHSA-f48w-9m4c-m7f5 Astro: XSS via unescaped spread attribute names in renderHTMLElement (incomplete fix for CVE-2026-54298)
MEDIUM
Official page ↗
- Aliases
- CVE-2026-59729
- Ecosystem
- npm
- Packages
- astro
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N
- Weaknesses
- CWE-79
- Reviewed
- 2026-07-20
Affected packages
• npm astro: < 7.0.6 → fixed in 7.0.6
Description
Summary
The fix for CVE-2026-54298 (GHSA-jrpj-wcv7-9fh9) added an `INVALID_ATTR_NAME_CHAR` guard to `addAttribute()` so that spread-prop attribute names containing `"' >/=` or whitespace are dropped. A second attribute-rendering path, `renderHTMLElement()` in `packages/astro/src/runtime/server/render/dom.ts`, has its own inline attribute loop that does not go through `addAttribute()` and was not updated. It interpolates the attribute name unescaped and only escapes the value, so untrusted prop keys spread onto a native-`HTMLElement`-subclass component can still break out of the attribute context, resulting in XSS.
Details
`renderHTMLElement` builds attributes directly:
```js
for (const attr in props) {
attrHTML += ` ${attr}="${toAttributeString(await props[attr])}"`;
}
```
The attribute name (`attr`) is interpolated raw; only the value is escaped via `toAttributeString`. By contrast, the hardened `addAttribute` in `util.ts` rejects invalid names:
```js
if (INVALID_ATTR_NAME_CHAR.test(key)) { return ''; } // /[\s"'>/=]/
```
`renderHTMLElement` is reached from `component.ts` when the component is a native `HTMLElement` subclass:
```js
if (!renderer && typeof HTMLElement === 'function' && componentIsHTMLElement(Component)) {
const output = await renderHTMLElement(result, Component, _props, slots);
}
```
where `_props` carries spread props verbatim.
Reachability
The branch only runs when `typeof HTMLElement === 'function'` at SSR time. In default Node SSR `HTMLElement` is `undefined`, so the branch is dead. It becomes reachable when the SSR runtime exposes a global `HTMLElement` (Deno, Bun with a DOM shim, or jsdom/happy-dom in Node) and a class extending `HTMLElement` is used directly as an Astro component that receives untrusted-keyed spread props.
Proof of Concept
Given malicious spread props:
```js
const maliciousProps = {
'onmouseover=alert(document.domain) x': 'y',
'x>alert(1)': 'z',
};
```
• `addAttribute` (post-fix) → `` (key stripped — safe)
• `renderHTMLElement` → `alert(1)="z">` (handler + `` injected — XSS)
Equivalent Astro template, served by an SSR runtime that defines a global `HTMLElement`:
```astro
---
import MyElement from '../MyElement.js'; // class MyElement extends HTMLElement {}
const userInput = Astro.url.searchParams; // untrusted keys
---
```
Impact
Cross-site scripting (CWE-79) via attribute-name breakout — the same vulnerability class as CVE-2026-54298, in a code path its fix did not cover. An attacker who controls the keys of an object spread onto a native-`HTMLElement`-subclass component can inject arbitrary event-handler attributes or sibling elements (including ``) into the SSR output. Reachability is constrained by the runtime and component preconditions described above.
Related
Alias · CVE (1)
- CVE-2026-59729 Astro is a web framework for content-driven websites. vulnerability
Weakness · CWE (1)
- CWE-79 Improper Neutralization of Input During Web Page Generation weakness
References
- https://github.com/withastro/astro/security/advisories/GHSA-f48w-9m4c-m7f5 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-59729 advisory
- https://github.com/withastro/astro/pull/17251 web
- https://github.com/withastro/astro/commit/5240e26c9dd91f9bc7140dcfacdb48d5a132830d web
- https://github.com/withastro/astro package
- https://github.com/withastro/astro/releases/tag/[email protected] web