このチェックリストは、ShiftBのAIシフトコース20章「公開前セルフレビュー」で受講生が実際に使っているものを、初めての人でも使えるように書き直したものです。
AIに頼めば、ログイン付きのアプリが1日で動くところまで作れます。ただ、アプリが動いていることと、他人のデータを預かってよい状態であることは別の話です。前者は画面を見れば分かりますが、後者は画面を見ても分かりません。
項目は「公開したあとで気づいても手遅れになりやすい順」に並べています。上から順に潰していけば、万が一のときにも、取り返しがつかないものから守られます。
前提にしている構成は Next.js × Supabase × Vercel です。AIに「ログイン付きのWebアプリを作って」と頼むとよく選ばれる組み合わせで、コースでもこの構成を使っています。別の構成で作っている場合も、考え方はほぼそのまま使えます。
全部を一度にやる必要はありません。小さなアプリなら1〜5番だけでも、最低限のラインは越えられます。逆に、名前やメールアドレスなど他人の情報を預かるアプリなら、1〜7番は手を抜かないでください。
使い方:洗い出しはAIに、判断は自分で
一人で上から読んで手を動かすより、Claude Codeなどのコーディングエージェントに洗い出しを頼む方が速いです。コースの20章でもこのやり方をとっています。
アプリのフォルダでAIを起動して、下の文章をそのまま送ってください。
このリポジトリを、下のチェックリストに沿って確認してください。
まだ修正はしないでください。
項目ごとに「対応済み」「未対応」「判断できない」のどれかと、そう判断した根拠(ファイル名や設定の場所)を返してください。
Supabaseの管理画面など、コードからは確認できない項目は「判断できない」にして、私が確認する手順を書いてください。
1. 秘密のキー(.env)がGitHubに上がっていない。過去のコミットにも混ざっていない
2. Secret key(旧service_role)など秘密の値に NEXT_PUBLIC_ を付けていない
3. Supabaseのpublicスキーマの全テーブルでRLSが有効で、不要な許可ポリシーがない
4. 他人のデータを、URLやIDの書き換えで見たり変えたりできない
5. ログインが必要なAPIは、API側でも毎回 getUser() でログインを確かめている
6. 認証Cookieに httpOnly が付いていて、document.cookie から読めない
7. Supabase Authの設定(Site URL・Redirect URLs・メール確認・メール送信)が本番向けになっている
8. APIの入口で、入力をサーバー側でも検証している
9. Storageのバケットが公開と非公開で分かれ、ファイルの種類と上限サイズが設定されている
10. Vercelのお試し環境(Preview)に本番のキーを入れていない
11. 使っているパッケージに、既知の脆弱性や古すぎる版がない
12. エラー画面や404で、内部の情報を見せていない返ってきた「未対応」を、上から1つずつ直していきます。直すときも、AIにいきなり書き換えさせるのではなく、先に「なぜ危ないのか」を説明してもらってください。そうしておくと、次に同じ穴を作りそうになったとき、自分で気づけるようになります。
AIが「対応済み」と答えても、4番と6番だけは必ず自分の手で試してください。この2つは、実際にブラウザで操作しないと本当のところが分かりません。「AIが大丈夫と言ったので」は、事故が起きたときに誰にも受け取ってもらえない説明です。
データと鍵を守る(ここが一番大事)
1〜5番は、抜けていると公開した瞬間から誰かにデータを触られる可能性がある項目です。ほかを全部仕上げても、ここが抜けているなら公開は待ってください。
1. 秘密のキーがGitHubに上がっていない
データベースの接続情報や、決済サービスなどの秘密のキーは、.env というファイルに書いておくのが普通です。このファイルがGitHubに上がると、リポジトリを見られる人なら誰でもそのキーを使えます。公開リポジトリなら、世界中の誰でもです。
確かめ方
.gitignoreというファイルに.envと.env.localが書かれている過去のコミットにも混ざっていない。ターミナルで次を実行して、キーの値そのもの(長い英数字)が出てこなければOKです。変数名が出てくるだけなら問題ありません。
git log -p | grep -iE 'service_role|sb_secret|sk_live|sk_test'もしキーの値が出てきたら、ファイルを消すだけでは足りません。GitHubの履歴に残り続けるので、各サービスの管理画面でキーそのものを作り直してください。
2. ブラウザに秘密のキーを配っていない
Next.jsでは、環境変数の名前が NEXT_PUBLIC_ で始まると「ブラウザに配ってよい値」として扱われます。つまり、アプリを開いた人なら誰でも中身を見られます。
Supabaseのキーには2種類あります。ブラウザに配ってよい Publishable key(sb_publishable_ で始まる)と、サーバーだけで使う Secret key(sb_secret_ で始まる。古いプロジェクトでは service_role キー)です。Secret key は、次の3番で説明するデータベースの鍵を素通りできる強い権限を持っています。NEXT_PUBLIC_ を付けた時点でアウトです。
確かめ方
プロジェクト全体を
NEXT_PUBLIC_で検索して、出てきた値を1つずつ「世界中の人に見られても困らない値か」で判定する
Publishable key は見られても構いません。ただし、それだけでデータが守られているわけではありません。データを守るのは、次の3番と4番です。
3. データベースに外から直接さわれない(RLS)
Supabaseには、ブラウザから直接データベースを読み書きできる窓口(Data API)があります。RLS(行レベルセキュリティ)は、その窓口に「誰がどの行を触れるか」の条件を付ける仕組みです。
RLSが無効なテーブルは、Publishable key を知っている人なら誰でも読み書きできます。そして Publishable key は、2番で書いたとおりブラウザに配られています。
AIで作ったアプリでは、ここが特に抜けやすいです。2025年には、AI開発ツールのLovableで作られて公開されていたアプリ1,645件を研究者が調べたところ、約170件で利用者のデータに外からアクセスできる状態だったと報告されています。
確かめ方
Supabaseの管理画面で、publicスキーマのすべてのテーブルのRLSが有効になっている
管理画面の Advisors から Security Advisor を開き、指摘が残っていない
使っていない「誰でも読める・書ける」ポリシー(許可のルール)が残っていない
コースの構成のように、データの読み書きをすべて自分のAPI(サーバー側)経由にしている場合は、Data APIの許可ポリシーは1つも要りません。RLSを有効にして、窓口を全部閉じておけば大丈夫です。そのぶん、守りの本体は次の4番と5番、つまりAPI側の確認になります。
反対に、ブラウザから直接データベースを読み書きする作りの場合は、必要な操作だけを、本人の行に限って許可するポリシーを作ります。作ったら、次の4番の方法で本当に他人の行に触れないかを確かめてください。
4. 他人のデータが見えないことを、2つのアカウントで試した
よくあるのが、URLの中のIDを1つ書き換えると、他人のデータが見えてしまうパターンです。「ログインしているか」は確認していても、「そのデータの持ち主か」を確認していないと起きます。AIに機能を頼むときにここまで指定しないと、抜けることがあります。
確かめ方
ブラウザの通常のウィンドウでアカウントAにログインし、データを1つ作る
そのデータを開いたときのURLをコピーする(
/posts/12のような形)シークレットウィンドウでアカウントBにログインし、コピーしたURLを開く
見えてはいけないものが見えたら要修正。編集や削除も同じように試す
公開記事のように、もともと誰でも見てよいデータは見えて問題ありません。見えてはいけないものが見えたら、API側で「ログイン中のユーザーがそのデータの持ち主か」を確かめる処理を足します。AIに頼むなら「すべてのAPIで、ログイン中のユーザーIDでデータを絞り込んでください」と具体的に伝えると通じます。
5. ログインが必要なAPIは、API側でも毎回ログインを確かめている
Next.jsには proxy.ts という、ページを表示する前に未ログインの人を追い返す仕組みがあります(Next.js 15以前は middleware.ts という名前でした)。便利ですが、これは入口に立つ警備員のようなもので、最後の鍵ではありません。設定の書き方ひとつで、守っているつもりのページが対象から外れることもあります。
もう1つ覚えておいてほしいのは、APIは画面にボタンを出していなくても誰でも呼べるということです。ブラウザのアドレス欄にURLを打てば届きます。だから、データを返す側(app/api の中の route.ts)でも、毎回ログインを確かめます。
確かめ方
ログアウトした状態で、ログインが必要なAPIのURL(
/api/admin/postsなど)をブラウザで直接開き、「ログインが必要です」のようなエラー(401)が返ってくるサーバー側のログイン判定に
supabase.auth.getUser()を使っている。getSession()だけで判定していない(getSession()は、サーバーに本人確認をしにいかないため)
ログインまわり
6. ログインの証(Cookie)を、JavaScriptから読めない
ログイン中の人のブラウザには、本人であることを証明する情報(トークン)がCookieとして保存されています。これがJavaScriptから読める状態だと、悪意のあるスクリプトがページで動いてしまったとき(XSSと呼ばれる攻撃)に、丸ごと持ち去られます。持ち去られたトークンがあれば、攻撃者はその人になりすましてデータを見たり書き換えたりできます。
これは自分のアプリで、今すぐ試せます。
確かめ方
自分のアプリにログインする
開発者ツールのコンソールを開く(Chromeなら、Macは option + command + J、Windowsは Ctrl + Shift + J)
document.cookieと入力してEnterを押すsb-で始まる長い文字列が表示されたら要対応
表示された文字列は暗号ではなく、見た目を変えただけのものです。元に戻すと、ログインの鍵(access_token)と、それを更新するための鍵(refresh_token)がそのまま出てきます。
対策は、Cookieに httpOnly という印を付けて、JavaScriptから読めなくすることです。httpOnly が付いたCookieは、今まで通りサーバーへは自動で送られますが、document.cookie には出てこなくなります。Supabaseのサーバー用ライブラリ(@supabase/ssr)は初期設定で httpOnly が付いていないので、自分で指定する必要があります。
コースの18章では、ログインの処理をブラウザからサーバーへまるごと引っ越しさせて、ここを直します。目安8時間の章です。AIに「httpOnlyにして」と一言頼めば終わる話ではない、ということは先にお伝えしておきます。httpOnly にすると、ブラウザ側のプログラムからはログイン状態を読めなくなります。そのため、サインイン・ログアウト・ログイン状態の確認・画像のアップロードまで、関係する処理を全部サーバー側に移す必要があるからです。
7. ログインの設定が本番向けになっている
Supabaseの管理画面の Authentication で確かめます。
Site URL と Redirect URLs が本番のドメインになっている。
*(ワイルドカード)を使っていないメールアドレスの確認(Confirm email)がONになっている。OFFだと、他人のメールアドレスで登録できてしまう
登録やログインのメールを、Resendなどのメール送信サービス(カスタムSMTP)から送っている。Supabase標準の送信はお試し用の位置づけで、送れる数や宛先に制限があります。利用者が増えたとたんに「確認メールが届かないのでログインできない」が起きます
パスワードの変更や退会のような重要な操作の前に、もう一度本人確認を挟んでいる
入力とファイル
8. 入力チェックをサーバーでもやっている
フォームに入力チェックを付けても、それは画面の上だけの話です。5番で書いたとおりAPIは直接呼べるので、チェックを飛ばした値もそのまま届きます。サーバー側(route.ts の入口)で、Zodなどのライブラリを使ってもう一度確かめます。
利用者が入力したURLをリンクにする場所で、
javascript:で始まる値を弾いているdangerouslySetInnerHTMLを使っている場所があれば、DOMPurifyなどで危ないタグを取り除いている(使っていなければOK)ログイン後に「元のページへ戻す」ような処理がある場合、戻り先のURLを許可リストで確かめている
9. 画像やファイルの置き場所を、公開と非公開で分けている
Supabase Storageのバケットを、誰でも見てよいもの(Public)と、本人だけが見るもの(Private)に分けている。個人情報を含むファイルをPublicのバケットに置いていない
Privateのファイルは、有効期限付きのURL(
createSignedUrl)で渡しているバケットに、受け付けるファイルの種類(Allowed MIME types)と上限サイズ(File size limit)を設定している
本番の環境
10. お試し環境に、本番のキーを入れていない
Vercelでは、変更をGitHubに上げるたびにお試し用のURL(Preview)が作られます。環境変数は、本番(Production)とお試し(Preview)で別々に設定できます。Previewに本番のデータベースや決済のキーを入れないようにしてください。お試しのつもりでした操作で、本番のデータが消えるのを防ぐためです。
11. 使っている部品が古いままになっていない
アプリは、たくさんの部品(パッケージ)を組み合わせてできています。部品に穴が見つかると、修正版が出ます。古いまま放っておくと、穴の開いた部品を使い続けることになります。
node -v
npm outdated
npm auditただし npm audit fix --force は、何が変わるかを確かめずに実行しないでください。大きな変更が入って、アプリが動かなくなることがあります。AIに「npm audit の結果を見て、影響と直し方を説明して」と頼み、中身を理解してから上げるのが安全です。Next.jsを使っているなら、公式ブログのセキュリティのお知らせも、自分のバージョンが対象になっていないか見ておきます。
12. エラーの画面で、内部の情報を見せていない
存在しないページにアクセスされたとき、ちゃんと404を返している(中身が空のページを正常な扱いで返していない)
エラー画面に、エラーの詳細(スタックトレース)やファイルの場所を表示していない
error.tsxとnot-found.tsxを用意している
全部できなくても、理由が書ければいい
12個すべてを、最初から満たす必要はありません。ファイルを扱わないアプリなら9番は飛ばせますし、メールを送らないなら7番の一部は要りません。
コースで受講生にお願いしているのは、「今回は対応しない」と決めた項目に理由を書いてもらうことです。小規模だから、個人情報を扱わないから、といった理由です。理由が書けるなら、それは見落としではなく判断です。
逆に、理由が書けない項目は、たいてい中身がまだよく分かっていない項目です。調べるならそこからが効率的です。
僕が自分のサービスで先に決めていること
最後に、このリストとは別に、僕が自分のサービスを作るときに最初に決めていることを書いておきます。チェックリストが「どう守るか」だとすると、こちらは「守る範囲をどう小さくするか」の話です。
個人情報は、必要になるまで取らない。持っていないデータは漏れません。「念のため」「あとで分析に使えるかも」で、住所や電話番号、生年月日を入力させない。必要になってから足せば十分です
DMやチャットは作らない。人はDMに何でも書きます。電話番号も、人に言いにくい相談も、ときにはパスワードまで。それを一人で守り続ける仕組みを運用するのは大変なので、連絡はメールや外部のツールに任せます
任せられるところは任せる。サーバーは自分で組まずにVercelやSupabaseを使い、ログインはSupabaseの認証機能、決済はStripeの決済画面に任せる。そうすると、自分が守らなければいけない範囲が「自分のコードと設定」まで縮みます
一人で守れる範囲には限界があります。だから先に守る範囲を小さくして、残った部分だけはこのチェックリストで確かめる。僕はこの順番で考えています。
AIシフトコースでは、ここをどう学ぶか
ここまで書いたことは、まだまだごく一部で、どんなサービスでも共通するような一般的な内容です。
実際の開発では機能ごとにチェックすべきこと異なり、
自分のサービス、またユーザーを保護していかないといけません。
これからエンジニアになっていったり、自分で作ってみたいものがあるという方は
ShiftBのAIシフトコースで、基礎から学び、AIを使った開発を伴走支援します。
その中で、個別のセキュリティのチェックや、リリース後の運用まで学ぶことができます。
作りたいもの、あるいはもう作ったものについて、どこから学ぶのがよさそうか知りたい方は、個別相談で一緒に整理しましょう。学習にかかる時間や、サポートの範囲もそこでお伝えします。
コースの中身を先に見ておきたい方は、AIシフトコースの紹介ページをどうぞ。


