Skip to content

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Oct 14, 2024

This PR contains the following updates:

Package Change Age Confidence
astro (source) 4.11.6 -> 5.15.9 age confidence

GitHub Vulnerability Alerts

CVE-2024-47885

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., iframe tags with unsanitized name attributes) 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):

https://github.com/withastro/astro/blob/7814a6cad15f06931f963580176d9b38aa7819f2/packages/astro/src/transitions/router.ts#L135-L156

However, this implementation is vulnerable to a DOM Clobbering attack. The document.scripts lookup 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 a type attribute.

Finally, the innerHTML of an attacker-injected non-script HTML elements, which is plain text content before, will be set to the .innerHTML of 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., form or iframe). 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 an alert(1) from an attacker-injected form element.

---
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 ViewTransitions and store the user-inserted scriptless HTML tags without properly sanitizing the name attributes on the page.

Patch

We recommend replacing document.scripts with document.getElementsByTagName('script') for referring to script elements. This will mitigate the possibility of DOM Clobbering attacks leveraging the name attribute.

Reference

Similar issues for reference:

CVE-2024-56140

Summary

A bug in Astro’s CSRF-protection middleware allows requests to bypass CSRF checks.

Details

When the security.checkOrigin configuration option is set to true, 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 '@&#8203;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 forbidden

However, a vulnerability exists that can bypass this security.

Pattern 1: Requests with a semicolon after the Content-Type

A semicolon-delimited parameter is allowed after the type in Content-Type.

Web browsers will treat a Content-Type such as application/x-www-form-urlencoded; abc as 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-Type header

The Content-Type header is not required for a request. The following examples are sent without a Content-Type header, 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.

CVE-2024-56159

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.
https://github.com/withastro/astro/blob/176fe9f113fd912f9b61e848b00bbcfecd6d5c2c/packages/astro/src/core/build/static-build.ts#L139

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 for src/pages/index.astro gets named dist/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 @&#8203;sentry/astro

This vulnerability is the root cause of https://github.com/withastro/astro/issues/12703, which links to a simple stackblitz project demonstrating the vulnerability. Upon build, notice the contents of the dist/client (referred to as config.build.client in astro code) folder. All astro servers make the folder in question accessible to the public internet without any authentication. It contains .map files 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 [email protected].

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 [email protected], and backported to Astro v4 in [email protected].

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 [email protected], and the fix for static-output projects was released in [email protected] and backported to Astro v4 in [email protected]. Users are advised to update immediately if they are using sourcemaps or an integration that enables sourcemaps.

CVE-2025-55303

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 /_image endpoint which returns optimized versions of images.

The /_image endpoint is restricted to processing local images bundled with the site and also supports remote images from domains the site developer has manually authorized (using the image.domains or image.remotePatterns options).

However, a bug in impacted versions of astro allows 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

  1. Create a new minimal Astro project ([email protected]).

  2. Configure it to use the Node adapter (@astrojs/[email protected] — newer versions are not impacted):

    // astro.config.mjs
    import { defineConfig } from 'astro/config';
    import node from '@&#8203;astrojs/node';
    
    export default defineConfig({
    	adapter: node({ mode: 'standalone' }),
    });
  3. Build the site by running astro build.

  4. Run the server, e.g. with astro preview.

  5. Append /_image?href=//placehold.co/600x400 to the preview URL, e.g. http://localhost:4321/_image?href=//placehold.co/600x400

  6. The site will serve the image from the unauthorized placehold.co origin.

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.

CVE-2025-61925

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-Host header that is reflected when using the recommended Astro.url property as there is no validation that the value is safe.

Details

Astro reflects the value in X-Forwarded-Host in output when using Astro.url without any validation.

It is common for web servers such as nginx to route requests via the Host header, and forward on other request headers. As such as malicious request can be sent with both a Host header and an X-Forwarded-Host header where the values do not match and the X-Forwarded-Host header is malicious. Astro will then return the malicious value.

This could result in any usages of the Astro.url value in code being manipulated by a request. For example if a user follows guidance and uses Astro.url for 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 use
  • yarn run build
  • node ./dist/server/entry.mjs
  • curl --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-Host header

For 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 curl request 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.

CVE-2025-64525

