権限を設定した。テストを実行した。レビューも依頼した。
ここまでは進めやすいのですが、もう一歩踏み込むと、こんな疑問が出てきます。
そのテストは、本当に守りたい境界を確かめているのか。設定を間違えたとき、ちゃんと失敗するのか。
この記事では、自分のサービスで進めているセキュリティ点検から、「設定した」を「確かめた」に変えるための考え方と、検証の仕組みを紹介します。
この記事の範囲: 隔離したローカル環境と架空データで確認した内容と、これから取り組む項目を分けて書いています。本番環境の安全性を保証するものではありません。
この記事で書くこと、書かないこと
- 書くこと: 点検の全体像、どんな場面で使えるか、検証の仕組みと設計の考え方
- 書かないこと: 個別のサービス構成、表やロールの具体名、非公開の指摘内容、検証スクリプトそのもの
点検の方法は共有する価値がありますが、「どこに何があるか」まで公開すると、攻撃の手がかりになるためです。
🔰 この記事に出てくる言葉
- 認可: 「あなたはこれをしていいですか」を確かめること。ログイン(認証)とは別のものです。
- データベース(DB): 記録を整理してしまっておく倉庫。
- ロール: DBに接続するときの「役割」。役割ごとに、できる操作を決めます。
- RLS(Row Level Security): DBの中で、行ごとに「誰がどの行を扱えるか」を制御する仕組み。
- migration(マイグレーション): DBの表や権限の設定を変えるためのSQLファイル。本番にも、このファイルを適用します。
- 試験(テスト): 期待どおりに動くかを、自動で確かめる仕組み。
- 使い捨て環境: 試験のためだけに作り、終わったら消す環境。本物のデータや接続情報は入れません。
概要:点検の対象を6つに分ける
セキュリティ対策を1つのチェックリストにまとめると、「何かを設定したから全体も大丈夫」と考えやすくなります。そこで最初に、確かめる対象を分けました。
| 対象 | 確かめたいこと |
|---|---|
| アプリの認可 | ログインした人が、許されたデータだけを操作できるか |
| DBの権限 | 接続する役割ごとに、許可していない操作が拒否されるか |
| 検証コード | 対策が欠けたとき、その欠落を検出できるか |
| 共有文書・ログ | 点検結果に、秘密の値や個人情報が混ざっていないか |
| 開発ツールの実行環境 | ファイル、コマンド、外部サービスへのアクセス範囲が適切か |
| 依存関係・端末 | 更新、アカウントの保護、復旧の手順を確認できているか |
この表の全部を確認し終えたわけではありません。まず、繰り返し確認できる「DBの権限の試験」と「共有文書の点検」から整えています。
たとえるなら「鍵をかけた」と「ドアノブを回してみた」
鍵をかけたつもりでも、実際にドアノブを回してみるまでは、本当に閉まっているかは分かりません。さらに言えば、「鍵を外したらちゃんと開く」ことも確かめないと、そもそもドアノブが壊れていて回らないだけかもしれません。
この記事でやっているのは、まさにこの2つです。
- 鍵がかかっているか、回して確かめる(拒否されるか試す)
- 鍵を外したら開くことも確かめる(許可側も試す、対策を外したら試験が落ちるか試す)
どういうふうに使えるか:点検する6つの場面
場面1:新しい表やデータを追加したとき
新しい表を作ったら、RLSと権限を設定するだけでなく、「許可していないロールで実際に操作すると拒否されるか」を試験に加えます。表が増えるたびに、守りの境界も増えるからです。
場面2:権限やロールを変更したとき
権限を変えたら、変更前に通っていた拒否の試験が、変更後も通るかを確認します。うっかり権限を広げてしまったときに、試験が落ちて気づけます。
場面3:依存ライブラリを更新したとき
脆弱性の指摘を受けてライブラリを更新したら、ログインや認可、主要な画面が引き続き動くかを確認します。
場面4:公開や本番適用の前
ローカルで試験が通っても、それは「用意した条件の範囲で通った」という意味です。本番に出す前に、確認できたことと、まだ確認していないことを分けて整理します。
場面5:点検結果を人と共有する前
点検のログや報告書に、秘密の値や個人情報が混ざっていないかを確認します。点検そのものが、情報の持ち出し経路になってしまわないようにするためです。
場面6:新しい開発ツールを使い始めるとき
AIのコーディングツールのように、ファイルを読み、コマンドを実行するツールを使う場合、どこまでアクセスできるかを、架空のファイルで実際に試します。
アーキテクチャ:点検の全体像
点検は、次の流れで組み立てています。
ポイントは3つです。
- 本番と同じファイルを使う: 試験用にSQLを書き直すと、本番に適用するファイルとの差が生まれます。検証の対象は、本番に適用するファイルそのものです。
- 本番から隔離する: 試験は使い捨ての環境で行い、本番の接続情報は使いません。
- 結果の「理由」まで判定する: 「失敗した」だけでは、権限が効いたのか、別の理由で失敗したのか分かりません。
ここから、それぞれの仕組みを詳しく紹介します。
仕組み1:「設定」と「実際のアクセス権」を分けて見る
RLSは、DBで行ごとのアクセスを制御する仕組みです。でも、「RLSを有効にした」という事実だけでは、アプリ全体のデータ分離を説明できません。
たとえば、接続ロールにRLSを迂回する権限(BYPASSRLS)がある場合、RLSは効きません。また、表全体を空にするTRUNCATEのような操作は、RLSの対象外です。(PostgreSQL公式:Row Security Policies)
そこで点検では、次を別々に確認します。
- 対象の表が存在するか
- RLSが有効か、どんなルール(ポリシー)があるか
- 接続ロールが、実際にどんな権限を持っているか
- そのロールで実際にSQLを実行すると、どうなるか
- アプリが、ログインした利用者の情報から操作範囲を決めているか
特に大事なのは、DBの権限の試験と、アプリの認可の試験は別物だということです。DBで制限したロールが拒否されても、アプリが強い権限で接続していれば、アプリ側の認可は別に確かめる必要があります。
仕組み2:使い捨ての環境で、本物に近い試験をする
権限の試験は、使い捨てのDB環境を作り、架空のデータを入れ、本番に適用するSQLファイルをそのまま適用して行います。
接続先も明示します。外部からの接続を受け付けない一時的なDBを用意し、手元のPCにある普段の接続設定に引きずられないようにしています。うっかり別のDBにつながって試験してしまう、という事故を防ぐためです。
仕組み3:「拒否された」の理由まで確かめる
SQLが失敗しても、その原因が「書き方の間違い」や「別の制約違反」なら、権限が効いた証拠にはなりません。
そこで、権限による拒否を期待する試験では、失敗したことに加えて、失敗の理由を表すコードも確認します。PostgreSQLでは、権限不足のエラーコードは42501(insufficient_privilege)です。(PostgreSQL公式:Error Codes)
考え方を疑似コードで示すと、次のようになります。
# 権限による拒否を期待する試験の判定例
assert result.exit_code != 0 # 失敗したこと
assert result.sqlstate == "42501" # 理由が「権限不足」であること
ただし、すべての試験に同じ判定を使うわけではありません。RLSで「行を見せない」設計なら、拒否ではなく「取得件数が0」という結果になります。どの仕組みの、どの動きを確かめる試験なのかを、先に決めておくことが大切です。
仕組み4:許可されるケースも、同じSQLで試す
拒否だけを確認すると、思わぬ落とし穴があります。そもそもSQLが間違っていて、誰が実行しても失敗するなら、拒否の試験は「合格」してしまうのです。
そこで、許可されたロールでは、同じSQLが成功し、期待どおりの結果になることも確かめます。あわせて、試験の中で切り替えたロールが、本当に使われているかも確認します。
試した操作は、読み取り(SELECT)、追加(INSERT)、更新(UPDATE)、削除(DELETE)、全削除(TRUNCATE)の5種類です。表、ロール、操作の組み合わせごとに、拒否と許可の両方を確認しました。
ただし、試験の件数が保証するのは、用意した条件と対象範囲だけです。本番のすべての表や、アプリのすべての経路を検査したという意味ではありません。
仕組み5:テストが緑でも、テスト自体を疑う
今回いちばん力を入れたのが、「試験の試験」です。
よくある落とし穴:部分一致
期待値が0のとき、出力に文字0が含まれているかだけを調べると、10も合格してしまいます。
# 部分一致では、期待しない値も通ってしまう
assert "0" in "10" # 通ってしまう
# 値として取り出し、期待値と比べる
actual = "10".strip()
assert actual == "0" # 正しく失敗する
同じように、対象の表が存在しないのに成功したり、検査対象を1件も取得できなかったのに合格したりすることも防ぎます。
対策をわざと外して、試験が落ちるか確かめる
本番には一切手を加えず、一時的なコピーの上で、対策をわざと1つずつ外してみます。
外してみた対策は、次の5種類です。
| わざと作る変更 | 試験に期待する反応 |
|---|---|
| 権限の取り消しを外す | 想定外の権限を検出する |
| RLSを無効にする | 設定の欠落を検出する |
| 対象の表を外す | 対象が存在しないことを検出する |
| 期待する件数を変える | 件数の不一致を検出する |
| 実行するロールを変える | 想定外のロールを検出する |
正常な状態と、この5種類の変更のすべてで、試験が期待どおりに反応することを確認しました。
ここで注意したのは、「何らかの理由でコマンドが止まった」を検出成功と数えないことです。試験の準備に失敗したのか、期待した不一致を検出したのかを、終了の仕方で区別しています。
対策を外したら、試験が落ちる。 この対応関係まで確かめておくと、「緑」という結果を安心して解釈できます。
仕組み6:点検の出力にも、情報を載せすぎない
点検の結果を共有するとき、ログに秘密の値が残っていると、点検そのものが情報の持ち出し経路になります。そこで、共有する予定の文書だけを対象にした、小さなスキャナーを作りました。
設計で決めたのは、次のことです。
- 対象は、明示したテキストファイルだけ。作業フォルダや履歴を勝手に全部調べない。
- ファイルの数と容量に上限を置く。
- シンボリックリンクのように、別の場所へたどり着ける入力は拒否する。
- 見つけた文字列そのものは出力しない。
- 「見つからなかった」「見つかった」「検査できなかった」の3つを区別する。
結果は「何番目の入力で、どのルールに一致したか」だけを返します。メールアドレスや秘密の値に一致しても、その値を報告にコピーしません。
ただし、これは決まったパターンを探す補助の道具です。人名や文脈から分かる個人情報、画像の中の文字、未知の形式の秘密の値までは見つけられません。公開前の目視確認は、今後も残します。
運用の工夫:状態を段階で分ける
作業の進み具合は、次の5段階で分けて記録しています。
ローカルの試験が通った段階で、「対策完了」とまとめてしまわないためです。
レビューでは「賛成か」より「何を確かめたか」を聞く
レビューを依頼するときも、「安全ですか?」ではなく、確認する対象を絞って聞くようにしています。
今回の変更について、次を確認してください。
1. 守ろうとしている境界は何か
2. その境界を検証する試験はどれか
3. 拒否の理由が、期待した理由と一致しているか
4. 対策を外したときに、その試験は失敗するか
5. ローカルの試験だけでは分からないことは何か
確認した事実、推測、未確認を分けて記録してください。
意見が分かれたときは、小さな検証に落とし込みます。ロールを変える、設定を1つ外す、期待値を変える。そうすれば、どちらの説明が正しいかを実際に確かめられます。
これから確かめたいこと
開発ツールのアクセス制限を、架空ファイルで試す
AIのコーディングツールのように、ファイルを読み、コマンドを実行するツールでは、どこまで制限が効いているかを実際に試す予定です。
たとえばClaude Codeのサンドボックスは、公式文書ではBashツールに対する制限として説明されています。これだけを見て、ファイル操作や外部連携を含むすべての経路が同じ制限を受けると決めつけないようにします。(Claude Code公式:Sandboxing)
本物の秘密情報を使わずに、次を確かめる計画です。
- 許可した架空ファイルは読めるか
- 許可していない架空ファイルは拒否されるか
- そのツールが起動した別のプログラムからも、制限が効くか
- 許可していない場所への書き込みは拒否されるか
- 失敗したときに、制限のない実行に切り替わってしまわないか
- 外部サービスへの「送る権限」と「読む権限」を分けられているか
依存関係の更新を、条件と一緒に整理する
依存ライブラリの脆弱性の指摘は、対象のバージョン、発生する条件、自分が使っている機能、実際に公開されている修正版を分けて整理します。指摘された脆弱性の名前だけで判断せず、公式の情報と実装を照らし合わせます。
端末と復旧の手順も対象にする
アプリだけでなく、開発に使うPCの暗号化、画面ロック、アカウントの保護、バックアップからの復旧も確認の対象です。ここでも「設定画面で有効になっていた」と「実際に復旧できた」を分けます。復旧の試験は、まず架空のファイルで手順を確かめます。
いまの到達点
| 項目 | 現在の到達点 |
|---|---|
| DBの権限の試験 | 隔離したローカル環境で、許可と拒否の両方を確認 |
| 試験の検出能力 | 正常な状態と、5種類の意図的な変更で確認 |
| 共有文書のスキャナー | 架空の入力で動作を確認 |
| レビュー | 指摘・証拠・未確認事項を文書で交換。追加の確認は継続中 |
| 開発ツールの実行環境の制限 | ツールごとの実測を計画中 |
| 運用への組み込み | 手動の検証から、継続的な実行へ進める段階 |
自分の環境で始めるなら:最初の一歩
- 守りたい境界を1つ選ぶ。 例:「ログインした人は、自分の記録しか読めない」。
- 使い捨ての環境を用意する。 本番の接続情報は使わず、架空データだけを入れる。
- 本番と同じ設定ファイルを適用する。 試験用に書き換えない。
- 拒否されることを、理由まで確かめる。 失敗したかどうかだけで判定しない。
- 許可されることも確かめる。 同じ操作が、許可された役割では成功すること。
- 対策をわざと外して、試験が落ちるかを見る。 落ちなければ、試験に穴がある。
- 確かめたことと、確かめていないことを分けて書く。
まとめ
今回の作業でいちばん大きかったのは、「設定を入れた」という記録から、何を実行し、何が起きれば合格なのかを説明できる記録へと変えていけたことです。
全部を一度に整える必要はありません。まず1つの境界を選び、許可と拒否の両方を試し、対策を外すと試験が落ちるところまで確かめる。その積み重ねを、次の開発でも使える形にしていきます。
注記: この記事は、2026年10月時点の個人開発での取り組みの記録です。隔離したローカル環境と架空データによる確認結果であり、本番環境の安全性を保証するものではありません。セキュリティ上の理由から、個別の構成、検証スクリプト、非公開の指摘内容は省略しています。