本文へスキップ

Appwrite vs Supabaseの安全性:同じ攻撃で比較

Markdownで表示Claudeで開くChatGPTで開くPerplexityで開く

質問もコピーされます。チャットが空で開いた場合は貼り付けてください。

TL;DR

どちらも新しいテーブルはデフォルトで閉じています。Appwriteはゲストにもサインイン済みユーザーにも401を返し、SupabaseはテーブルをAPIロールにgrantするまで401/403を返します。違いが出るのは、ルールが間違っているときや、リクエストがユーザーをまたぐときです。Supabaseでは、他のユーザーの行へのupdateが200 []を返し、緩いinsertポリシーのせいでユーザーが別の会社に参加できました。Appwriteでは、私のテストでユーザーをまたぐ試みはすべて401か404で終わり、ユーザーは自分の行を、自分自身か全員以外の誰にも付与できませんでした。Appwriteのサーバーキーにはscopeがあり、Supabaseのsecret keyはすべてのポリシーを素通りします。

Denysです。フルスタック・AIエンジニアで、私が作る、あるいは直すアプリのほとんどはSupabaseで動いています。バックエンドをまだ選んでいるクライアントからは、Appwriteはセキュリティ面でどうなのかとよく聞かれます。機能の一覧では答えが出ないので、両方に同じリクエストを送りました。

テスト方法。 Appwrite 2.3.0を2026年10月30日にDockerでセルフホストし、REST API経由で、ゲスト、サインイン済みの2人のユーザーAとB、2つのサーバーキーとして実行しました。Supabase側は、2026年10月24日にローカルスタック(CLI 2.109.1、Postgres 17.6)で同じロールを使って行った私のテストで、詳細はSupabase RLSポリシー例にあります。どちらも架空の行です。

AppwriteとSupabaseはデータをどう守る?

SupabaseはPostgresでテーブルを守ります。grantがAPIロールがそもそもテーブルに届くかを決め、SQLで書くrow level securityポリシーが、各リクエストが見られる行と書き込める行を決めます。Appwriteは権限をデータの隣に持ちます。テーブルには、読み取り、作成、更新、削除を許可するロールのリストがあり、row securityをオンにすると各行が自分のリストを持ちます。Appwriteの権限のドキュメントでは、アクセスは付与されるだけで拒否されることはなく、ユーザーにはテーブルレベルか行レベルのどちらかで必要で、両方は要りません。

つまり、問題はどちらにアクセス制御があるかではありません。両方にあります。問われるのは、それぞれがどんな間違いを許してしまうかです。

新しいテーブルをそれぞれどう扱う?

リクエスト Appwrite、権限の設定なし Supabase、RLSオン、grantなし(10月24日)
ゲストが行を一覧する 401 user_unauthorized 401 permission denied
サインイン済みユーザーが行を一覧 401 user_unauthorized 403 permission denied
サインイン済みユーザーが行を追加 401 No permissions provided for action ‘create’ 未テスト

作ったばかりのテーブルは、どちらでも閉じています。Supabaseではこれは新しい挙動です。2026年10月30日以降に作られたテーブルには、Data APIのgrantが自動では付かなくなりました。これはSupabase 10月30日のgrant変更:壊れる点と確認方法で扱いました。

テーブルが全員に開いているとどうなる?

2つの「開いた」設定は同じものではありません。Appwriteでは、テーブルにread("any")という権限を1つだけ与えました。Supabaseで最もよくある開いた状態は、RLSがオフで、デフォルトのgrantが残っている状態です。

ゲストのリクエスト Appwrite、read("any") Supabase、RLSオフ、デフォルトgrant(10月24日)
行を一覧 200、2行とも 200、2行とも
行を削除 401 200、2行とも削除された

Appwriteの「公開」は、書き込みも付与しない限り読み取り専用です。RLSがオフのSupabaseテーブルは、publishable keyを持つ誰でも読み書きできます。だから「RLS disabled in public」が最初に確認する項目なのです。

「自分の行」はそれぞれどう動く?

Appwriteでは、row securityをオンにして、テーブルにcreate("users")という権限を1つだけ与えました。Aが権限を指定せずに行を作ると、Appwriteが自分で権限を設定しました。ユーザーAだけのread、update、deleteです。Supabaseで同じ意図を実現するには、(select auth.uid()) = user_idに対するポリシーが4つ必要です。

リクエスト Appwrite(10月30日) Supabase(10月24日)
ゲストが一覧 200、0行 401(anonにgrantなし)
Aが行を一覧 200、Aの2行 200、Aの2行
AがBの行を読む 404 row_not_found 一覧には返らない
AがBの行をupdate 401 200 []
AがBの行をdelete 401 200 []
同じリクエスト、2つのバックエンド。ユーザーAが、それぞれのREST API経由でユーザーBの行を編集。Supabase、所有者専用ポリシー:ユーザーA → Bの行を更新 → 200 [] → 0行、エラーなし、Appwrite、row securityオン:ユーザーA → Bの行を更新 → 401 → エラーが返る。
図1. 私のテスト:ローカルのSupabaseスタック(CLI 2.109.1)、2026年10月24日、セルフホストのAppwrite 2.3.0、2026年10月30日。

どちらもBの行は漏らしませんでした。違いは応答です。Supabaseはポリシーが隠す行を除外するので、何にも一致しなかったupdateは、ゼロ行の成功になります。「エラーなし」を「保存できた」とみなすアプリは、編集できたとユーザーに伝えます。Appwriteは、アプリが見逃せないエラーで拒否します。

