GitHub Advisoryadvisory · npm

GHSA-4g3v-8h47-v7g6 Astro: Reflected XSS via unescaped View Transition animation properties

MEDIUM Published 2026-07-20 Modified 2026-08-12 Official page ↗
Aliases
CVE-2026-73422
Ecosystem
npm
Packages
astro
Severity
Moderate
CVSS vector
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
Weaknesses
CWE-79
Reviewed
2026-07-20

Affected packages

• npm astro: >= 2.9.0, < 7.1.0 → fixed in 7.1.0

Description

Summary Astro's server-side View Transition CSS generator interpolates animation properties into an inline `` element without escaping them for the CSS and HTML contexts. An attacker-controlled value passed to an animation property such as `duration` can contain a `` sequence, terminate the generated style element, and inject arbitrary HTML or JavaScript. This is similar to GHSA-8hv8-536x-4wqp, but exploits a different injection point: unescaped View Transition animation values in a server-generated `` element rather than an unescaped slot name in a hydration template. Like GHSA-8hv8-536x-4wqp, exploitation requires an application to pass attacker-controlled data to an Astro API. However, the value is subsequently inserted into the HTML response without context-appropriate escaping by Astro. Details `packages/astro/src/runtime/server/transition.ts` The generated stylesheet is wrapped in a `` element and marked as HTML-safe: ```ts const css = sheet.toString(); result._metadata.extraHead.push(markHTMLString(`${css}`)); ``` Animation properties are added to the stylesheet without escaping: ```ts if (anim.duration) { addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration)); } ``` For string values, `toTimeValue()` returns the input unchanged: ```ts export function toTimeValue(num: number | string) { return typeof num === 'number' ? num + 'ms' : num; } ``` As a result, a `duration` value containing `` can escape from the generated style element. Other `TransitionAnimation` properties, including `easing`, `direction`, `delay`, `fillMode`, and `name`, are serialized by the same animation builder. The following PoC only relies on the official `fade()` helper and its `duration` option. PoC Using: • `[email protected]` • `@astrojs/[email protected]` `astro.config.mjs` ```js import node from '@astrojs/node'; import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), }); ``` `src/pages/index.astro` ```astro --- import { fade } from 'astro:transitions'; const duration = Astro.url.searchParams.get('duration') ?? '300ms'; --- PoC Animated content ``` Payload: open: ```text http://localhost:4321/?duration=%3C%2Fstyle%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E%3C!-- ``` The browser interprets `` as the end of the generated style element and executes the injected script. An alert dialog is displayed when the page is opened. Impact An attacker who can control a View Transition animation value can execute arbitrary JavaScript in the origin of the affected Astro application. The query-based reflected XSS scenario affects on-demand/server-rendered routes, such as: • projects configured with `output: "server"`; • pages using `export const prerender = false`; • other server-side data flows that pass attacker-controlled values into a View Transition animation definition. Successful exploitation may allow access to sensitive page data and authenticated actions available to the victim. Suggested Fix Animation values should be serialized using context-appropriate CSS escaping or validation before being added to the generated stylesheet. Additionally, content inserted into a raw `` element must not be able to contain an HTML end-tag sequence such as ``. The final generated CSS should be made safe for the HTML raw-text context before it is passed to `markHTMLString()`.

Related

Alias · CVE (1)

  • CVE-2026-73422 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