👿

"use server";を勘違いして使うと危ない

2024/10/10に公開

ムーザルちゃんねるのzaruです、こんにちは。今回はNext.js AppRouterのServer Actionsで "use server"; を勘違いして使うと危ないケースがあるよというのを紹介します。

以前書いた"use server"; でexportした関数が意図せず?公開されるとはまた違うケースだったので改めて書きます。

今回のケースが発生することは稀だと思いますが、Server Actionsの仕組みを勘違いしてしまっていたり雰囲気で使っているとセキュリティリスクをはらむコードになってしまいます。

例として、以下のようなフォームのデータフローを実装したとします。3つのファイルに分かれています。

<form> のデータを submitForm() 関数で受け取り、バリデーションを実行します。バリデーションに問題がなければ insertRecord() 関数でデータを保存します。

さて、この動作確認のためにフォームからサブミットすると以下のエラーになります。

なるほど、ブラウザ環境でPrisma ORMを使っていておかしいぞと。エラーの発生場所は insertRecord.ts です。まさにそこでデータベースに保存する処理を書いています。エラーログが見やすくて良いですね。

あとはこのエラーを直すだけです。

勘違いによる悲劇

このコードの実装者はServer Actionsの "use server"; を「サーバ側で処理するファイルに付けるもの」と勘違い していました。

上記のPrismaエラーログを見て「あ、そうか。 "use server"; を付け忘れちゃったな…」と思い、以下のように insertRecord.ts"use server"; を付け加える修正をします。エラーが出ていた箇所ですし、データベースを操作している箇所ですからね。

動作確認をするとエラーも消え正常にデータベースに保存されています。よしOK!

そしてこれが悲劇の始まりです。

この実装の処理をフロントエンド・バックエンドに色分けすると以下のようになります。

データベースの保存は意図した通り "use server"; を付けることによって正しくサーバ上で実行されています。しかし、submitForm() 関数はフロントエンドで動いています。つまりブラウザ上のバリデーションになっています。

本来サーバ上で実行されるべきバリデーションがブラウザ側で実行されてしまっています。そして insertRecord() 関数が "use server;" を付けたことによって無防備に露出してしまっています。

Server Actionsなので外部からPOSTリクエストで直接呼び出すこともできますし、ブラウザ上でバリデーションのJSロジックを改変し、どんなデータでも渡せるようにすることもできます。

正しい対応は submitForm() 関数のファイルに "use server"; を付けることです。

こうすることで、バリデーションJSコードが改変されることもなく、データベース操作している関数が外部に露出することもなくなりました。

フロントエンドとバックエンドの境界線を意識せず開発できることがReact Server ComponentやServer Actionsの良さですが、その挙動が曖昧のままだとこのようなリスクのあるコードを書き得てしまいます。

同じTypeScript言語でそのままフルスタックに書けるがゆえに、このコードはどこで実行されるのか?というのを常に意識しながらプログラミングをしていきたいですね。

ムーザルちゃんねる

Discussion