security について
Elixir と Phoenix の開発者向けセキュリティリファレンスです。入力検証、認可、エスケープ、シークレット管理、インジェクション対策をまとめ、LiveView イベントの確認方法も扱います。
主な機能
- 01changeset による境界での入力検証
- 02パラメーター化 Ecto クエリのパターン
- 03LiveView イベントの認可
- 04安全なエスケープと Atom の扱い
- 05ランタイムでのシークレット管理
活用シーン
- 01ユーザー入力処理の確認
- 02LiveView イベントハンドラーの保護
- 03SQL インジェクションと Atom 枯渇の防止
- 04シークレット保存と HTML エスケープの確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ oliver-kriska/ claude-elixir-phoenix --skill securityインストール後、Claude Code で/securityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-patterns.mdauthentication.mdauthorization.mdinput-validation.mdoauth-linking.mdrate-limiting.mdsecurity-headers.md詳細
Elixir/Phoenix セキュリティリファレンス
Ash プロジェクト:
AshAuthenticationには独自のストラテジー/トークンのパターンがあります。ash-frameworkスキルを使用してください。以下の CSRF、XSS、シークレット管理のパターンも引き続き適用されます。
Elixir/Phoenix のセキュリティパターンに関するクイックリファレンスです。
絶対に守るべき原則 — 例外なく遵守すること
- 境界で検証する — クライアントからの入力を決して信頼しないでください。すべてのデータを変更セット経由で処理します
- ユーザー入力を文字列に埋め込まない — Ecto の
^演算子を使用し、文字列補間は決して使用しないでください - ユーザー入力に
String.to_atomを使わない — アトム枯渇による DoS が発生します。to_existing_atom/1を使用してください - すべての箇所で認可する — コンテキストで確認し、LiveView のイベントでも再検証します
- デフォルトでエスケープする — 信頼できないコンテンツに
raw/1を決して使用しないでください - シークレットをコードに含めない — すべてのシークレットを環境変数から
runtime.exsに設定します - LIVEVIEW のイベントパラメーターは信頼できない — ユーザーは DevTools でフォーム、フック、すべての
phx-value-*を改変できます。処理を行う前に、サーバー側の状態に基づいて検証し、認可してください
クイックパターン
タイミングセーフな認証
def authenticate(email, password) do
user = Repo.get_by(User, email: email)
cond do
user && Argon2.verify_pass(password, user.hashed_password) ->
{:ok, user}
user ->
{:error, :invalid_credentials}
true ->
Argon2.no_user_verify() # Timing attack prevention
{:error, :invalid_credentials}
end
end
LiveView の認可(重大)
# `id` is client input even when it came from phx-value-id.
# RE-AUTHORIZE IN EVERY EVENT HANDLER
def handle_event("delete", %{"id" => id}, socket) do
post = Blog.get_post!(id)
# Don't trust that mount authorized this action!
with :ok <- Bodyguard.permit(Blog, :delete_post, socket.assigns.current_user, post) do
Blog.delete_post(post)
{:noreply, stream_delete(socket, :posts, post)}
else
_ -> {:noreply, put_flash(socket, :error, "Unauthorized")}
end
end
レンダリングされた LiveView イベントでは、HTML や WebSocket のペイロードに ID が含まれる場合があります。 それ自体が自動的に脆弱性となるわけではありません。ID は公開識別子として扱い、アクセス権の証明として 決して使用しないでください。識別子自体を公開してはならない場合に限り、不透明な参照を使用し、 その場合も必ずサーバー側で認可してください。
SQL インジェクションの防止
# ✅ SAFE: Parameterized queries
from(u in User, where: u.name == ^user_input)
# ❌ VULNERABLE: String interpolation
from(u in User, where: fragment("name = '#{user_input}'"))
クイック判断ガイド
何を検証するか?
- すべてのユーザー入力 → Ecto の変更セット
- ファイルアップロード → 拡張子+マジックバイト+サイズ
- パス → パストラバーサル対策に
Path.safe_relative/2 - アトム →
String.to_existing_atom/1のみ
何をエスケープするか?
- HTML 出力 → デフォルトで自動エスケープされます(
<%= %>) - ユーザー HTML → スクラバー付きの HtmlSanitizeEx
- 決して使用しない → 信頼できないコンテンツに対する
raw/1
アンチパターン
| 誤り | 正しい方法 |
|---|---|
"SELECT * FROM users WHERE name = '#{name}'" | from(u in User, where: u.name == ^name) |
String.to_atom(user_input) | String.to_existing_atom(user_input) |
<%= raw @user_comment %> | <%= @user_comment %> |
| 設定ファイルにハードコードされたシークレット | 環境変数から runtime.exs に設定 |
| mount でのみ認証する | 各 handle_event で再認証する |
phx-value-* や非表示の ID を信頼する | サーバー側の状態を読み込み、認可する |
参考資料
詳しいパターンについては、以下を参照してください。
${CLAUDE_SKILL_DIR}/references/authentication.md- phx.gen.auth、MFA、セッション${CLAUDE_SKILL_DIR}/references/authorization.md- Bodyguard、スコープ、LiveView の認可${CLAUDE_SKILL_DIR}/references/input-validation.md- 変更セット、ファイルアップロード、パス${CLAUDE_SKILL_DIR}/references/security-headers.md- CSP、CSRF、レート制限、ヘッダー${CLAUDE_SKILL_DIR}/references/oauth-linking.md- OAuth アカウントの連携、トークン管理${CLAUDE_SKILL_DIR}/references/rate-limiting.md- 複合キー戦略、Hammer のパターン${CLAUDE_SKILL_DIR}/references/advanced-patterns.md- SSRF の防止、シークレット管理、サプライチェーン
コメント
Markdown 対応