Summary

In impacted versions of Astro using on-demand rendering, request headers x-forwarded-proto and x-forwarded-port are 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-proto and x-forwarded-port headers are used without sanitization in two parts of the Astro server code. The most important is in the createRequest() function. Any configuration, including the default one, is affected:

https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L97
https://github.com/withastro/astro/blob/970ac0f51172e1e6bff4440516a851e725ac3097/packages/astro/src/core/app/node.ts#L121

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-proto header), 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/ssr

As 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 createRequest function 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

Download the PoC repository

Middleware-based protected route bypass - x-forwarded-proto only

The middleware has been configured to protect the /admin route 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();
});
  1. When tryint to access /admin the attacker is naturally redirected :

    curl -i http://localhost:4321/admin
    image
  2. 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
    image

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: admin

During 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-protocol header, 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 :

image

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 404 cab be forced, causing an error on the /ssr page like this : curl -i -H "x-forwarded-proto: https://localhost/vulnerable?" http://localhost:4321/ssr
image

Same logic applies to x-forwarded-port : curl -i -H "x-forwarded-port: /vulnerable?" http://localhost:4321/ssr

How ​​is this possible?

The router sees the request for the path /vulnerable, which does not exist, and therefore returns a 404, while the potential CDN sees /ssr and can then cache the 404 response, 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.url is used to create links within the page, this can lead to Stored XSS with x-forwarded-proto and the following value:

x-forwarded-proto: javascript:alert(document.cookie)//

results in the following URL object:

image

It is also possible to inject any link, always, if the value of request.url is 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-port

If the value of request.url is used to create links within the page, this can lead to broken links, with the header and the following value:

X-Forwarded-Port: /nope?

Example of an Astro website:
Capture d’écran 2025-11-03 à 22 07 14

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-host in 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-Host header.

By sending x-forwarded-host with an empty value, the forwardedHostname variable is assigned an empty string. Then, during the subsequent check, the condition fails because forwardedHostname returns false, 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 the host. This is because the http/https schemes are considered special schemes by the WHATWG URL Standard Specification, requiring an authority state.

From there, the following request on the example SSR application (astro repo) yields an SSRF:
Capture d’écran 2025-11-06 à 21 18 26
empty x-forwarded-host + the target host in the path

Credits

  • Allam Rachid (zhero;)
  • Allam Yasser (inzo)

CVE-2025-64757

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 href parameter 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

  1. Astro development server must be running (astro dev)
  2. The /_image endpoint must be accessible to the attacker
  3. Target image files must be readable by the Node.js process

Exploit Steps

  1. Start Astro Development Server:

    astro dev  # Typically runs on http://localhost:4321
  2. Craft Malicious Request:

    GET /_image?href=/[ABSOLUTE_PATH_TO_IMAGE]&w=100&h=100&f=png HTTP/1.1
    Host: localhost:4321
  3. Example 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.png

Demonstration 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.png containing processed system image

Attack Payload:

http://localhost:4321/_image?href=/%2FSystem%2FLibrary%2FImage%20Capture%2FAutomatic%20Tasks%2FMakePDF.app%2FContents%2FResources%2F0blank.jpg&w=100&h=100&f=png

Server Response:

Status: 200 OK
Content-Type: image/png
Content-Length: 303

Impact

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

CVE-2025-64764

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 export
  • p: the transmitted properties, encrypted
  • s: for the slots

Slots 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 parameter e is default, 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: url and file. file returns a string value corresponding to the absolute path of the island file. Since the value is of type string, it fulfills the following condition and leads to this code block:

image

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 the s parameter, is injected as a child

All 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

Download the PoC repository

Access the following URL and note the opening of the popup, demonstrating the reflected XSS:

http://localhost:4321/_server-islands/ServerTime?e=file&p=&s={%22zhero%22:%22%3Cimg%20src=x%20onerror=alert(0)%3E%22}

image

The value of the parameter s must be in JSON format and the payload must be injected at the value level, not the key level :

for_respected_patron

Despite the initial template being empty, it is created because the value of the URL parameter e is set to file, 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 parameter s.

Credits

  • Allam Rachid (zhero;)
  • Allam Yasser (inzo)

CVE-2025-64765

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 uses context.url.pathname without 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.

