iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🔭

Why react2shell-like Vulnerabilities Don't Occur in Astro and How to Prevent Them

に公開
5

CVE-2025-55182, a vulnerability in React disclosed on December 3, 2025, allows for remote code execution on the server side. Commonly known as react2shell, this vulnerability has sparked discussions across the entire frontend community that go beyond being dismissed as a simple implementation bug.

Conclusion

  • Astro Actions are designed so that the vulnerability that occurred in RSF does not happen in Astro.
  • Additionally, Astro Actions have mechanisms in place to prevent sites from becoming vulnerable.
  • There isn't just one way to make a site secure; there are countless ways.

Why does react2shell happen?

RSC works by sending crafted requests to a server where RSC is enabled, allowing an attacker to execute arbitrary code on the server side. Specifically, by planting function references or prototype pollution into React's Flight protocol, attacks are possible regardless of authentication.

So, what exactly was the cause?

Implementation Bug in React Flight Protocol

This is the direct cause of this vulnerability. It was in a state where holes in the JavaScript language itself could be exploited, such as using anyObject.constructor.constructor for eval, exploiting Thenables, and prototype pollution. While it should have been implemented carefully to prevent such vulnerabilities, the response was insufficient.

Over-reliance on User Input

This is a very indirect cause of this vulnerability (primarily a mindset issue). To provide an experience where one can "call server functions directly from the client" or "represent the internal state of a Promise," RSF highly automates the deserialization of input values on the framework side.

Because of this high level of flexibility, rigorous validation could not be performed sufficiently, resulting in unintended gaps for developers.


Summarizing the discussion so far, RSF is characterized by the following elements:

  1. Difficulty of implementation due to the adoption of a proprietary protocol (the cause of this vulnerability)
  2. Responsibility for validating user requests is left to the developer (can be a cause of vulnerability in the site itself)

While it is innovative as a means to solve various problems, the complexity becomes apparent when it comes to actual use.


Now, moving on to the story of Astro, the SSG.

In the case of Astro

There is a feature called Astro Actions as a means to solve the same challenge (easier form submission and data processing). Let's see how Astro approaches this challenge.
https://docs.astro.build/en/guides/actions/

What is Astro Actions?

It is a feature that allows you to easily implement server-side processing from Astro components.

I will provide a broad overview of the flow of Astro Actions.

You define actions to be executed on the server side in a file named src/actions/index.ts. If you are receiving input, you need to specify a schema for that input.

src/actions/index.ts
import { defineAction } from "astro:actions";
import { z } from "astro/zod";

export const server = {
  // Uses a dedicated function called defineAction
  getGreeting: defineAction({
    // Schema definition is mandatory
    input: z.object({
      name: z.string(),
    }),
    handler: async (input) => {
      return `Hello, ${input.name}!`
    }
  })
}

And then, you call the process within any component. It can be called in the format of actions.actionName.

src/pages/index.astro
---
---
<button>Get greeting</button>

<script>
import { actions } from 'astro:actions';

const button = document.querySelector('button');
button?.addEventListener('click', async () => {
  // Here, `actions` refers to the `server` object exported from src/actions/index.ts
  const { data, error } = await actions.getGreeting({ name: "Houston" });
  if (!error) alert(data);
})
</script>

[1]

When using actions, the editor's autocomplete feature works.

And when you click the button, an HTTP request like the following is executed:

Request
POST /_actions/getGreeting/ HTTP/1.1
Host: localhost:4321
Content-Length: 19
Content-Type: application/json

{
    "name": "Houston"
}
Response
HTTP/1.1 200 OK
Content-Type: application/json+devalue
Vary: Origin

[
    "Hello, Houston!"
]

That concludes the overview of Astro Server Actions.

Comparison with RSF

Implementation Difficulty Due to Proprietary Protocol

Astro Actions uses primitive JSON (application/json) for user input. Consequently, its internal implementation is much simpler compared to RSF.

This makes gadget chains (a vulnerability where an attacker can execute arbitrary code by linking internal object states or references to manipulate method call order) through chunk references difficult to occur. The risk of prototype pollution by restoring unintended objects on the server side during deserialization is also significantly reduced. This is why vulnerabilities like react2shell do not occur in Astro.

Responsibility for Validating User Requests

In Astro Actions, validation via Zod is always performed on the server. Inputs are defined using Zod schemas instead of simple function arguments.

Zod is used as a "defensive wall that prevents the framework's processing from starting unless it passes the defined schema." Furthermore, Zod improves input reliability by stripping away unintended properties during parsing.

Aside: In Other Words, RPC

Both Astro Actions and RSF are types of RPC (Remote Procedure Call) that perform communication between the frontend and backend in the form of function calls. However, Astro Actions are designed with an emphasis on security and readability in their RPC implementation. This is because Astro Server Actions is a more recent technology compared to RSF and was designed by taking RSF's design and implementation as a preceding case. [2]

