ブラックリストより、ホワイトリストで考えたい
プログラミングをしていると、「これは除外したい」「この条件は弾きたい」という実装をすることがあります。
私も最近、ある機能を作っているときに、最初は 不要なものを除外する 考え方で実装していました。
ただ、進めていくうちにコードがどんどん複雑になりました。
- この場合は除外
- ただし、この条件なら許可
- このパターンは例外
- この値も後から除外対象に追加
こうなってくると、読んでいてかなり不安になります。
そこで発想を逆にして、必要なものだけを許可する 形に変えてみました。
すると、コードがかなり読みやすくなりました。
除外リストは、増え続けるとつらい
例えば、ユーザー入力で特定の文字を禁止したいとします。
if input.match?(/[!"#$%&'()=~|`{}+*<>?_]/)
raise "禁止文字が含まれています"
end
一見すると分かりやすいです。
でも、禁止したい文字が増えたらどうでしょうか。
- 全角記号はどうするか
- 絵文字はどうするか
- 空白は許可するか
- ハイフンは許可するか
- 日本語は許可するか
禁止リスト方式だと、考慮漏れが怖くなります。
「これは弾いたけど、あれは弾けているのか?」という不安が残りやすいです。
許可リストにすると、意図が読みやすい
逆に、許可する範囲を先に決めるとどうでしょうか。
unless input.match?(/\A[a-zA-Z0-9_-]+\z/)
raise "許可されていない文字が含まれています"
end
このコードは、「英数字、アンダースコア、ハイフンだけを許可する」と読めます。
禁止対象を一つずつ考えるのではなく、通してよいものを明示する。
これだけで、読む側の負担がかなり減ります。
OWASP の Input Validation Cheat Sheet でも、入力検証の戦略として allowlist と denylist が整理されています。
Input Validation Cheat Sheet - OWASP
セキュリティの観点でも、何を許可するかを明確にすることはかなり大事です。
権限チェックでも同じことが言える
この考え方は、入力チェックだけではありません。
権限チェックでも同じです。
例えば、管理者以外を弾く実装を考えます。
unless current_user.guest? || current_user.viewer?
allow_access
end
この書き方だと、将来 support や operator のようなロールが増えたときに、意図せず通ってしまうかもしれません。
許可リストで書くなら、こうです。
if current_user.admin?
allow_access
else
deny_access
end
許可する人を明確にする。
このほうが、読み手は安心できます。
「誰を除外するか」ではなく、「誰なら許可するか」で考える。
この切り替えは、セキュアで読みやすいコードを書くうえでかなり効くと思っています。
許可リストが向いている場面
許可リスト方式は、特に次のような場面で向いています。
- 入力できる文字種が決まっている
- アップロードできる拡張子が決まっている
- 実行できる操作がロールごとに決まっている
- APIで受け付けるパラメータが決まっている
- ステータス遷移のパターンが決まっている
例えば、ファイルアップロードなら、
ALLOWED_EXTENSIONS = %w[.jpg .jpeg .png .webp].freeze
extension = File.extname(uploaded_file.original_filename).downcase
unless ALLOWED_EXTENSIONS.include?(extension)
raise "許可されていないファイル形式です"
end
のように、許可するものを明示できます。
この形なら、後から見たときに「この機能では何を許しているのか」がすぐ分かります。
ただし、何でも許可リストにすればいいわけではない
許可リストは便利ですが、万能ではありません。
自由入力の文章のように、許可する文字を厳密に絞りすぎると使いづらくなる場面もあります。
例えば、問い合わせフォームで日本語や記号を自然に入力したい場合、英数字だけを許可するのは現実的ではありません。
その場合は、
- 文字数制限
- 必須チェック
- HTMLやスクリプトの扱い
- 保存時と表示時のエスケープ
- サーバー側での検証
を組み合わせて考える必要があります。
許可リスト方式は「何でも厳しく縛る」ことではありません。
そのデータに対して、何を安全な入力として扱うのかを明確にする考え方です。
まとめ
「不要なものを除外する」実装は、最初は簡単に見えます。
でも、除外条件が増えるほど複雑になり、考慮漏れも怖くなります。
一方で、「必要なものだけを許可する」実装は、意図が読みやすく、変更にも強くなりやすいです。
迷ったら、まずこう考えるようにしたいです。
- 何を弾くか
- ではなく
- 何なら通してよいか
「良いだろう」で書いた実装は、レビューでだいたい指摘される でも書いたように、実装では意図を説明できることが大事です。
許可リスト方式は、その意図をコードに出しやすい書き方だと思います。