https://github.com/withastro/astro/blob/ebc4b1cde82c76076d5d673b5b70f94be2c066f3/packages/astro/src/vite-plugin-astro-server/request.ts#L40-L44

/** 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 context

Consider 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.pathname is validated , if it's equal to /admin the isAuthed property 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.pathname returns the raw version which is /%61admin while 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
image

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);

Thankyou, let me know if any more info is needed happy to help :)

CVE-2025-65019

Summary

When using Astro's Cloudflare adapter (@​astrojs/cloudflare) with output: 'server', the image optimization endpoint (/_image) contains a critical vulnerability in the isRemoteAllowed() function that unconditionally allows data: protocol URLs. This enables Cross-Site Scripting (XSS) attacks through malicious SVG payloads, bypassing domain restrictions and Content Security Policy protections.

Details

On-demand rendered sites built with Astro include an /_image endpoint for image optimization. While this endpoint is designed to restrict processing to local images and authorized remote domains (configured via image.domains or image.remotePatterns), a critical vulnerability exists in the underlying validation logic.

The isRemoteAllowed() function in packages/internal-helpers/src/remote.ts (lines 128-131) unconditionally allows ALL data: protocol URLs without any validation or sanitization. When combined with SVG images containing JavaScript, this creates a vector for XSS attacks.

Vulnerable Code:

/packages/ packages/internal-helpers/src/remote.ts lines 128-131
if (url.protocol === 'data:') {
    return true;  // ← Unconditionally allows ALL data: URLs!
}

The vulnerability manifests differently depending on the image endpoint implementation:

  • Safe implementation: Server processes SVG and converts to raster format (PNG/JPEG), removing JavaScript
  • Vulnerable implementation: Server redirects browser to raw SVG data URL, allowing JavaScript execution

PoC

  1. Create a new minimal Astro project (astro@latest)

  2. Configure it to use the Cloudflare adapter (@​astrojs/[email protected])

  3. Deploy to Cloudflare Pages or Workers.

  4. Write page to load SVG Image like : SVG XSS Payload

  5. Open directly the SVG file to show an alert (in read scenarios, the apps that use the framework will use CDN for example, to load SVG, depending that the framework is secure)

Impact

  1. Stored XSS: Malicious URLs can be crafted to execute JavaScript in victim's browser
  2. Session Hijacking: JavaScript can access cookies and session tokens
  3. Account Takeover: Combined with CSRF, can perform unauthorized actions
  4. Data Exfiltration: Sensitive information can be stolen and sent to attacker-controlled servers

References


Release Notes

withastro/astro (astro)

v5.15.9

Compare Source

Patch Changes
  • #​14786 758a891 Thanks @​mef! - Add handling of invalid encrypted props and slots in server islands.

  • #​14783 504958f Thanks @​florian-lefebvre! - Improves the experimental Fonts API build log to show the number of downloaded files. This can help spotting excessive downloading because of misconfiguration

  • #​14791 9e9c528 Thanks @​Princesseuh! - Changes the remote protocol checks for images to require explicit authorization in order to use data URIs.

    In order to allow data URIs for remote images, you will need to update your astro.config.mjs file to include the following configuration:

    // astro.config.mjs
    import { defineConfig } from 'astro/config';
    
    export default defineConfig({
      images: {
        remotePatterns: [
          {
            protocol: 'data',
          },
        ],
      },
    });
  • #​14787 0f75f6b Thanks @​matthewp! - Fixes wildcard hostname pattern matching to correctly reject hostnames without dots

    Previously, hostnames like localhost or other single-part names would incorrectly match patterns like *.example.com. The wildcard matching logic has been corrected to ensure that only valid subdomains matching the pattern are accepted.

  • #​14776 3537876 Thanks @​ktym4a! - Fixes the behavior of passthroughImageService so it does not generate webp.

  • Updated dependencies [9e9c528, 0f75f6b]:

v5.15.8

Compare Source

Patch Changes
  • #​14772 00c579a Thanks @​matthewp! - Improves the security of Server Islands slots by encrypting them before transmission to the browser, matching the security model used for props. This improves the integrity of slot content and prevents injection attacks, even when component templates don't explicitly support slots.

    Slots continue to work as expected for normal usage—this change has no breaking changes for legitimate requests.

  • #​14771 6f80081 Thanks @​matthewp! - Fix middleware pathname matching by normalizing URL-encoded paths

    Middleware now receives normalized pathname values, ensuring that encoded paths like /%61dmin are properly decoded to /admin before middleware checks. This prevents potential security issues where middleware checks might be bypassed through URL encoding.

v5.15.7

Compare Source

Patch Changes

v5.15.6

Compare Source

Patch Changes
  • #​14751 18c55e1 Thanks @​delucis! - Fixes hydration of client components when running the dev server and using a barrel file that re-exports both Astro and UI framework components.

  • #​14750 35122c2 Thanks @​florian-lefebvre! - Updates the experimental Fonts API to log a warning if families with a conflicting cssVariable are provided

  • #​14737 74c8852 Thanks @​Arecsu! - Fixes an error when using transition:persist with components that use declarative Shadow DOM. Astro now avoids re-attaching a shadow root if one already exists, preventing "Unable to re-attach to existing ShadowDOM" navigation errors.

  • #​14750 35122c2 Thanks @​florian-lefebvre! - Updates the experimental Fonts API to allow for more granular configuration of remote font families

    A font family is defined by a combination of properties such as weights and styles (e.g. weights: [500, 600] and styles: ["normal", "bold"]), but you may want to download only certain combinations of these.

    For greater control over which font files are downloaded, you can specify the same font (ie. with the same cssVariable, name, and provider properties) multiple times with different combinations. Astro will merge the results and download only the required files. For example, it is possible to download normal 500 and 600 while downloading only italic 500:

    // astro.config.mjs
    import { defineConfig, fontProviders } from 'astro/config';
    
    export default defineConfig({
      experimental: {
        fonts: [
          {
            name: 'Roboto',
            cssVariable: '--roboto',
            provider: fontProviders.google(),
            weights: [500, 600],
            styles: ['normal'],
          },
          {
            name: 'Roboto',
            cssVariable: '--roboto',
            provider: fontProviders.google(),
            weights: [500],
            styles: ['italic'],
          },
        ],
      },
    });

v5.15.5

Compare Source

Patch Changes
  • #​14712 91780cf Thanks @​florian-lefebvre! - Fixes a case where build's process.env would be inlined in the server output

  • #​14713 666d5a7 Thanks @​florian-lefebvre! - Improves fallbacks generation when using the experimental Fonts API

  • #​14743 dafbb1b Thanks @​matthewp! - Improves X-Forwarded header validation to prevent cache poisoning and header injection attacks. Now properly validates X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Port headers against configured allowedDomains patterns, rejecting malformed or suspicious values. This is especially important when running behind a reverse proxy or load balancer.

v5.15.4

Compare Source

Patch Changes
  • #​14703 970ac0f Thanks @​ArmandPhilippot! - Adds missing documentation for some public utilities exported from astro:i18n.

  • #​14715 3d55c5d Thanks @​ascorbic! - Adds support for client hydration in getContainerRenderer()

    The getContainerRenderer() function is exported by Astro framework integrations to simplify the process of rendering framework components when using the experimental Container API inside a Vite or Vitest environment. This update adds the client hydration entrypoint to the returned object, enabling client-side interactivity for components rendered using this function. Previously this required users to manually call container.addClientRenderer() with the appropriate client renderer entrypoint.

    See the container-with-vitest demo for a usage example, and the Container API documentation for more information on using framework components with the experimental Container API.

  • #​14711 a4d284d Thanks @​deining! - Fixes typos in documenting our error messages and public APIs.

  • #​14701 9be54c7 Thanks @​florian-lefebvre! - Fixes a case where the experimental Fonts API would filter available font files too aggressively, which could prevent the download of woff files when using the google provider

v5.15.3

Compare Source

Patch Changes
  • #​14627 b368de0 Thanks @​matthewp! - Fixes skew protection support for images and font URLs

    Adapter-level query parameters (assetQueryParams) are now applied to all image and font asset URLs, including:

    • Dynamic optimized images via /_image endpoint
    • Static optimized image files
    • Font preload tags and font requests when using the experimental Fonts API
  • #​14631 3ad33f9 Thanks @​KurtGokhan! - Adds the astro/jsx-dev-runtime export as an alias for astro/jsx-runtime

v5.15.2

Compare Source

Patch Changes
  • #​14623 c5fe295 Thanks @​delucis! - Fixes a leak of server runtime code when importing SVGs in client-side code. Previously, when importing an SVG file in client code, Astro could end up adding code for rendering SVGs on the server to the client bundle.

  • #​14621 e3175d9 Thanks @​GameRoMan! - Updates vite version to fix CVE

v5.15.1

Compare Source

Patch Changes

v5.15.0

Compare Source

Minor Changes
  • #​14543 9b3241d Thanks @​matthewp! - Adds two new adapter configuration options assetQueryParams and internalFetchHeaders to the Adapter API.

    Official and community-built adapters can now use client.assetQueryParams to specify query parameters that should be appended to asset URLs (CSS, JavaScript, images, fonts, etc.). The query parameters are automatically appended to all generated asset URLs during the build process.

    Adapters can also use client.internalFetchHeaders to specify headers that should be included in Astro's internal fetch calls (Actions, View Transitions, Server Islands, Prefetch).

    This enables features like Netlify's skew protection, which requires the deploy ID to be sent with both internal requests and asset URLs to ensure client and server versions match during deployments.

  • #​14489 add4277 Thanks @​dev-shetty! - Adds a new Copy to Clipboard button to the error overlay stack trace.


Configuration

📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@github-actions
Copy link

github-actions bot commented Oct 14, 2024

Run report for 704b41ba

Total time: 44.2s | Comparison time: 40.5s | Estimated loss: 3.7s (8.4% slower)

Action Time Status Info
🟩 SyncWorkspace 0ms Passed
⬛️ SetupToolchain(node:20.15.1) 2.4s Skipped
🟩 InstallWorkspaceDeps(node:20.15.1) 13.9s Passed
🟩 SyncProject(node, site) 0.3ms Passed
🟩 RunTask(site:imports) 584.4ms Passed
🟩 RunTask(site:sync) 3.2s Passed
🟩 RunTask(site:format) 326.1ms Passed
🟩 RunTask(site:lint) 271.2ms Passed
🟩 RunTask(site:check) 9.1s Passed
🟩 RunTask(site:build) 14.9s Passed
Touched files
apps/site/package.json
pnpm-lock.yaml

@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from a7ec47f to 0f4b827 Compare December 8, 2024 18:15
@github-actions github-actions bot requested a deployment to [site] (renovate/npm-astro-vulnerability) December 8, 2024 18:16 Abandoned
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from 0f4b827 to ea7db75 Compare December 18, 2024 16:55
@renovate renovate bot changed the title fix(deps): update dependency astro to v4.16.1 [security] fix(deps): update dependency astro to v4.16.17 [security] Dec 18, 2024
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from ea7db75 to 704b41b Compare December 19, 2024 15:52
@renovate renovate bot changed the title fix(deps): update dependency astro to v4.16.17 [security] fix(deps): update dependency astro to v4.16.18 [security] Dec 19, 2024
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch 2 times, most recently from 7724e08 to 9cfc1ce Compare August 13, 2025 14:31
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch 2 times, most recently from 70536ac to b4d52bd Compare August 19, 2025 21:14
@renovate renovate bot changed the title fix(deps): update dependency astro to v4.16.18 [security] fix(deps): update dependency astro to v4.16.19 [security] Aug 19, 2025
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from b4d52bd to a014448 Compare August 31, 2025 10:20
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from a014448 to 19e1bd3 Compare September 25, 2025 20:44
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from 19e1bd3 to 7599316 Compare October 11, 2025 01:07
@renovate renovate bot changed the title fix(deps): update dependency astro to v4.16.19 [security] fix(deps): update dependency astro to v5 [security] Oct 11, 2025
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from 7599316 to a4e0c1c Compare October 21, 2025 14:55
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch 2 times, most recently from 217102e to f3b9bc9 Compare November 19, 2025 00:08
@renovate renovate bot force-pushed the renovate/npm-astro-vulnerability branch from f3b9bc9 to bd76b98 Compare November 19, 2025 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant