Sitemap

Open Source Hacking — Breaking AstroJs

4 min readJan 5, 2026

How a convenience feature turned into SSRF and local file reads

Press enter or click to view image in full size

“What could possibly go wrong with an image optimization endpoint?”

That was the thought when I started looking into Astro’s /_image route. It is one of those features developers rarely question. You pass an image URL or path, Astro optimizes it, caches it, and serves it back. Simple. Safe. Boring.

Except it was none of those.

While auditing AstroJS, I ended up finding two separate issues hiding behind the same endpoint. One allowed server-side request forgery in production on Cloudflare deployments. The other allowed arbitrary local file reads on development servers.

Same feature. Two environments. Two very different failures.

The Setup

Astro exposes an on-demand image optimization endpoint at /_image. It is used internally when you reference images, but it is also directly reachable over HTTP.

The intended behavior is straightforward. Local images are allowed. Remote images are allowed only if explicitly whitelisted.
Everything else should be blocked.

That logic exists. It is implemented. It even works in most adapters.

Except when it doesn’t.

Issue 1: SSRF via the Cloudflare adapter

CVE-2025–58179 / GHSA-qpr4-c339–7vq8

This one showed up while testing Astro deployments on Cloudflare Pages using the official Cloudflare adapter.

The configuration looked like:

export default defineConfig({
output: 'server',
adapter: cloudflare(),
});

Once deployed, the /_image endpoint became available. I passed it a remote image URL from an untrusted domain.

/_image?href=https://placehold.co/600x400

It worked.

That should not have happened.

Astro requires developers to explicitly allow remote image domains. Without that, the request should be rejected. Instead, the Cloudflare adapter happily fetched and served the image.

At this point, it stopped being an image bug and started becoming something more interesting.

Because nothing was validating the URL at all.

The Cloudflare adapter had reimplemented the image endpoint instead of using the shared, hardened logic. In doing so, it skipped every single security check.

The code boiled down to this.

return fetch(new URL(href, ctx.url.origin));

Anything the Cloudflare runtime could reach, the attacker could reach too.

Metadata services.
Internal APIs.
Private networks.
External services that trust Cloudflare IPs.

All exposed through a single GET request.

This was not a domain bypass. It was unrestricted server-side request forgery.

Why this one mattered

Astro is often used for content-heavy sites. Blogs. Docs. Marketing pages. Many of them are deployed to Cloudflare because it is fast and cheap.

Most developers do not think twice about image optimization endpoints. They do not protect them. They do not monitor them. They assume they are safe.

Issue 2: Arbitrary local file read in dev mode

CVE-2025–64757 / GHSA-x3h8–62x9–952g

While digging deeper into /_image, I tested the same endpoint in development mode.

This time, on a local Astro dev server.

astro dev

The behavior was different.

In development, Astro takes a faster path. Security checks that exist in production are skipped for convenience.

That shortcut turned into a vulnerability.

The endpoint accepted an href parameter pointing to a file path. In dev mode, that path was converted directly into a file URL.

No checks that it lived inside the project.
No checks that it was even relative.

Which meant absolute paths worked. A request like this was enough.

/_image?href=/System/Library/Image Capture/Automatic Tasks/MakePDF.app/Contents/Resources/0blank.jpg&w=100&h=100&f=png

Astro read the file from disk, processed it, and returned it as a PNG.

I was now reading arbitrary local files from the developer’s machine over HTTP.

It only worked for image files, but that still included a surprising amount of sensitive material. System images. App assets. User files. Anything readable by the Node process.

The reason was simple. The dev-only branch bypassed the same path validation that production relied on.

Why dev bugs still matter

This was marked as low severity, and technically that is correct.

It only affects development servers.
It does not allow file modification.
It does not hit production users.

But dev servers are often exposed unintentionally.

Local tunneling.
Shared preview links.
Internal staging environments.

Once exposed, this became an unauthenticated file read primitive.

And more importantly, it showed a pattern.

Security logic existed. But it was duplicated. And diverged.

That is how bugs like this happen.

ViteJs had a very similar bug reported as well: https://www.cve.org/CVERecord?id=CVE-2025-30208

The common root cause

Both issues came down to the same design mistake.

The image endpoint logic was not centralized.

Different adapters and environments had their own implementations. Some were secure. Some were not. Some skipped checks entirely.

The generic implementation had proper validation.
The Cloudflare adapter did not.
The dev path skipped it for speed.

Same feature. Different trust assumptions. Different security outcomes.

Closing thoughts

Image optimization endpoints look harmless. They are everywhere. They rarely get audited.

But they sit at a dangerous intersection.

They accept user input.
They fetch remote content.
They touch the filesystem.

That combination deserves paranoia.

Open source frameworks like Astro sit at the very bottom of the modern web stack. They power personal blogs, company landing pages, internal tools, and large production sites, often without teams ever reading a single line of their internals. That reach is what makes issues like this dangerous. A small oversight in a shared abstraction does not stay small. It gets copied, deployed, scaled, and trusted by thousands of applications. When security assumptions break at that layer, the blast radius is not one app or one company, but an entire ecosystem that believed the defaults were safe.

These bugs were not exotic. There was no clever trick. No race condition. No parsing abuse. Just missing checks in code paths that felt safe and that is usually where the real bugs live.

Stay curious.

--

--

Monish
Monish

Written by Monish

Developer by day, Hacker by night