We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Preventing atom exhaustion attacks with `String.to_existing_atom`
almirsarajcic
Using String.to_atom/1
on user input is a critical security vulnerability that can crash your entire BEAM VM through atom exhaustion. Here are two approaches to safely convert user input to atoms - from manual validation to automatic protection.
# ❌ DANGEROUS - Never do this with user input!
def process_filter(params) do
params["filter_by"]
|> String.to_atom() # Atom table grows forever!
|> query_by()
end
# ✅ SAFER - Use String.to_existing_atom (prevents new atom creation)
def convert_filter_param(user_input) when is_binary(user_input) do
{:ok, String.to_existing_atom(user_input)}
rescue
ArgumentError -> {:error, :invalid_atom}
end
# Usage: only works if atom already exists in your app
convert_filter_param("status") # {:ok, :status} (if atom exists)
convert_filter_param("malicious") # {:error, :invalid_atom} (atom doesn't exist)
String.to_existing_atom/1
only converts strings to atoms that already exist in the atom table - if the atom doesn’t exist, it raises ArgumentError
. This prevents attackers from creating new atoms but still requires error handling.
Pro tip: For Phoenix apps, use Ecto.Enum
for the cleanest solution:
defmodule MyApp.FilterParams do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :filter_by, Ecto.Enum, values: [:status, :priority, :category, :type]
field :sort_by, Ecto.Enum, values: [:name, :created_at, :updated_at]
end
def changeset(attrs) do
%__MODULE__{}
|> cast(attrs, [:filter_by, :sort_by])
end
end
# In your controller - completely safe, no error handling needed
def index(conn, params) do
items = case FilterParams.changeset(params) do
%{valid?: true, changes: filters} ->
query_with_filters(filters) |> Repo.all()
_ ->
Repo.all(Item)
end
render(conn, :index, items: items)
end
Ecto.Enum
automatically handles safe atom conversion and validation - no manual error handling required. You can also monitor atom usage in production with :erlang.system_info(:atom_count)
.
copied to clipboard