ユーザーは自分のデータを他人に渡せる?

生成されたコードが最も間違えやすいところなので、両方で試しました。

Aのリクエスト Appwrite Supabase(10月24日)
ユーザーBが読める行を作る 401, Permissions must be one of: any, users, user:A… ポリシー次第
自分の行をBの所有に変更 401、同じメッセージ 完全なwith checkなら403、with check (true)なら200
全員が読める行を作る 201、その後ゲストが一覧できた ポリシーが許す場合のみ

Appwriteは、ユーザーが付与できるものを、自分自身が持つロール(any、users、自分のユーザー、自分のチーム)に限定します。これにより、私がルールを書かなくても「自分の行を他人に渡す」操作は止まりました。ただし、Aがread("any")で自分の行を全員に公開することは止められませんでした。行を絶対に公開してはいけないなら、権限を設定するのをクライアントにさせず、サーバー関数から作成してください。

Supabaseでは、結果はポリシーの書き方しだいです。完全なwith checkは移動を403で拒否しました。一部のジェネレーターが書くwith check (true)では、AがBのアカウントへ行を移せました。

ユーザーは他人の会社に参加できる?

Supabaseでは、チームのアクセスは通常membershipsテーブルとポリシーで実現します。私のテストでは、多くのチュートリアルが勧めるinsertポリシーwith check (user_id = auth.uid())のせいで、Aは自分を会社2に追加して201 Createdを得て、その給与プロジェクトを読めました。

Appwriteにはチーム機能が組み込まれています。Bがチームを作り、Aはメンバーとして、そしてオーナーとして自分を追加しようとしました。どちらのリクエストも404 team_not_foundを返しました。Aはチームを見ることすらできなかったのです。チームへの所属はサーバーが決めるもので、あなたが書くルールではないため、この漏洩は起こりえません。ただし、行を共有するにはread("team:…")権限が必要で、それは自分で設定します。

サーバーキーは何を開く?

キー Appwrite Supabase(anon vs service roleのテストより)
フルのサーバーキー 200、両ユーザーの4行すべて 3行すべて、RLSを素通り
狭いscopeのキー(users.read) 401, missing scope rows.read scope付きキーはない

Supabaseのsecret keyはservice_roleに対応し、BYPASSRLSを持ちます。何が開くかはanon keyとservice role keyの違いにあります。Appwriteのキーも行の権限を無視しますが、作成時のscopeに限られるため、ユーザーを管理する処理用のキーでは、あなたのテーブルは読めません。どちらのキーも、ブラウザのバンドルに入れば、届く範囲すべてが漏れます。

Appwriteの小さな発見が2つあります。テーブルの一覧を求めたゲストとサインイン済みユーザーは、どちらも401でした(この呼び出しにはtables.readが必要で、それを持つのはキーだけです)。そしてread("users")は、サインイン済みのあらゆるアカウントを意味します。サインアップが開いているSupabaseでのauth.uid() is not nullと同じ落とし穴です。

どちらが安全?

どちらもデフォルトで安全とも危険とも言えません。どちらも閉じた状態から始まりました。違いは、間違いが潜む場所です。

質問 Appwrite Supabase
アクセスルールの置き場所 テーブルと行のロールのリスト SQLのgrantとポリシー
拒否されたユーザー間の書き込み エラー(401 / 404) updateとdeleteは0行の200
行を他者に付与 ユーザーが持つロールにのみ ポリシーが許す範囲
列の値、MFAレベルに基づくルール 権限にはない。サーバーコードが必要 ポリシーで可能。例:機密行にaal2
サーバーキー scope付き secret keyはすべてのポリシーを素通り

Appwriteのモデルのほうが狭く、私のテストでは、その狭いモデルが、何も書かなくてもユーザー間のあらゆる手口を拒否しました。SupabaseのモデルはSQLなので、Appwriteの権限では表せないルール、たとえば「MFAのときだけ」や「自分が所属する会社の行だけ」を表現できますが、間違ったルールも表現できてしまいます。チームがポリシーを読まないなら、Appwriteのデフォルトのほうが許容範囲が広いです。データに依存するルールが必要なら、Supabaseでは書けますが、そのあとテストが必要です。私の方法はSupabase RLSのテスト方法にあります。

Supabaseプロジェクトをどう確認する?

無料のSupabaseセキュリティチェックは、プロジェクトを読み取り専用で読み、RLSがオフのテーブル、常に真になるポリシー、ゲスト向けの書き込みポリシー、ポリシーが示す以上の権限を与えるgrantを指摘します。Supabaseが守ってくれる範囲と、あなたに任される範囲はSupabaseは安全?にあります。Appwriteに相当するものはありません。Appwriteでは、上のリクエストを2人のユーザーとして実行してください。Firebaseについては、同じ種類の攻撃をFirebaseセキュリティルールの例で試しました。

制限。 ノートPC上のセルフホストのAppwrite 2.3.0を1つ、ローカルのSupabaseスタックを1つ、6日の間隔で、架空の行で試しました。Appwrite Cloudはセルフホストと異なる可能性があります。Storage、関数、realtime、認証設定、コンプライアンス認証は、ここでは比較していません。

非公開データのあるアプリのバックエンドを選んでいますか?

アクセスモデルを一緒に見ることができれば嬉しいです。売り込みはありません。

お問い合わせ

© 2026 Denys Zaichenko. 元の記事: https://madsalt.tech/ja/blog/appwrite-vs-supabase/