While RSF abstracts away REST APIs as direct server function calls, Astro Actions abstracts them into a format that corresponds one-to-one with REST API endpoints, such as actions.actionName.

Astro Actions are designed to eliminate the boilerplate of creating APIs and setting up endpoints one by one.

This kind of API design is also adopted by other frameworks like Hono RPC.

Summary

Astro Actions solve the same challenges as RSF through different means, such as using the simple JSON format, enforcing schema validation, and separating server code.

Astro is not a replacement for React. Despite being born from entirely different needs, it encountered the same challenges. Consequently, the Astro team observed the approaches of other (non-competing) frameworks to seek out a better way. Astro Actions was the result of that process.

Special Thanks

This post was the inspiration for this article. Thanks!!

https://zenn.dev/connect0459/scraps/daff522bc625a5

I also referred to this article for some parts.

脚注
  1. Houston is the name of the official Astro mascot (?). Kawaii ↩︎

  2. https://github.com/withastro/roadmap/issues/898#:~:text=So far%2C Astro,actions%2C and more. ↩︎

Discussion

odanodan

こちらの記事について Twitter での言及当初から公開されることを楽しみにしていて、たいへん興味深く読ませていただきました。

1点質問があります。

自分はこの記事を「Astro は Server Actions の呼び出しに JSON を使っていて zod で validation しているから安全」という意味で読み取りました。
また自分は React2Shell は Flight プロトコルを実行するときに RCE に繋がるバグがあったと理解しています。
zod による validation の話はユーザーの入力値の検証 (フレームワークの使い方) であるのに対して、React2Shell は Flight プロトコルの実装の考慮漏れ (フレームワーク内部の実装ミス) が原因なので、議論のレイヤーが噛み合っていないと感じています。

具体的には Server Actions の input を zod で validation する話は、RSC だと Server Function の引数を validation する話に対応していると思います。
今回の脆弱性は引数の validation が不足していたのが原因ではないので、議論が噛み合っていないと感じた次第です。
RCE に繋がる Flight プロトコルの payload は、 Flight プロトコルとしては valid なものであったはずなので、zod の validation があっても防げないと考えています。

こちらの解釈だとタイトルと記事の内容が一致しないと思うのですが、なにか解釈が誤っているところがあれば教えていただきたいです。

Hideki IkemotoHideki Ikemoto

実装でなく設計の違いですね。入力が信頼できないしPromiseとか渡す必要性がないからAstroはdevalueをサーバ→クライアントにしか使っていない。一方でRSC(RSF)は両方にFlightプロトコルを使っている(ですよね?)。そしてそれが設計思想。

FlightReplyServer are for client->server and ReactFlightClient is for server->client. They're not 100% symmetrical.
(中略)
This PR brings those changes to synchronize the two approaches.
https://github.com/facebook/react/pull/35277

これが根本的な違いです。間違った思想で設計しているからこんな20年前の脆弱性が起きます。実装の問題ではありません。

れやかれやか

@odan
とても参考になるご指摘ありがとうございます。

自分はこの記事を「Astro は Server Actions の呼び出しに JSON を使っていて zod で validation しているから安全」という意味で読み取りました。

この記事は以下の内容を想定して書いたものです。

  1. Astro Actionsではreact2shellを引き起こした仕組みが存在しない。
  2. そのほかにもサイトに脆弱性が生まれないようにする仕組みがある。

Astro ActionsのZodの下りは 2. を想定して書いたものです。タイトルが 1. の内容にしか触れていない上、記事の中でこの二つをうまく切り分けられていませんでした。

こちらの解釈だとタイトルと記事の内容が一致しないと思うのですが、

こちらのご指摘はおっしゃる通りです。

よって、タイトルおよび本文の一部を変更しましたので、ご再読いただけると幸いです。

odanodan

なるほど、ご説明ありがとうございます
たしかに後者の説明なら違和感がないと思いました

修正後の内容も再度読みました
重ねてありがとうございましたmm

れやかれやか

@ikemo
コメントありがとうございます。

実装でなく設計の違いですね。入力が信頼できないしPromiseとか渡す必要性がないからAstroはdevalueをサーバ→クライアントにしか使っていない。一方でRSC(RSF)は両方にFlightプロトコルを使っている(ですよね?)。

はい、そのように理解しています。RSFは入出力の自由度を得るためにFlightプロトコルを使って双方向にやり取りしています。

これが根本的な違いです。間違った思想で設計しているからこんな20年前の脆弱性が起きます。実装の問題ではありません。

RSFの設計思想は間違いとは言えません。言い訳がましく聞こえるかもしれませんが、この記事は「ReactよりAstroのほうが優れている」というような対立を煽ることを意図していません。結局は入力の自由さと入力の検証についてはトレードオフであったと考えています。