VYPR
High severityNVD Advisory· Published Sep 4, 2025· Updated Sep 5, 2025

Astro Cloudflare adapter is vulnerable to Server-Side Request Forgery via /_image endpoint

CVE-2025-58179

Description

Astro is a web framework for content-driven websites. Versions 11.0.3 through 12.6.5 are vulnerable to SSRF when using Astro's Cloudflare adapter. When configured with output: 'server' while using the default imageService: 'compile', the generated image optimization endpoint doesn't check the URLs it receives, allowing content from unauthorized third-party domains to be served. a A bug in impacted versions of the @astrojs/cloudflare adapter for deployment on Cloudflare’s infrastructure, allows an attacker to bypass the third-party domain restrictions and serve any content from the vulnerable origin. This issue is fixed in version 12.6.6.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@astrojs/cloudflarenpm
>= 11.0.3, < 12.6.612.6.6

Affected products

1

Patches

1
9ecf3598e2b2

Merge commit from fork

https://github.com/withastro/astroAlexander NiebuhrAug 27, 2025via ghsa
6 files changed · +90 10
  • .changeset/evil-rabbits-flow.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@astrojs/cloudflare': patch
    +---
    +
    +Improves the image proxy endpoint when using the default compile option to adhere to user configuration regarding the allowed remote domains
    
  • packages/integrations/cloudflare/src/entrypoints/image-endpoint.ts+13 0 modified
    @@ -1,4 +1,8 @@
    +// @ts-expect-error
    +import { imageConfig } from 'astro:assets';
    +import { isRemotePath } from '@astrojs/internal-helpers/path';
     import type { APIRoute } from 'astro';
    +import { isRemoteAllowed } from 'astro/assets/utils';
     
     export const prerender = false;
     
    @@ -11,5 +15,14 @@ export const GET: APIRoute = (ctx) => {
     		});
     	}
     
    +	if (isRemotePath(href)) {
    +		if (isRemoteAllowed(href, imageConfig) === false) {
    +			return new Response('Forbidden', { status: 403 });
    +		} else {
    +			// Redirect here because it is safer than a proxy, remote image will be served by remote domain and not own domain
    +			return Response.redirect(href, 302);
    +		}
    +	}
    +
     	return fetch(new URL(href, ctx.url.origin));
     };
    
  • packages/integrations/cloudflare/test/compile-image-service.test.js+68 0 added
    @@ -0,0 +1,68 @@
    +import * as assert from 'node:assert/strict';
    +import { after, before, describe, it } from 'node:test';
    +import { fileURLToPath } from 'node:url';
    +import { astroCli, wranglerCli } from './_test-utils.js';
    +
    +const root = new URL('./fixtures/compile-image-service/', import.meta.url);
    +
    +describe('CompileImageService', () => {
    +	let wrangler;
    +	before(async () => {
    +		await astroCli(fileURLToPath(root), 'build');
    +
    +		wrangler = wranglerCli(fileURLToPath(root));
    +		await new Promise((resolve) => {
    +			wrangler.stdout.on('data', (data) => {
    +				// console.log('[stdout]', data.toString());
    +				if (data.toString().includes('http://127.0.0.1:8788')) resolve();
    +			});
    +			wrangler.stderr.on('data', (_data) => {
    +				// console.log('[stderr]', data.toString());
    +			});
    +		});
    +	});
    +
    +	after(() => {
    +		wrangler.kill();
    +	});
    +
    +	it('forbids http://', async () => {
    +		const res = await fetch('http://127.0.0.1:8788/_image?href=http://placehold.co/600x400');
    +		const html = await res.text();
    +		const status = res.status;
    +		assert.equal(html, 'Forbidden');
    +		assert.equal(status, 403);
    +	});
    +
    +	it('forbids https://', async () => {
    +		const res = await fetch('http://127.0.0.1:8788/_image?href=https://placehold.co/600x400');
    +		const html = await res.text();
    +		const status = res.status;
    +		assert.equal(html, 'Forbidden');
    +		assert.equal(status, 403);
    +	});
    +
    +	it('forbids //', async () => {
    +		const res = await fetch('http://127.0.0.1:8788/_image?href=//placehold.co/600x400');
    +		const html = await res.text();
    +		const status = res.status;
    +		assert.equal(html, 'Forbidden');
    +		assert.equal(status, 403);
    +	});
    +
    +	it('allows trusted with redirect', async () => {
    +		const res = await fetch('http://127.0.0.1:8788/_image?href=https://astro.build/_astro/HeroBackground.B0iWl89K_2hpsgp.webp', { redirect: "manual" })
    +		const header = res.headers.get("location")
    +		const status = res.status;
    +		assert.equal(header, "https://astro.build/_astro/HeroBackground.B0iWl89K_2hpsgp.webp")
    +		assert.equal(status, 302)
    +	})
    +
    +	it('allows local', async () => {
    +		const res = await fetch('http://127.0.0.1:8788/_image?href=/_astro/placeholder.gLBdjEDe.jpg');
    +		const blob = await res.blob();
    +		const status = res.status;
    +		assert.equal(blob.type, 'image/jpeg');
    +		assert.equal(status, 200);
    +	});
    +});
    
  • packages/integrations/cloudflare/test/fixtures/compile-image-service/astro.config.mjs+3 0 modified
    @@ -6,4 +6,7 @@ export default defineConfig({
     		imageService: 'compile',
     	}),
     	output: 'static',
    +	image: {
    +		domains: ["astro.build"]
    +	}
     });
    
  • packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content/blog/post/index.md+1 1 modified
    @@ -2,4 +2,4 @@
     image: './placeholder.jpg'
     ---
     
    -![placeholder](./placeholder.jpg)
    +![placeholder](./placeholder.jpg)
    \ No newline at end of file
    
  • packages/integrations/cloudflare/test/fixtures/compile-image-service/src/pages/blog/[...slug].astro+0 9 modified
    @@ -1,5 +1,4 @@
     ---
    -import { Image } from "astro:assets";
     import { getEntry, type CollectionEntry } from "astro:content";
     
     export const prerender = false;
    @@ -21,14 +20,6 @@ const { Content } = await post.render();
         <title>Document</title>
       </head>
       <body>
    -    <div class="aspect-video w-full overflow-hidden flex items-end rounded-lg">
    -      <Image
    -        class="aspect-[4/3] object-cover object-left w-full"
    -        src={post.data.image}
    -        alt=""
    -      />
    -    </div>
    -
         <Content />
       </body>
     </html>
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.