{"repro_id":"REPRO-2026-00067","version":5,"title":"Svelte XSS via textarea bind:value in SSR","repro_type":"security","status":"published","severity":"high","description":"A server-side rendered `<textarea>` with two-way bound value does not have its value correctly escaped in the rendered HTML.","root_cause":"## Summary\nServer-side rendering of `<textarea bind:value>` in vulnerable versions of Svelte emits the bound value without HTML-escaping, so attacker-controlled content can prematurely close the `<textarea>` element and inject arbitrary markup or JavaScript into the SSR response, leading to reflected or stored XSS when delivered to a browser.\n\n## Impact\n- **Component:** `svelte` SSR renderer for `<textarea bind:value>`\n- **Affected versions:** `>= 3.0.0` and `< 3.59.2` (verified on `3.59.1`)\n- **Risk level:** High – injected scripts execute in the victim’s browser, enabling credential/session theft, CSRF, or page defacement whenever SSR output includes untrusted textarea content.\n\n## Root Cause\nDuring SSR the Svelte compiler concatenates the textarea’s bound value directly inside `<textarea>...</textarea>` without escaping special characters. Because `<textarea>` stores its value as innerHTML rather than an attribute, sequences such as `</textarea><script>` terminate the element and introduce attacker-supplied markup. The issue was fixed by commit [`a31dec5`](https://github.com/sveltejs/svelte/commit/a31dec5eb30978cff7ff4d77f4bf316841f711bc), which HTML-escapes textarea content before emission.\n\n## Reproduction Steps\n1. Execute `repro/reproduction_steps.sh`.\n2. The script bootstraps a clean project pinned to vulnerable `svelte@3.59.1`, writes a malicious textarea component, compiles it for SSR, and captures the output HTML.\n3. Reproduction succeeds when the script exits `0` after detecting the injected `</textarea><script>alert('BIM');</script>` sequence in `logs/ssr-output.html`; exit `1` means the payload was not observed.\n\n## Evidence\n- **Run log:** `logs/reproduction.log` – shows Node (`v22.21.1`) and npm (`10.9.4`) versions, dependency installation, SSR execution, and the final \"VULNERABILITY REPRODUCED\" message.\n- **SSR output:** `logs/ssr-output.html` contains `<textarea>test'\"></textarea><script>alert('BIM');</script></textarea>`, proving that hostile script tags are emitted unescaped.\n- **Install log:** `logs/npm-install.log` documents dependency installation for auditability.\n\n## Recommendations / Next Steps\n- Upgrade Svelte to version `3.59.2` or later (or the latest 4.x release) where textarea values are escaped during SSR.\n- If upgrading is not immediately possible, manually escape user-controlled textarea values before SSR or avoid binding raw user data to `<textarea>` on the server.\n- Add regression tests that verify SSR output escapes textarea contents, including cases with `</textarea>` substrings and quotes.\n- Re-scan dependent applications for additional SSR contexts (e.g., `<style>`/`<script>` bindings) that may require escaping.\n\n## Additional Notes\n- **Idempotency:** `repro/reproduction_steps.sh` was executed multiple times back-to-back, each time yielding the same successful reproduction and clean logs, confirming idempotent behavior.\n- **Limitations:** The PoC targets SSR output only; client-side rendering escapes values correctly, so the vulnerability manifests when unescaped SSR HTML is sent to browsers before hydration.","ghsa_id":"GHSA-gw32-9rmw-qwww","package":{"name":"svelte","ecosystem":"npm"},"reproduced_at":"2026-01-17T15:01:53.564380+00:00","duration_secs":529.4037036895752,"tool_calls":104,"handoffs":2,"total_cost_usd":1.0735915,"agent_costs":{"repro":1.0605902500000002,"support":0.01300125},"cost_breakdown":{"repro":{"gpt-5.1-codex":1.0605902500000002},"support":{"gpt-5.1-codex":0.01300125}},"quality":{"idempotent_verified":false,"community_verifications":0},"key_moments":[{"agent":"repro","description":"The reproduction steps script was created, marking the development of the exploit code.","index":78,"time":201371.4096546173,"title":"PoC Script Written","type":"poc_created"},{"agent":"repro","description":"The reproduction script was executed for the first time, attempting to trigger the vulnerability.","index":96,"time":251605.85236549377,"title":"PoC Executed First Time","type":"vuln_triggered"},{"agent":"repro","description":"The reproduction script was run again, indicating iterative attempts to trigger the vulnerability.","index":123,"time":325879.5602321625,"title":"PoC Re-executed","type":"vuln_triggered"},{"agent":"repro","description":"The SSR output log was read, confirming the vulnerability was successfully triggered.","index":129,"time":331063.64011764526,"title":"Exploit Output Verified","type":"confirmation"},{"agent":"repro","description":"A root cause analysis report was written, representing a key insight into the vulnerability.","index":264,"time":479313.9524459839,"title":"RCA Report Created","type":"discovery"}],"published_at":"2026-01-17T15:02:09.037056+00:00","retracted":false,"artifacts":[{"path":"repro/rca_report.md","filename":"rca_report.md","size":3110,"category":"analysis"},{"path":"repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":2329,"category":"reproduction_script"},{"path":"bundle/ticket.md","filename":"ticket.md","size":1108,"category":"ticket"},{"path":"repro/svelte-textarea-xss/package.json","filename":"package.json","size":209,"category":"other"},{"path":"repro/svelte-textarea-xss/render.js","filename":"render.js","size":536,"category":"other"},{"path":"repro/svelte-textarea-xss/Component.svelte","filename":"Component.svelte","size":120,"category":"other"},{"path":"logs/reproduction.log","filename":"reproduction.log","size":5609,"category":"log"},{"path":"logs/ssr-output.html","filename":"ssr-output.html","size":69,"category":"other"},{"path":"logs/npm-install.log","filename":"npm-install.log","size":161,"category":"log"}]}