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.mdsource content
Elixir/Phoenix Security Reference
Quick reference for security patterns in Elixir/Phoenix.
Iron Laws — Never Violate These
- VALIDATE AT BOUNDARIES — Never trust client input. All data through changesets
- NEVER INTERPOLATE USER INPUT — Use Ecto's
operator, never string interpolation^ - NO String.to_atom WITH USER INPUT — Atom exhaustion DoS. Use
to_existing_atom/1 - AUTHORIZE EVERYWHERE — Check in contexts AND re-validate in LiveView events
- ESCAPE BY DEFAULT — Never use
with untrusted contentraw/1 - SECRETS NEVER IN CODE — All secrets in
from env varsruntime.exs
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 →
for traversalPath.safe_relative/2 - Atoms →
onlyString.to_existing_atom/1
What to escape?
- HTML output → Auto-escaped by default (
)<%= %> - User HTML → HtmlSanitizeEx with scrubber
- Never →
with untrusted contentraw/1
Anti-patterns
| Wrong | Right |
|---|---|
| |
| |
| |
| Hardcoded secrets in config | from env vars |
| Auth only in mount | Re-auth in every |
References
For detailed patterns, see:
- phx.gen.auth, MFA, sessions${CLAUDE_SKILL_DIR}/references/authentication.md
- Bodyguard, scopes, LiveView auth${CLAUDE_SKILL_DIR}/references/authorization.md
- Changesets, file uploads, paths${CLAUDE_SKILL_DIR}/references/input-validation.md
- CSP, CSRF, rate limiting, headers${CLAUDE_SKILL_DIR}/references/security-headers.md
- OAuth account linking, token management${CLAUDE_SKILL_DIR}/references/oauth-linking.md
- Composite key strategies, Hammer patterns${CLAUDE_SKILL_DIR}/references/rate-limiting.md
- SSRF prevention, secrets management, supply chain${CLAUDE_SKILL_DIR}/references/advanced-patterns.md