Claude-elixir-phoenix security

Enforce Elixir/Phoenix security — auth, OAuth, sessions, CSRF, XSS, SQL injection, input validation, secrets. Use when editing auth files, login flows, RBAC, or API keys.

install
source · Clone the upstream repo
git clone https://github.com/oliver-kriska/claude-elixir-phoenix
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/oliver-kriska/claude-elixir-phoenix "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/elixir-phoenix/skills/security" ~/.claude/skills/oliver-kriska-claude-elixir-phoenix-security && rm -rf "$T"
manifest: plugins/elixir-phoenix/skills/security/SKILL.md
source content

Elixir/Phoenix Security Reference

Quick reference for security patterns in Elixir/Phoenix.

Iron Laws — Never Violate These

  1. VALIDATE AT BOUNDARIES — Never trust client input. All data through changesets
  2. NEVER INTERPOLATE USER INPUT — Use Ecto's
    ^
    operator, never string interpolation
  3. NO String.to_atom WITH USER INPUT — Atom exhaustion DoS. Use
    to_existing_atom/1
  4. AUTHORIZE EVERYWHERE — Check in contexts AND re-validate in LiveView events
  5. ESCAPE BY DEFAULT — Never use
    raw/1
    with untrusted content
  6. SECRETS NEVER IN CODE — All secrets in
    runtime.exs
    from env vars

Quick Patterns

Timing-Safe Authentication

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 Authorization (CRITICAL)

# 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

SQL Injection Prevention

# ✅ SAFE: Parameterized queries
from(u in User, where: u.name == ^user_input)

# ❌ VULNERABLE: String interpolation
from(u in User, where: fragment("name = '#{user_input}'"))

Quick Decisions

What to validate?

  • All user input → Ecto changesets
  • File uploads → Extension + magic bytes + size
  • Paths
    Path.safe_relative/2
    for traversal
  • Atoms
    String.to_existing_atom/1
    only

What to escape?

  • HTML output → Auto-escaped by default (
    <%= %>
    )
  • User HTML → HtmlSanitizeEx with scrubber
  • Never
    raw/1
    with untrusted content

Anti-patterns

WrongRight
"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 %>
Hardcoded secrets in config
runtime.exs
from env vars
Auth only in mountRe-auth in every
handle_event

References

For detailed patterns, see:

  • ${CLAUDE_SKILL_DIR}/references/authentication.md
    - phx.gen.auth, MFA, sessions
  • ${CLAUDE_SKILL_DIR}/references/authorization.md
    - Bodyguard, scopes, LiveView auth
  • ${CLAUDE_SKILL_DIR}/references/input-validation.md
    - Changesets, file uploads, paths
  • ${CLAUDE_SKILL_DIR}/references/security-headers.md
    - CSP, CSRF, rate limiting, headers
  • ${CLAUDE_SKILL_DIR}/references/oauth-linking.md
    - OAuth account linking, token management
  • ${CLAUDE_SKILL_DIR}/references/rate-limiting.md
    - Composite key strategies, Hammer patterns
  • ${CLAUDE_SKILL_DIR}/references/advanced-patterns.md
    - SSRF prevention, secrets management, supply chain