{"id":"GHSA-f4j9-crrj-867q","kind":"vulnerability","aliases":["GHSA-f4j9-crrj-867q"],"related":[],"title":"Reflected XSS via unescaped slot names in @astrojs/svelte","description":"## Summary\n\nThe `@astrojs/svelte` renderer wraps slotted content passed to Svelte components in an `<astro-slot>` element, and writes the slot name into its `name` attribute without HTML escaping. A slot name containing `\"` could end the attribute and inject arbitrary HTML into the page.\n\nFor example, a page that takes a slot name from the request:\n\n```astro\n---\nimport Tabs from '../components/Tabs.svelte';\nconst tab = Astro.url.searchParams.get('tab');\n---\n<Tabs client:load active={tab}><div slot={tab}>…</div></Tabs>\n```\n\nrendered with a Svelte 5 component that renders its snippets by name:\n\n```svelte\n<script>\n\tlet { active, ...snippets } = $props();\n</script>\n{@render snippets[active]?.()}\n```\n\nwould output a `tab` value such as `x\"><img src=x onerror=alert(1)>` as live HTML.\n\n## Impact\n\nReflected cross-site scripting. An attacker who gets a user to open a crafted link can run JavaScript on the site's origin, but only in applications where all of the following are true:\n\n- The page passes request-controlled input (such as a query parameter) as a slot name.\n- The Svelte component is hydrated with a `client:*` directive. For non-hydrated components the slot wrapper is removed from the output, together with its `name` attribute.\n- The Svelte component renders the attacker-chosen slot, which in practice means a Svelte 5 component that renders snippets by a dynamic name. Components that never render the slot only output Astro's already escaped fallback template.\n\nThis is the `@astrojs/svelte` counterpart of GHSA-8hv8-536x-4wqp (CVE-2026-50146), which fixed the same issue in `astro` core. The same unescaped value was also used when hydrating the component in the browser.\n\n## Affected versions\n\n`@astrojs/svelte` >= 5.7.3, <= 9.1.1.\n\n## Patches\n\nFixed in `@astrojs/svelte` 9.1.2. Slot names are now HTML-escaped in the server renderer and in the client-side hydration code.\n\n## Workarounds\n\nUpgrade to `@astrojs/svelte` 9.1.2 or later. Before upgrading, do not use request-controlled input as a slot name, or map it to a fixed set of known slot names before passing it to a component.\n\n## Credits\n\nReported by @manus-pi.","summary":"The @astrojs/svelte renderer writes slot names into an HTML attribute without escaping them, so a crafted slot name can break out and inject HTML. An attacker who gets a user to open a crafted link can run JavaScript on the site's origin, but only under specific conditions.","severity":"medium","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"@astrojs/svelte","fixed":["9.1.2"],"affected":">= 5.7.3, <= 9.1.1","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["astro","cwe-79","npm","package","self-hosted","svelte"],"not_affected_if":["auth-required","default-config","static-export"],"how_to_check":"- Check the installed version: `npm ls @astrojs/svelte`\n- Look for pages that pass request input (such as a query parameter) as a slot name to a Svelte component with a `client:*` directive\n- Look for Svelte 5 components that render snippets by a dynamic name, such as `{@render snippets[active]?.()}`","fix":"- Upgrade `@astrojs/svelte` to `9.1.2` or later\n- Affected range: `>= 5.7.3, <= 9.1.1`\n- Until upgraded, do not use request-controlled input as a slot name, or map it to a fixed set of known slot names","status":"open","published_at":"2026-10-28T19:27:20+00:00","first_seen_at":"2026-11-09T22:53:17+00:00","triaged_at":"2026-11-09T22:58:52+00:00","updated_at":"2026-11-09T22:58:52+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-09T22:58:52Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade `@astrojs/svelte` to `9.1.2` or later\n- Affected range: `>= 5.7.3, <= 9.1.1`\n- Until upgraded, do not use request-controlled input as a slot name, or map it to a fixed set of known slot names","reach":"internet","title":"Astro Svelte renderer: unescaped slot names allow reflected cross-site scripting","impact":"medium","summary":"The @astrojs/svelte renderer writes slot names into an HTML attribute without escaping them, so a crafted slot name can break out and inject HTML. An attacker who gets a user to open a crafted link can run JavaScript on the site's origin, but only under specific conditions.","audience":"developers","category":"package","nicknames":[],"who_cares":["astro","svelte","npm","self-hosted"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"JavaScript runs on the site origin for a user who opens a crafted link","analyst_note":"","how_to_check":"- Check the installed version: `npm ls @astrojs/svelte`\n- Look for pages that pass request input (such as a query parameter) as a slot name to a Svelte component with a `client:*` directive\n- Look for Svelte 5 components that render snippets by a dynamic name, such as `{@render snippets[active]?.()}`","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["auth-required","static-export","default-config"],"who_is_affected":"Applications built with Astro that use the @astrojs/svelte integration, pass request-controlled input such as a query parameter as a slot name, hydrate the component with a client:* directive, and render the attacker-chosen slot in a Svelte 5 component. Sites that do not take slot names from requests, or that use only static or non-hydrated components, are not exposed.","user_interaction":"yes","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000867,"input_tokens":5316,"output_tokens":670,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-2026':309C '-50146':310C '-79':22B '/components/tabs.svelte':134C '1':176C '4wqp':307C '5':151C,274C '5.7.3':336C '536x':306C '867q':13A,18A '8hv8':305C '9.1.1':337C '9.1.2':342C,366C 'activ':142C,162C 'affect':333C 'alert':175C 'alreadi':293C 'also':324C 'applic':208C 'arbitrari':113C 'astro':20B,130C,291C,317C 'astro.url.searchparams.get':137C 'astrojs/svelte':8A,19A,30B,79C,300C,335C,341C,365C 'attack':54B,187C,265C 'attacker-chosen':264C 'attribut':38B,99C,110C,258C 'break':48B 'browser':332C 'chosen':266C 'client':140C,239C,358C 'client-sid':357C 'code':361C 'compon':87C,152C,234C,245C,261C,275C,283C,329C,398C 'condit':76B 'const':135C 'contain':106C 'content':83C 'control':221C,376C 'core':318C 'could':107C 'counterpart':301C 'craft':44B,62B,195C 'credit':399C 'cross':183C 'cross-sit':182C 'crrj':12A,17A 'cve':308C 'cwe':21B 'direct':240C 'div':144C 'dynam':281C 'element':90C 'end':108C 'escap':40B,102C,294C,349C 'exampl':119C 'f4j9':11A,16A 'fallback':295C 'fix':312C,339C,387C 'follow':213C 'get':56B,189C 'ghsa':10A,15A,304C 'ghsa-8hv8-536x-4wqp':303C 'ghsa-f4j9-crrj-867q':9A,14A 'host':27B 'html':37B,52B,101C,114C,179C,348C 'html-escap':347C 'hydrat':236C,244C,327C,360C 'img':171C 'impact':180C 'import':131C 'inject':51B,112C 'input':222C,377C 'issu':315C 'javascript':66B,199C 'known':390C 'later':368C 'link':63B,196C 'live':178C 'load':141C 'manus':403C 'manus-pi':402C 'map':383C 'mean':271C 'name':6A,34B,46B,95C,98C,105C,126C,158C,231C,257C,282C,344C,381C,392C 'never':285C 'non':243C 'non-hydr':242C 'npm':23B 'onerror':174C 'open':60B,193C 'origin':71B,204C 'output':164C,253C,290C 'packag':24B 'page':117C,121C,217C 'paramet':227C 'pass':84C,218C,394C 'patch':338C 'pi':404C 'practic':270C 'queri':226C 'reflect':1A,181C 'remov':250C 'render':31B,80C,147C,154C,160C,262C,277C,286C,353C 'report':400C 'request':129C,220C,375C 'request-control':219C,374C 'run':65B,198C 'script':185C 'self':26B 'self-host':25B 'server':352C 'set':388C 'side':359C 'site':69B,184C,202C 'slot':5A,33B,45B,82C,94C,104C,125C,145C,230C,247C,267C,288C,343C,380C,391C 'snippet':156C,161C,278C 'specif':75B 'src':172C 'summari':77C 'svelt':28B,86C,150C,159C,233C,260C,273C 'tab':132C,136C,138C,139C,143C,146C,166C 'take':123C 'templat':296C 'togeth':254C 'true':215C 'unescap':4A,321C 'upgrad':363C,370C 'use':325C,373C 'user':58B,191C 'valu':167C,322C 'version':334C 'via':3A 'without':39B,100C 'workaround':362C 'would':163C 'wrap':81C 'wrapper':248C 'write':32B,92C 'x':170C,173C 'xss':2A","nicknames":[],"mitigations":[],"who_affected":"Applications built with Astro that use the @astrojs/svelte integration, pass request-controlled input such as a query parameter as a slot name, hydrate the component with a client:* directive, and render the attacker-chosen slot in a Svelte 5 component. Sites that do not take slot names from requests, or that use only static or non-hydrated components, are not exposed.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["svelte"],"ptokens":["astrojs","svelte"],"labels":["astro","cwe79","medium","npm","package","selfhosted","svelte"],"campaign_type":null,"search_a":"'867q':13A,18A 'astrojs/svelte':8A,19A 'crrj':12A,17A 'f4j9':11A,16A 'ghsa':10A,15A 'ghsa-f4j9-crrj-867q':9A,14A 'name':6A 'reflect':1A 'slot':5A 'unescap':4A 'via':3A 'xss':2A","search_b":"'-79':22B 'astro':20B 'astrojs/svelte':30B 'attack':54B 'attribut':38B 'break':48B 'condit':76B 'craft':44B,62B 'cwe':21B 'escap':40B 'get':56B 'host':27B 'html':37B,52B 'inject':51B 'javascript':66B 'link':63B 'name':34B,46B 'npm':23B 'open':60B 'origin':71B 'packag':24B 'render':31B 'run':65B 'self':26B 'self-host':25B 'site':69B 'slot':33B,45B 'specif':75B 'svelt':28B 'user':58B 'without':39B 'write':32B","search_c":"'-2026':309C '-50146':310C '/components/tabs.svelte':134C '1':176C '4wqp':307C '5':151C,274C '5.7.3':336C '536x':306C '8hv8':305C '9.1.1':337C '9.1.2':342C,366C 'activ':142C,162C 'affect':333C 'alert':175C 'alreadi':293C 'also':324C 'applic':208C 'arbitrari':113C 'astro':130C,291C,317C 'astro.url.searchparams.get':137C 'astrojs/svelte':79C,300C,335C,341C,365C 'attack':187C,265C 'attacker-chosen':264C 'attribut':99C,110C,258C 'browser':332C 'chosen':266C 'client':140C,239C,358C 'client-sid':357C 'code':361C 'compon':87C,152C,234C,245C,261C,275C,283C,329C,398C 'const':135C 'contain':106C 'content':83C 'control':221C,376C 'core':318C 'could':107C 'counterpart':301C 'craft':195C 'credit':399C 'cross':183C 'cross-sit':182C 'cve':308C 'direct':240C 'div':144C 'dynam':281C 'element':90C 'end':108C 'escap':102C,294C,349C 'exampl':119C 'fallback':295C 'fix':312C,339C,387C 'follow':213C 'get':189C 'ghsa':304C 'ghsa-8hv8-536x-4wqp':303C 'html':101C,114C,179C,348C 'html-escap':347C 'hydrat':236C,244C,327C,360C 'img':171C 'impact':180C 'import':131C 'inject':112C 'input':222C,377C 'issu':315C 'javascript':199C 'known':390C 'later':368C 'link':196C 'live':178C 'load':141C 'manus':403C 'manus-pi':402C 'map':383C 'mean':271C 'name':95C,98C,105C,126C,158C,231C,257C,282C,344C,381C,392C 'never':285C 'non':243C 'non-hydr':242C 'onerror':174C 'open':193C 'origin':204C 'output':164C,253C,290C 'page':117C,121C,217C 'paramet':227C 'pass':84C,218C,394C 'patch':338C 'pi':404C 'practic':270C 'queri':226C 'reflect':181C 'remov':250C 'render':80C,147C,154C,160C,262C,277C,286C,353C 'report':400C 'request':129C,220C,375C 'request-control':219C,374C 'run':198C 'script':185C 'server':352C 'set':388C 'side':359C 'site':184C,202C 'slot':82C,94C,104C,125C,145C,230C,247C,267C,288C,343C,380C,391C 'snippet':156C,161C,278C 'src':172C 'summari':77C 'svelt':86C,150C,159C,233C,260C,273C 'tab':132C,136C,138C,139C,143C,146C,166C 'take':123C 'templat':296C 'togeth':254C 'true':215C 'unescap':321C 'upgrad':363C,370C 'use':325C,373C 'user':191C 'valu':167C,322C 'version':334C 'without':100C 'workaround':362C 'would':163C 'wrap':81C 'wrapper':248C 'write':92C 'x':170C,173C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-10-28T19:27:20+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-f4j9-crrj-867q","url":"https://github.com/withastro/astro/security/advisories/GHSA-f4j9-crrj-867q","severity":"medium","published_at":"2026-10-28T19:27:20+00:00","modified_at":"2026-10-28T19:27:21+00:00","first_seen_at":"2026-11-09T22:53:17+00:00","fetched_at":"2026-11-09T22:53:17+00:00","note":null}],"documents":[{"id":110449,"url":"https://github.com/withastro/astro/security/advisories/GHSA-f4j9-crrj-867q","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}