Code Audit is live!  Try it now
Blog

CVE-2026-29772: Memory DoS in Astro Server Islands

A single POST request can exhaust your server's memory. The vulnerable endpoint is registered by default, even if you don't use Server Islands.

Dragos Albastroiu

Dragos Albastroiu

March 26, 2026

CVE-2026-29772: Memory DoS in Astro Server Islands

What is it?

CVE-2026-29772 is a denial-of-service vulnerability in Astro's Server Islands feature. The POST handler at /_server-islands/[name] buffers the entire request body with request.text() and feeds it straight into JSON.parse() without any size limit. A single oversized request can exhaust the process heap. Wire bytes amplify roughly 15x once V8 turns them into heap objects, so a 50 MB payload eats around 750 MB of memory.

AISafe discovered this on February 28, 2026. The fix shipped in astro v6.0.0 / @astrojs/node v10.0.0 on March 10, and the CVE was publicly disclosed on March 24.


Who is impacted?

The vulnerable code lives in the core astro package at packages/astro/src/core/server-islands/endpoint.ts. Every adapter executes the same endpoint code, so this is not limited to @astrojs/node. The GitHub advisory scopes the affected package to @astrojs/node because that is the most exploitable scenario (a long-lived Node.js process), but the root cause is in core.

The important detail: the /_server-islands route is registered by default on all Astro SSR apps, even if your app does not use Server Islands at all. The Astro source code confirms this: the route is unconditionally injected when any adapter is present.

AdapterAffected?Why
@astrojs/node (standalone)YesNo body size limit, long-lived process crashes on OOM
@astrojs/node (middleware)DependsIf the parent server enforces body limits, you may be protected
@astrojs/cloudflareYes (limited blast radius)Workers run the same vulnerable code. 100 MB network limit still allows large payloads. 128 MB isolate memory can be exhausted, but each crash only kills one isolate
@astrojs/denoYesNo built-in body size limits. Same OOM risk as Node.js
@astrojs/vercelMitigatedVercel enforces a 4.5 MB payload limit on serverless functions, making exploitation impractical
@astrojs/netlifyMitigatedNetlify Functions enforce a 6 MB payload limit, making exploitation impractical

Affected Versions

The vulnerable endpoint is in the core astro package. The fix shipped in astro v6.0.0 and @astrojs/node v10.0.0.


Are you affected?

  1. Are you running Astro SSR with any adapter?
  2. Is your astro version below 6.0.0?

Both yes? You are affected. Apply the mitigation below. The risk is highest on @astrojs/node (standalone) and @astrojs/deno where a crash takes down the whole process.

You can check your version with:

npm ls astro @astrojs/node

Mitigation

Option 1: Upgrade (recommended)

npm install astro@latest @astrojs/node@latest

