How to fix Business Logic Errors
in Phoenix
Executive Summary
Business Logic Errors (BLE) in Phoenix are the silent killers of Elixir backends. While Phoenix's immutability mitigates some memory safety issues, logic flaws—like price manipulation, unauthorized state transitions, or ID-based resource harvesting—thrive in the gap between the Controller and the Ecto Context. Hackers exploit these by tampering with parameters that should be server-controlled. Stop trusting the client; start enforcing state integrity at the database layer.
The Vulnerable Pattern
def create_order(conn, %{"item_id" => id, "price" => user_provided_price}) do
# VULNERABLE: Direct trust in client-supplied price parameter
# An attacker can intercept the request and change price from 100.00 to 0.01
case Orders.create_order(%{item_id: id, total_price: user_provided_price, user_id: conn.assigns.user.id}) do
{:ok, order} ->
render(conn, "show.json", order: order)
{:error, _changeset} ->
conn |> put_status(400) |> json(%{error: "Order failed"})
end
end
The Secure Implementation
The vulnerability stems from 'Parameter Injection'—allowing the client to define the value of a sensitive field (the price). The fix implements the 'Source of Truth' principle: the application fetches the price directly from the database based on a verified ID, completely ignoring user-supplied financial data. Furthermore, wrapping the logic in Ecto.Multi ensures that the check-and-insert operation is atomic, preventing race conditions (TOCTOU) where the product state might change between fetching and ordering.
def create_order(conn, %{"item_id" => id}) do # SECURE: Ignore client-side pricing. Fetch canonical price from the DB. # Use Ecto.Multi for atomicity to prevent race conditions. alias Ecto.Multiresult = Multi.new() |> Multi.run(:product, fn repo, _ -> case repo.get(Product, id) do nil -> {:error, :not_found} product -> {:ok, product} end end) |> Multi.insert(:order, fn %{product: product} -> %Order{item_id: id, total_price: product.price, user_id: conn.assigns.user.id} end) |> Repo.transaction()
case result do {:ok, %{order: order}} -> render(conn, “show.json”, order: order) {:error, _, _reason} -> conn |> put_status(422) |> json(%{error: “Invalid transaction”}) end end
Your Phoenix API
might be exposed to Business Logic Errors
74% of Phoenix apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.
Free Tier • No Credit Card • Instant Report
Verified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.