{"id":"GHSA-88gm-j2wx-58h6","revision":"e68262c02d484a8b8fe52b67d84120b3","records":[{"revision":"e68262c02d484a8b8fe52b67d84120b3","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-88gm-j2wx-58h6","aliases":["CVE-2026-41321"],"title":"Cloudflare has SSRF via redirect following through its image-binding-transform endpoint (incomplete fix for GHSA-qpr4)","description":"## Summary\n\nThe `fetch()` call for remote images in `packages/integrations/cloudflare/src/utils/image-binding-transform.ts` (line 28) uses the default `redirect: 'follow'` behavior. This allows the Cloudflare Worker to follow HTTP redirects to arbitrary URLs, bypassing the `isRemoteAllowed()` domain allowlist check which only validates the initial URL.\n\nAll three other image fetch paths in the codebase correctly use `{ redirect: 'manual' }`. This is an incomplete fix for GHSA-qpr4-c339-7vq8.\n\nConfirmed on HEAD.\n\n## Root Cause\n\n`image-binding-transform.ts` line 28:\n\n    const content = await (isRemotePath(href) ? fetch(imageSrc) : assets.fetch(imageSrc));\n\nMissing `{ redirect: 'manual' }`. The three protected paths:\n\n    // image-passthrough-endpoint.ts:23\n    response = await fetch(href, { redirect: 'manual' });\n\n    // assets/endpoint/shared.ts:11\n    const res = await fetch(src, { redirect: 'manual' });\n\n    // assets/utils/remoteProbe.ts:53\n    const response = await fetch(url, { redirect: 'manual' });\n\n## PoC\n\nDemonstrated with Node.js that `fetch()` without `redirect: 'manual'` follows 302 redirects to arbitrary destinations:\n\n    # Server A (allowed domain) returns 302 → Server B (internal)\n    fetch('http://allowed:19741/img.jpg')                        → follows 302 → hits http://internal:19742/secret\n    fetch('http://allowed:19741/img.jpg', {redirect:'manual'})   → returns 302, internal server NOT hit\n\nAttack path: attacker finds an open redirect on an allowed domain, crafts `/_image?href=https://allowed-cdn.com/redirect?url=http://internal-service/`, and the Worker follows the redirect to the unauthorized destination.\n\n## Impact\n\nBypasses the `image.domains` and `image.remotePatterns` allowlist for the default Cloudflare image service (`cloudflare-binding`). Enables blind SSRF to domains not in the allowlist. Same vulnerability class as GHSA-qpr4-c339-7vq8 (HIGH) which fixed the passthrough endpoint but missed this one.\n\n## Suggested Fix\n\n    const content = await (isRemotePath(href) ? fetch(imageSrc, { redirect: 'manual' }) : assets.fetch(imageSrc));","state":0,"publishedAt":"2026-04-23T21:52:03+00:00","modifiedAt":"2026-05-05T16:05:51.817634+00:00","affected":[{"ecosystem":"npm","name":"@astrojs/cloudflare","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"13.1.10"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"LOW"}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-88gm-j2wx-58h6","https://nvd.nist.gov/vuln/detail/CVE-2026-41321","https://github.com/withastro/astro/commit/a43eb4b40b4f81530e3c9b5e2959495900320433","https://github.com/advisories/GHSA-qpr4-c339-7vq8","https://github.com/withastro/astro","https://github.com/withastro/astro/releases/tag/%40astrojs%2Fcloudflare%4013.1.10"],"sourceUrl":"https://github.com/advisories/GHSA-88gm-j2wx-58h6","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-88gm-j2wx-58h6","identifiers":["GHSA-88gm-j2wx-58h6","CVE-2026-41321"]},"retained":false,"historicalAffected":[]}],"coverage":[]}