The fix adds security.serverIslandBodySizeLimit in core astro (PR #15755), plus a separate HTTP-level bodySizeLimit in the @astrojs/node adapter (PR #15759). Requests exceeding the limit get a 413 response.

Option 2: Block at the reverse proxy

If you cannot upgrade immediately, block POST requests to the server-islands endpoint at your reverse proxy:

location ~* ^/_server-islands/ { limit_except GET { deny all; } }

Or on Cloudflare, create a WAF rule to block POST requests to /_server-islands/*.


Technical Details

Server Islands is an Astro feature that lets you mark components for server-side rendering on demand. When a page loads, the browser sends a POST request to /_server-islands/[ComponentName] with encrypted props and slots. The server decrypts them, renders the component, and returns the HTML.

The vulnerable code is in packages/astro/src/core/server-islands/endpoint.ts:

const raw = await request.text(); const data = JSON.parse(raw);

Two lines. No size check in between. The request body goes directly from the wire into a string, then into a parsed JavaScript object.

The thing that makes this worse than it looks: JSON parsing in V8 has significant memory amplification. A JSON string on the wire turns into JavaScript objects, string objects, property maps, and hidden classes on the heap. The measured ratio is about 15x. A 50 MB payload produces roughly 750 MB of heap pressure.

Astro actually has body size limits elsewhere. The actions system in actions/runtime/server.ts enforces actionBodySizeLimit before reading POST data. The server-islands endpoint just never got the same treatment.

The fix in PR #15755 adds security.serverIslandBodySizeLimit to core astro, enforcing a cap before request.text() is called. A companion PR #15759 adds HTTP-level bodySizeLimit to the @astrojs/node adapter. Requests exceeding the limit get a 413 response.

Here's how the finding looks in the AISafe platform:

AssessmentsAstroAIS-ASO-AST-005
medium

Memory DoS in Server Islands POST

Description

The server-islands POST handler reads the full request body into memory and immediately parses it as JSON without enforcing a size limit. Because this occurs on attacker-controlled input, oversized or concurrent requests can drive elevated memory and CPU consumption in SSR workers.

Unlike Astro actions, which enforce actionBodySizeLimit in actions/runtime/server.ts, the server-islands endpoint has no equivalent limit before reading/parsing POST data. This creates a straightforward denial-of-service condition against availability-focused workloads.

packages/astro/src/core/server-islands/endpoint.ts:64
64 if (!params.has('s') || !params.has('e') || !params.has('p')) {
65 return badRequest('Missing required query parameters.');
66 }
67 const encryptedSlots = params.get('s')!;
68 return {
69 encryptedComponentExport: params.get('e')!,
70 encryptedProps: params.get('p')!,
71 encryptedSlots,
72 };
73 }
74 case 'POST': {
75 try {
76 const raw = await request.text();
77 const data = JSON.parse(raw);
78 // Validate that slots is not plaintext
79 if ('slots' in data && typeof data.slots === 'object') {
80 return badRequest('Plaintext slots are not allowed. Slots must be encrypted.');
81 }
82 // Validate that componentExport is not plaintext
83 if ('componentExport' in data && typeof data.componentExport === 'string') {
84 return badRequest(
85 'Plaintext componentExport is not allowed. componentExport must be encrypted.',
86 );
87 }

Vulnerability Flow

3 Steps
endpoint.tsL74
SOURCE
request body

Public POST request to /_server-islands/[name] accepts attacker-controlled body bytes.

endpoint.tsL76
BRIDGE
raw
endpoint.tsL77
SINK
raw
packages/astro/src/core/server-islands/endpoint.ts
64 if (!params.has('s') || !params.has('e') || !params.has('p')) {
65 return badRequest('Missing required query parameters.');
66 }
67 const encryptedSlots = params.get('s')!;
68 return {
69 encryptedComponentExport: params.get('e')!,
70 encryptedProps: params.get('p')!,
71 encryptedSlots,
72 };
73 }
74 case 'POST': {
75 try {
76 const raw = await request.text();
77 const data = JSON.parse(raw);
78 // Validate that slots is not plaintext
79 if ('slots' in data && typeof data.slots === 'object') {
80 return badRequest('Plaintext slots are not allowed. Slots must be encrypted.');
81 }
82 // Validate that componentExport is not plaintext
83 if ('componentExport' in data && typeof data.componentExport === 'string') {
84 return badRequest(

Impact

Unauthenticated attackers can degrade or interrupt service by sending large request bodies to the server-islands endpoint, causing worker memory pressure, high CPU usage, and possible process instability. Users may experience timeouts or outages until traffic is mitigated or instances are recycled.


Proof of Concept

poc.py
 1import requests
 2import sys
 3 
 4TARGET = "http://localhost:4321" # change to target base URL
 5 
 6print("Step 1: Building oversized JSON payload (~50MB field)...")
 7big = "A" * (50 * 1024 * 1024)
 8payload = {
 9 "encryptedComponentExport": big,
10 "encryptedProps": "",
11 "encryptedSlots": ""
12}
13 
14print("Step 2: Sending POST to server-islands endpoint...")
15try:

Remediation

To remediate this issue, AISafe recommends to enforce strict request body size limits before reading POST payloads and return HTTP 413 when limits are exceeded.

Issue found and reported by aisafe.io

Timeline

DateEvent
2026-02-28AISafe discovers vulnerability during automated scan
2026-03-10astro v6.0.0 / @astrojs/node v10.0.0 released with fix
2026-03-24CVE-2026-29772 assigned, public disclosure

More information

You can follow us on Twitter for more security research.

Share this post